diff --git a/tensorbay/cli/branch.py b/tensorbay/cli/branch.py index 0aedac60f..8c2851225 100644 --- a/tensorbay/cli/branch.py +++ b/tensorbay/cli/branch.py @@ -8,13 +8,20 @@ import click from tensorbay.cli.tbrn import TBRN, TBRNType -from tensorbay.cli.utility import ContextInfo, error, exception_handler, get_dataset_client, shorten +from tensorbay.cli.utility import ( + ContextInfo, + error, + exception_handler, + get_dataset_client, + shorten, + sort_branches_or_tags, +) from tensorbay.client.gas import DatasetClientType @exception_handler -def _implement_branch( - obj: ContextInfo, tbrn: str, name: str, verbose: bool, is_delete: bool +def _implement_branch( # pylint: disable=too-many-arguments + obj: ContextInfo, tbrn: str, name: str, verbose: bool, is_delete: bool, sort_key: str ) -> None: tbrn_info = TBRN(tbrn=tbrn) if tbrn_info.type != TBRNType.DATASET: @@ -30,7 +37,7 @@ def _implement_branch( if name: _create_branch(dataset_client, name) else: - _list_branches(dataset_client, verbose) + _list_branches(dataset_client, verbose, sort_key) def _create_branch(dataset_client: DatasetClientType, name: str) -> None: @@ -42,8 +49,8 @@ def _create_branch(dataset_client: DatasetClientType, name: str) -> None: click.echo(f'Successfully created branch "{branch_tbrn}"') -def _list_branches(dataset_client: DatasetClientType, verbose: bool) -> None: - branches = dataset_client.list_branches() +def _list_branches(dataset_client: DatasetClientType, verbose: bool, sort_key: str) -> None: + branches = sort_branches_or_tags(sort_key, dataset_client.list_branches()) if not verbose: for branch in branches: click.echo(branch.name) diff --git a/tensorbay/cli/cli.py b/tensorbay/cli/cli.py index bcd23f528..4233f23c7 100644 --- a/tensorbay/cli/cli.py +++ b/tensorbay/cli/cli.py @@ -314,7 +314,7 @@ def rm(obj: ContextInfo, tbrn: str, is_recursive: bool) -> None: @command( synopsis=( - "$ gas branch tb: [--verbose] # List branches.", + "$ gas branch tb: [--verbose] [--sort=[-]] # List branches.", "$ gas branch tb:[@] # Create a branch.", "$ gas branch -d tb:@ # Delete a branch.", ) @@ -323,8 +323,16 @@ def rm(obj: ContextInfo, tbrn: str, is_recursive: bool) -> None: @click.argument("name", type=str, default="") @click.option("-v", "--verbose", is_flag=True, help="Show short commit id and commit message.") @click.option("-d", "--delete", "is_delete", is_flag=True, help="Delete the branch") +@click.option( + "--sort", + default="name", + help='Sort based on the key given, which can be "name" or "commit_date". ' + "Prefix - to sort in descending order of the value.", +) @click.pass_obj -def branch(obj: ContextInfo, tbrn: str, name: str, verbose: bool, is_delete: bool) -> None: +def branch( # pylint: disable=too-many-arguments + obj: ContextInfo, tbrn: str, name: str, verbose: bool, is_delete: bool, sort: str +) -> None: """List, create or delete branches.\f Arguments: @@ -333,16 +341,17 @@ def branch(obj: ContextInfo, tbrn: str, name: str, verbose: bool, is_delete: boo name: The name of the branch to be created. verbose: Whether to show the short commit id and commit message. is_delete: Whether to delete the branch. + sort: The key to sort on. """ # noqa: D301,D415 from tensorbay.cli.branch import _implement_branch - _implement_branch(obj, tbrn, name, verbose, is_delete) + _implement_branch(obj, tbrn, name, verbose, is_delete, sort) @command( synopsis=( - "$ gas tag tb: # List tags.", + "$ gas tag tb: [--sort=[-]] # List tags.", "$ gas tag tb:[@] # Create a tag.", "$ gas tag -d tb:@ # Delete a tag.", ) @@ -350,8 +359,14 @@ def branch(obj: ContextInfo, tbrn: str, name: str, verbose: bool, is_delete: boo @click.argument("tbrn", type=str) @click.argument("name", type=str, default="") @click.option("-d", "--delete", "is_delete", is_flag=True, help="Delete the tag.") +@click.option( + "--sort", + default="name", + help='Sort based on the key given, which can be "name" or "commit_date". ' + "Prefix - to sort in descending order of the value.", +) @click.pass_obj -def tag(obj: ContextInfo, tbrn: str, name: str, is_delete: bool) -> None: +def tag(obj: ContextInfo, tbrn: str, name: str, is_delete: bool, sort: str) -> None: """List, create or delete tags.\f Arguments: @@ -359,11 +374,12 @@ def tag(obj: ContextInfo, tbrn: str, name: str, is_delete: bool) -> None: tbrn: The tbrn of the dataset. name: The name of the tag. is_delete: Whether to delete the tag. + sort: The key to sort on. """ # noqa: D301,D415 from tensorbay.cli.tag import _implement_tag - _implement_tag(obj, tbrn, name, is_delete) + _implement_tag(obj, tbrn, name, is_delete, sort) @command( diff --git a/tensorbay/cli/tag.py b/tensorbay/cli/tag.py index aefeea065..7f4481d51 100644 --- a/tensorbay/cli/tag.py +++ b/tensorbay/cli/tag.py @@ -8,12 +8,18 @@ import click from tensorbay.cli.tbrn import TBRN, TBRNType -from tensorbay.cli.utility import ContextInfo, error, exception_handler, get_dataset_client +from tensorbay.cli.utility import ( + ContextInfo, + error, + exception_handler, + get_dataset_client, + sort_branches_or_tags, +) from tensorbay.client.gas import DatasetClientType @exception_handler -def _implement_tag(obj: ContextInfo, tbrn: str, name: str, is_delete: bool) -> None: +def _implement_tag(obj: ContextInfo, tbrn: str, name: str, is_delete: bool, sort_key: str) -> None: tbrn_info = TBRN(tbrn=tbrn) if tbrn_info.type != TBRNType.DATASET: @@ -27,7 +33,7 @@ def _implement_tag(obj: ContextInfo, tbrn: str, name: str, is_delete: bool) -> N elif name: _create_tag(dataset_client, name) else: - _list_tags(dataset_client) + _list_tags(dataset_client, sort_key) def _delete_tag(dataset_client: DatasetClientType, tbrn_info: TBRN) -> None: @@ -51,6 +57,6 @@ def _create_tag(dataset_client: DatasetClientType, name: str) -> None: click.echo(f'Successfully created tag "{tag_tbrn}"') -def _list_tags(dataset_client: DatasetClientType) -> None: - for tag in dataset_client.list_tags(): +def _list_tags(dataset_client: DatasetClientType, sort_key: str) -> None: + for tag in sort_branches_or_tags(sort_key, dataset_client.list_tags()): click.echo(tag.name) diff --git a/tensorbay/cli/utility.py b/tensorbay/cli/utility.py index 74c4c713a..e78686b83 100644 --- a/tensorbay/cli/utility.py +++ b/tensorbay/cli/utility.py @@ -11,7 +11,7 @@ from collections import OrderedDict from configparser import ConfigParser, SectionProxy from functools import wraps -from typing import Any, Callable, Iterable, Optional, Tuple, TypeVar, overload +from typing import Any, Callable, Iterable, Optional, Sequence, Tuple, TypeVar, overload import click from typing_extensions import Literal, NoReturn @@ -23,11 +23,18 @@ from tensorbay.client.gas import DatasetClientType from tensorbay.client.log import dump_request_and_response from tensorbay.client.requests import logger +from tensorbay.client.struct import Branch, Tag from tensorbay.exception import InternalServerError, TensorBayException _Callable = TypeVar("_Callable", bound=Callable[..., None]) +_T = TypeVar("_T", Tag, Branch) INDENT = " " * 4 +_SORT_KEYS = { + "name": lambda x: x.name, + "commit_date": lambda x: (x.committer.date, x.name), +} + class ContextInfo: """This class contains command context.""" @@ -430,3 +437,24 @@ def wrapper(*args: Any, **kwargs: Any) -> None: error(str(err)) return wrapper # type: ignore[return-value] + + +def sort_branches_or_tags(sort_key: str, target: Sequence[_T]) -> Sequence[_T]: + """Check whether the sort_key is valid and sort the target. + + Arguments: + sort_key: The key to sort on. + target: The target to be sorted. + + Returns: + The sorted target. + + """ + key, reverse = (sort_key, False) if sort_key[0] != "-" else (sort_key[1:], True) + if key not in _SORT_KEYS: + error( + 'The "--sort" option must be "name" or "commit_date" with an optional "-" before them' + ) + if not reverse and sort_key == "name": + return target + return sorted(target, key=_SORT_KEYS[key], reverse=reverse)