Skip to content

Commit

Permalink
refactor(cli): add CustomCommand to support custom help messages
Browse files Browse the repository at this point in the history
  • Loading branch information
Lee-000 committed Jun 21, 2021
1 parent 2d601c7 commit 8e13adb
Showing 1 changed file with 146 additions and 25 deletions.
171 changes: 146 additions & 25 deletions tensorbay/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,87 @@

"""Command-line interface.
Use 'gas' + COMMAND in terminal to operate on datasets.
Use 'gas <command>' in terminal to operate on datasets.
Use 'gas config' to configure environment.
Use ``gas auth`` to authenticate the accessKey of gas.
Use 'gas ls' to list data.
Use ``gas branch`` to list, create or delete branches.
Use 'gas draft' to operate a draft.
Use ``gas commit`` to commit drafts.
Use 'gas branch' to operate a branch.
Use ``gas config`` to configure the options when using gas CLI.
Use ``gas cp`` to copy local data to a remote path.
Use ``gas dataset`` to list, create or delete datasets.
Use ``gas draft`` to list or create drafts.
Use ``gas log`` to show commit logs.
Use ``gas ls`` to list data under the path.
Use ``gas rm`` to remove the remote data.
Use ``gas tag`` to list, create or delete tags.
"""

from functools import partial
from typing import Any, Dict, Iterable, Optional, Tuple

import click

from .. import __version__


class DeprecatedCommand(click.Command):
class CustomCommand(click.Command):
"""``click.Command`` wrapper class for CLI commands with custom help.
Arguments:
kwargs: The keyword arguments pass to ``click.Command``.
"""

def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.docstring = kwargs["help"]

def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
"""Writes the custom help into the formatter if it exists.
The custom method adds :meth:`CustomCommand.format_examples`
to the original ``Command.format_help`` method.
Arguments:
ctx: The context of the command.
formatter: The help formatter of the command.
"""
formatter.width = 100
self.format_usage(ctx, formatter)
self.format_help_text(ctx, formatter)
self.format_options(ctx, formatter)
self.format_examples(formatter)
self.format_epilog(ctx, formatter)

def format_examples(self, formatter: click.HelpFormatter) -> None:
"""Wirte the examples to the formatter if exist.
Arguments:
formatter: The help formatter of the command.
"""
examples = self.docstring.split("Examples:\n", 1)
if len(examples) == 1:
return

with formatter.section("Examples"):
for example in examples[-1].split("\n"):
formatter.write_text(example.strip())


class DeprecatedCommand(CustomCommand):
"""Customized ``click.Command`` wrapper class for deprecated CLI commands.
Arguments:
Expand Down Expand Up @@ -93,7 +154,7 @@ def invoke(self, ctx: click.Context) -> Any:
@click.option("-d", "--debug", is_flag=True, help="Debug mode.")
@click.pass_context
def cli(ctx: click.Context, access_key: str, url: str, profile_name: str, debug: bool) -> None:
"""You can use 'gas' + COMMAND to operate on your dataset.\f
"""You can use "gas <command>" to operate on your datasets.\f
Arguments:
ctx: The context to be passed as the first argument.
Expand All @@ -108,7 +169,10 @@ def cli(ctx: click.Context, access_key: str, url: str, profile_name: str, debug:
_implement_cli(ctx, access_key, url, profile_name, debug)


@cli.command(
command = partial(cli.command, cls=CustomCommand)


@command(
cls=DeprecatedCommand,
hidden=True,
since="v1.5.0",
Expand All @@ -130,7 +194,7 @@ def create(obj: Dict[str, str], name: str) -> None:
_implement_dataset(obj, name, is_delete=False, yes=False)


@cli.command(
@command(
cls=DeprecatedCommand,
hidden=True,
since="v1.5.0",
Expand All @@ -154,7 +218,7 @@ def delete(obj: Dict[str, str], name: str, yes: bool) -> None:
_implement_dataset(obj, name, is_delete=True, yes=yes)


@cli.command()
@command()
@click.argument("tbrn", type=str, default="")
@click.option(
"-a", "--all", "list_all_files", is_flag=True, help="List all files under the segment."
Expand All @@ -170,13 +234,18 @@ def ls( # pylint: disable=invalid-name
tbrn: Path to be listed, like "tb:KITTI:seg1". If empty, list names of all datasets.
list_all_files: If true, list all files under the segment.
Examples:
$ gas ls # List all the datasets.
$ gas ls tb:<dataset_name> # List segments of a dataset.
$ gas ls tb:<dataset_name>:<segment_name> # List files of a segment.
""" # noqa: D301,D415
from .ls import _implement_ls

_implement_ls(obj, tbrn, list_all_files)


@cli.command()
@command()
@click.argument("key", type=str, default="")
@click.argument("value", type=str, default="")
@click.option("-u", "--unset", is_flag=True, help="Unset the config option")
Expand All @@ -188,33 +257,43 @@ def config(key: str, value: str, unset: bool) -> None:
value: The option value.
unset: Whether to unset the option.
Examples:
$ gas config [key] # Show the config.
$ gas config <key> <value> # Set the config.
$ gas config --unset [key] # Unset the config.
""" # noqa: D301,D415
from .config import _implement_config

_implement_config(key, value, unset)


@cli.command()
@command()
@click.argument("tbrn", type=str, default="")
@click.option("-d", "--delete", "is_delete", is_flag=True, help="Delete TensorBay dataset")
@click.option("-y", "--yes", is_flag=True, help="Confirm to delete the dataset completely.")
@click.pass_obj
def dataset(obj: Dict[str, str], tbrn: str, is_delete: bool, yes: bool) -> None:
"""Work with TensorBay datasets\f
"""List, create or delete datasets.\f
Arguments:
obj: A dict including config information.
tbrn: The tbrn of the dataset, like "tb:KITTI".
is_delete: Whether to delete the TensorBay dataset.
yes: Confirm to delete the dataset completely.
Examples:
$ gas dataset # List datasets.
$ gas dataset tb:<dataset_name> # Create a dataset.
$ gas dataset -d [-y] tb:<dataset_name> # Delete a dataset.
""" # noqa: D301,D415
from .dataset import _implement_dataset

_implement_dataset(obj, tbrn, is_delete, yes)


@cli.command()
@command()
@click.argument("tbrn", type=str)
@click.option("-l", "--list", "is_list", is_flag=True, help="List the drafts.")
@click.option("-t", "--title", type=str, default="", help="The title of the draft.")
Expand All @@ -225,41 +304,48 @@ def draft(
is_list: bool,
title: str,
) -> None:
"""Work with draft.\f
"""List or create drafts.\f
Arguments:
obj: A dict contains config information.
tbrn: The tbrn of the dataset.
is_list: Whether to list the drafts.
title: The title of the draft.
Examples:
$ gas draft -l tb:<dataset_name> # List drafts.
$ gas draft tb:<dataset_name>[@<branch_name>] [-t <title>] # Create a draft.
""" # noqa: D301,D415
from .draft import _implement_draft

_implement_draft(obj, tbrn, is_list, title)


@cli.command()
@command()
@click.argument("tbrn", type=str)
@click.option(
"-m", "--message", type=str, multiple=True, default=(), help="The message of the commit."
)
@click.pass_obj
def commit(obj: Dict[str, str], tbrn: str, message: Tuple[str, ...]) -> None:
"""Work with commit.\f
"""Commit drafts.\f
Arguments:
obj: A dict contains config information.
tbrn: The path to commit a draft, like "tb:KITTI#1".
message: The message of the commit.
Examples:
$ gas commit tb:<dataset_name>#<draft_number> [-m <message>] # Commit a draft.
""" # noqa: D301,D415
from .commit import _implement_commit

_implement_commit(obj, tbrn, message)


@cli.command()
@command()
@click.argument("local_paths", type=str, nargs=-1)
@click.argument("tbrn", type=str, nargs=1)
@click.option(
Expand Down Expand Up @@ -292,13 +378,23 @@ def cp( # pylint: disable=invalid-name, too-many-arguments
jobs: Number of threads to upload data.
skip_uploaded_files: Whether skip the uploaded files.
Examples:
# Upload a file.
$ gas cp <local_path> tb:<dataset_name>#<draft_number>:<segment_name>[://<remote_path]
# Upload files.
$ gas cp <local_path1> [local_path2 ...] tb:<dataset_name>#<draft_number>:<segment_name>
# Upload files in a folder.
$ gas cp -r <local_folder> tb:<dataset_name>#<draft_number>:<segment_name>
""" # noqa: D301,D415
from .cp import _implement_cp

_implement_cp(obj, local_paths, tbrn, is_recursive, jobs, skip_uploaded_files)


@cli.command()
@command()
@click.argument("tbrn", type=str)
@click.option(
"-r", "--recursive", "is_recursive", is_flag=True, help="Remove directories recursively."
Expand All @@ -314,20 +410,24 @@ def rm( # pylint: disable=invalid-name, too-many-arguments
tbrn: The path to be removed, like "tb:KITTI#1".
is_recursive: Whether remove directories recursively.
Examples:
$ gas rm -r tb:<dataset_name>#<draft_number>:<segment_name> # Remove a segment.
$ gas rm tb:<dataset_name>@<revision>:<segment_name>://<remote_path> # Remove a file.
""" # noqa: D301,D415
from .rm import _implement_rm

_implement_rm(obj, tbrn, is_recursive)


@cli.command()
@command()
@click.argument("tbrn", type=str)
@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.pass_obj
def branch(obj: Dict[str, str], tbrn: str, name: str, verbose: bool, is_delete: bool) -> None:
"""Work with branch.\f
"""List, create or delete branches.\f
Arguments:
obj: A dict contains config information.
Expand All @@ -336,33 +436,43 @@ def branch(obj: Dict[str, str], tbrn: str, name: str, verbose: bool, is_delete:
verbose: Whether to show the short commit id and commit message.
is_delete: Whether to delete the branch.
Examples:
$ gas branch tb:<dataset_name> [--verbose] # List branches.
$ gas branch tb:<dataset_name>[@<branch_name>] <branch_name> # Create a branch.
$ gas branch --delete tb:<dataset_name>@<branch_name> # Delete a branch.
""" # noqa: D301,D415
from .branch import _implement_branch

_implement_branch(obj, tbrn, name, verbose, is_delete)


@cli.command()
@command()
@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.pass_obj
def tag(obj: Dict[str, str], tbrn: str, name: str, is_delete: bool) -> None:
"""Work with tag.\f
"""List, create or delete tags.\f
Arguments:
obj: A dict contains config information.
tbrn: The tbrn of the dataset.
name: The name of the tag.
is_delete: Whether to delete the tag.
Examples:
$ gas tag tb:<dataset_name> # List tags.
$ gas tag tb:<dataset_name>[@<revision>] <tag_name> # Create a tag.
$ gas tag -d tb:<dataset_name>@<tag_name> # Delete a tag.
""" # noqa: D301,D415
from .tag import _implement_tag

_implement_tag(obj, tbrn, name, is_delete)


@cli.command()
@command()
@click.argument("tbrn", type=str)
@click.option(
"-n", "--max-count", type=int, default=None, help="Limit the max number of commits to be showed"
Expand All @@ -383,13 +493,18 @@ def log(
max_count: Max number of commits to show.
oneline: Whether to show a commit message in oneline.
Examples:
$ gas log tb:<dataset_name>[@<branch_name>] # Show commit logs.
$ gas log -n <number> tb:<dataset_name>[@<branch_name>] # Show up to <number> commit logs.
$ gas log --oneline tb:<dataset_name>[@<branch_name>] # Show oneline commit logs.
""" # noqa: D301,D415
from .log import _implement_log

_implement_log(obj, tbrn, max_count, oneline)


@cli.command()
@command()
@click.argument("arg1", type=str, default="", metavar="accessKey")
@click.argument("arg2", type=str, default="", metavar="")
@click.option("-g", "--get", is_flag=True, help="Get the accesskey of the profile")
Expand All @@ -409,6 +524,12 @@ def auth( # pylint: disable=too-many-arguments
unset: Whether to unset the accesskey of the profile.
is_all: All the auth info or not.
Examples:
$ gas auth # Interactive authentication.
$ gas auth <AccessKey> # Authenticate with AccessKey.
$ gas auth --get [--all] # Get the authentication info.
$ gas auth --unset [--all] # Unset the authentication info.
""" # noqa: D301,D415
from .auth import _implement_auth

Expand Down

0 comments on commit 8e13adb

Please sign in to comment.