From d29abb427b4e863a9494b617d8b278a3bc70a24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Mar 2020 21:26:49 +0100 Subject: [PATCH 1/9] :sparkles: Generate docs in Markdown for Typer apps --- typer_cli/main.py | 229 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 174 insertions(+), 55 deletions(-) diff --git a/typer_cli/main.py b/typer_cli/main.py index d8976e0..c11231d 100644 --- a/typer_cli/main.py +++ b/typer_cli/main.py @@ -1,4 +1,5 @@ import importlib.util +import re import sys from pathlib import Path from typing import Any, Iterable, List, Optional, Tuple, cast @@ -17,18 +18,34 @@ default_func_names = ("main", "cli", "app") app = typer.Typer() +utils_app = typer.Typer(help="Extra utility commands for Typer applications.") +app.add_typer(utils_app, name="utils") class State: def __init__(self) -> None: - self.app = None - self.func = None + self.app: Optional[str] = None + self.func: Optional[str] = None + self.file: Optional[Path] = None + self.module: Optional[str] = None state = State() def maybe_update_state(ctx: click.Context) -> None: + path_or_module = ctx.params.get("path_or_module") + if path_or_module: + file_path = Path(path_or_module) + if file_path.exists() and file_path.is_file(): + state.file = file_path + else: + if not re.fullmatch(r"[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*", path_or_module): + typer.echo( + f"Not a valid file or Python module: {path_or_module}", err=True + ) + sys.exit(1) + state.module = path_or_module app_name = ctx.params.get("app") if app_name: state.app = app_name @@ -51,82 +68,84 @@ def invoke(self, ctx: click.Context) -> Any: return super().invoke(ctx) def maybe_add_run(self, ctx: click.Context) -> None: - file = ctx.params.get("file") - if file: - maybe_update_state(ctx) - sub_cli = generate_cli_from_path(ctx=ctx, file=file) - if sub_cli: - self.add_command(sub_cli, "run") - - -def generate_cli_from_path(*, ctx: click.Context, file: str) -> Optional[Command]: - file_path = Path(file) - module_name = file_path.name - spec = importlib.util.spec_from_file_location(module_name, str(file_path)) - if spec is None: - typer.echo(f"Could not import as Python the file: {file}", err=True) - sys.exit(1) - mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(mod) # type: ignore + maybe_update_state(ctx) + if state.file or state.module: + obj = get_typer_from_state() + if obj: + obj._add_completion = False + click_obj = typer.main.get_command(obj) + if not click_obj.help: + click_obj.help = "Run the provided Typer application." + self.add_command(click_obj, "run") + + +def get_typer_from_module(module: Any) -> Optional[typer.Typer]: # Try to get defined app if state.app: - obj: typer.Typer = getattr(mod, state.app, None) + obj: typer.Typer = getattr(module, state.app, None) if not isinstance(obj, typer.Typer): typer.echo(f"Not a Typer object: --app {state.app}", err=True) sys.exit(1) - obj._add_completion = False - click_obj = typer.main.get_command(obj) - return click_obj + return obj # Try to get defined function if state.func: - func_obj = getattr(mod, state.func, None) + func_obj = getattr(module, state.func, None) if not callable(func_obj): typer.echo(f"Not a function: --func {state.func}", err=True) sys.exit(1) sub_app = typer.Typer() sub_app.command()(func_obj) - sub_app._add_completion = False - click_obj = typer.main.get_command(sub_app) - return click_obj + return sub_app # Iterate and get a default object to use as CLI - local_names = dir(mod) + local_names = dir(module) local_names_set = set(local_names) # Try to get a default Typer app for name in default_app_names: if name in local_names_set: - obj = getattr(mod, name, None) + obj = getattr(module, name, None) if isinstance(obj, typer.Typer): - obj._add_completion = False - click_obj = typer.main.get_command(obj) - return click_obj + return obj # Try to get any Typer app for name in local_names_set - set(default_app_names): - obj = getattr(mod, name) + obj = getattr(module, name) if isinstance(obj, typer.Typer): - obj._add_completion = False - click_obj = typer.main.get_command(obj) - return click_obj + return obj # Try to get a default function for func_name in default_func_names: - func_obj = getattr(mod, func_name, None) + func_obj = getattr(module, func_name, None) if callable(func_obj): sub_app = typer.Typer() sub_app.command()(func_obj) - sub_app._add_completion = False - click_obj = typer.main.get_command(sub_app) - return click_obj + return sub_app # Try to get any func app for func_name in local_names_set - set(default_func_names): - func_obj = getattr(mod, func_name) + func_obj = getattr(module, func_name) if callable(func_obj): sub_app = typer.Typer() sub_app.command()(func_obj) - sub_app._add_completion = False - click_obj = typer.main.get_command(sub_app) - return click_obj + return sub_app return None +def get_typer_from_state() -> Optional[typer.Typer]: + spec = None + if state.file: + module_name = state.file.name + spec = importlib.util.spec_from_file_location(module_name, str(state.file)) + elif state.module: + spec = importlib.util.find_spec(state.module) # type: ignore + if spec is None: + if state.file: + typer.echo(f"Could not import as Python file: {state.file}", err=True) + else: + typer.echo(f"Could not import as Python module: {state.module}", err=True) + sys.exit(1) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) # type: ignore + obj = get_typer_from_module(module) + return obj + + def get_choices( cli: Command, prog_name: str, args: List[str], incomplete: str ) -> List[Tuple[str, str]]: @@ -134,12 +153,13 @@ def get_choices( if ctx.parent is None: assert isinstance(cli, Group) cli = cast(Group, cli) - file = ctx.params.get("file") - if file: - maybe_update_state(ctx) - sub_cli = generate_cli_from_path(ctx=ctx, file=file) - if sub_cli: - cli.add_command(sub_cli, "run") + maybe_update_state(ctx) + if state.file or state.module: + obj = get_typer_from_state() + if obj: + obj._add_completion = False + click_obj = typer.main.get_command(obj) + cli.add_command(click_obj, "run") return original_get_choices(cli, prog_name, args, incomplete) @@ -154,12 +174,111 @@ def print_version(ctx: click.Context, param: Option, value: bool) -> None: def callback( ctx: typer.Context, *, - file: Path = typer.Argument(..., exists=True, file_okay=True, dir_okay=True), - app: str = typer.Option(None, help="The typer app object/variable to use"), - func: str = typer.Option(None, help="The function to convert to Typer"), - version: bool = typer.Option(False, "--version", callback=print_version), # type: ignore + path_or_module: str = typer.Argument(None), + app: str = typer.Option(None, help="The typer app object/variable to use."), + func: str = typer.Option(None, help="The function to convert to Typer."), + version: bool = typer.Option( + False, "--version", help="Print version and exit.", callback=print_version # type: ignore + ), +) -> None: + """ + Typer CLI. + + Run Typer scripts with completion, without having to create a package. + + You probably want to install completion for the typer command: + + $ typer --install-completion + + https://typer.tiangolo.com/ + """ + maybe_update_state(ctx) + + +def get_docs_for_click( + *, + obj: Command, + ctx: typer.Context, + indent: int = 0, + name: str = "", + call_prefix: str = "", +) -> str: + docs = "#" * (1 + indent) + command_name = name or obj.name + title = f"`{command_name}`" if command_name else "CLI" + docs += f" {title}\n\n" + if obj.help: + docs += f"{obj.help}\n\n" + usage_pieces = obj.collect_usage_pieces(ctx) + if usage_pieces: + docs += "**Usage**:\n\n" + docs += "```console\n" + docs += "$ " + if call_prefix: + docs += f"{call_prefix} " + if command_name: + docs += f"{command_name} " + docs += f"{' '.join(usage_pieces)}\n" + docs += "```\n\n" + opts = [] + for param in obj.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None: + opts.append(rv) + if opts: + docs += f"**Options**:\n\n" + for opt_name, opt_help in opts: + docs += f"* `{opt_name}`" + if opt_help: + docs += f": {opt_help}" + docs += "\n" + docs += "\n" + if obj.epilog: + docs += f"{obj.epilog}\n\n" + if isinstance(obj, Group): + group: Group = cast(Group, obj) + commands = group.list_commands(ctx) + if commands: + docs += f"**Commands**:\n\n" + for command in commands: + command_obj = group.get_command(ctx, command) + assert command_obj + docs += f"* `{command_obj.name}`" + command_help = command_obj.get_short_help_str() + if command_help: + docs += f": {command_help}" + docs += "\n" + docs += "\n" + for command in commands: + command_obj = group.get_command(ctx, command) + assert command_obj + use_prefix = "" + if call_prefix: + use_prefix += f"{call_prefix} " + if command_name: + use_prefix += f"{command_name}" + docs += get_docs_for_click( + obj=command_obj, ctx=ctx, indent=indent + 1, call_prefix=use_prefix + ) + return docs + + +@utils_app.command() +def docs( + ctx: typer.Context, + name: str = typer.Option("", help="The name of the CLI program to use in docs."), ) -> None: - pass + """ + Generate Markdown docs for a Typer application. + """ + typer_obj = get_typer_from_state() + if not typer_obj: + typer.echo(f"No Typer app found", err=True) + raise typer.Abort() + click_obj = typer.main.get_command(typer_obj) + docs = get_docs_for_click(obj=click_obj, ctx=ctx, name=name) + clean_docs = docs.strip() + typer.echo(clean_docs) def main() -> Any: From a376b73187e31a7f37715ba282749dc1ef11edd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Mar 2020 21:27:59 +0100 Subject: [PATCH 2/9] :white_check_mark: Add tests for docs --- tests/assets/__init__.py | 0 tests/assets/multi_app.py | 15 +++++-- tests/assets/multiapp-docs.md | 82 +++++++++++++++++++++++++++++++++++ tests/test_doc.py | 70 ++++++++++++++++++++++++++++++ tests/test_help.py | 2 +- tests/test_multi_app.py | 2 +- tests/test_multi_app_sub.py | 2 +- tests/test_not_python.py | 2 +- tests/test_sub.py | 2 +- 9 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 tests/assets/__init__.py create mode 100644 tests/assets/multiapp-docs.md create mode 100644 tests/test_doc.py diff --git a/tests/assets/__init__.py b/tests/assets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/assets/multi_app.py b/tests/assets/multi_app.py index 313e6a3..1c85b7f 100644 --- a/tests/assets/multi_app.py +++ b/tests/assets/multi_app.py @@ -6,19 +6,28 @@ @sub_app.command() -def hello(): - typer.echo("sub hello") +def hello(name: str = "World"): + """ + Say Hello + """ + typer.echo(f"Hello {name}") @sub_app.command() def bye(): + """ + Say bye + """ typer.echo("sub bye") -app = typer.Typer() +app = typer.Typer(help="Demo App", epilog="The end") app.add_typer(sub_app, name="sub") @app.command() def top(): + """ + Top command + """ typer.echo("top") diff --git a/tests/assets/multiapp-docs.md b/tests/assets/multiapp-docs.md new file mode 100644 index 0000000..a181e01 --- /dev/null +++ b/tests/assets/multiapp-docs.md @@ -0,0 +1,82 @@ +# `multiapp` + +Demo App + +**Usage**: + +```console +$ multiapp [OPTIONS] COMMAND [ARGS]... +``` + +**Options**: + +* `--install-completion`: Install completion for the current shell. +* `--show-completion`: Show completion for the current shell, to copy it or customize the installation. +* `--help`: Show this message and exit. + +The end + +**Commands**: + +* `sub` +* `top`: Top command + +## `sub` + +**Usage**: + +```console +$ multiapp sub [OPTIONS] COMMAND [ARGS]... +``` + +**Options**: + +* `--help`: Show this message and exit. + +**Commands**: + +* `bye`: Say bye +* `hello`: Say Hello + +### `bye` + +Say bye + +**Usage**: + +```console +$ multiapp sub bye [OPTIONS] +``` + +**Options**: + +* `--help`: Show this message and exit. + +### `hello` + +Say Hello + +**Usage**: + +```console +$ multiapp sub hello [OPTIONS] +``` + +**Options**: + +* `--name TEXT` +* `--help`: Show this message and exit. + +## `top` + +Top command + +**Usage**: + +```console +$ multiapp top [OPTIONS] +``` + +**Options**: + +* `--help`: Show this message and exit. diff --git a/tests/test_doc.py b/tests/test_doc.py new file mode 100644 index 0000000..18277a3 --- /dev/null +++ b/tests/test_doc.py @@ -0,0 +1,70 @@ +import subprocess +from pathlib import Path + + +def test_doc(): + result = subprocess.run( + [ + "coverage", + "run", + "-m", + "typer_cli", + "tests.assets.multi_app", + "utils", + "docs", + "--name", + "multiapp", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + ) + docs_path: Path = Path(__file__).parent / "assets/multiapp-docs.md" + docs = docs_path.read_text() + assert docs in result.stdout + + +def test_doc_not_existing(): + result = subprocess.run( + ["coverage", "run", "-m", "typer_cli", "no_typer", "utils", "docs"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + ) + assert "Could not import as Python module:" in result.stderr + + +def test_doc_no_typer(): + result = subprocess.run( + [ + "coverage", + "run", + "-m", + "typer_cli", + "tests/assets/empty_script.py", + "utils", + "docs", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + ) + assert "No Typer app found" in result.stderr + + +def test_doc_file_not_existing(): + result = subprocess.run( + [ + "coverage", + "run", + "-m", + "typer_cli", + "assets/not_existing.py", + "utils", + "docs", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + ) + assert "Not a valid file or Python module:" in result.stderr diff --git a/tests/test_help.py b/tests/test_help.py index 2f67c17..168f83b 100644 --- a/tests/test_help.py +++ b/tests/test_help.py @@ -18,4 +18,4 @@ def test_not_python(): stderr=subprocess.PIPE, encoding="utf-8", ) - assert "Could not import as Python the file" in result.stderr + assert "Could not import as Python file" in result.stderr diff --git a/tests/test_multi_app.py b/tests/test_multi_app.py index 888a49d..e6c1367 100644 --- a/tests/test_multi_app.py +++ b/tests/test_multi_app.py @@ -94,7 +94,7 @@ def test_script_sub_hello(): stderr=subprocess.PIPE, encoding="utf-8", ) - assert "sub hello" in result.stdout + assert "Hello World" in result.stdout def test_script_sub_bye(): diff --git a/tests/test_multi_app_sub.py b/tests/test_multi_app_sub.py index 30912e0..a91ba8c 100644 --- a/tests/test_multi_app_sub.py +++ b/tests/test_multi_app_sub.py @@ -40,4 +40,4 @@ def test_script(): stderr=subprocess.PIPE, encoding="utf-8", ) - assert "sub hello" in result.stdout + assert "Hello World" in result.stdout diff --git a/tests/test_not_python.py b/tests/test_not_python.py index b9e46b4..43c8051 100644 --- a/tests/test_not_python.py +++ b/tests/test_not_python.py @@ -8,4 +8,4 @@ def test_not_python(): stderr=subprocess.PIPE, encoding="utf-8", ) - assert "Could not import as Python the file" in result.stderr + assert "Could not import as Python file" in result.stderr diff --git a/tests/test_sub.py b/tests/test_sub.py index 73df944..38fd18d 100644 --- a/tests/test_sub.py +++ b/tests/test_sub.py @@ -106,4 +106,4 @@ def test_not_python(): stderr=subprocess.PIPE, encoding="utf-8", ) - assert "Could not import as Python the file" in result.stderr + assert "Could not import as Python file" in result.stderr From 91d42a36ac0f2a0ab91945f6d8e6329b5d9192d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Mar 2020 21:34:16 +0100 Subject: [PATCH 3/9] :speech_balloon: Update help strings --- typer_cli/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/typer_cli/main.py b/typer_cli/main.py index c11231d..e1f49c8 100644 --- a/typer_cli/main.py +++ b/typer_cli/main.py @@ -18,7 +18,7 @@ default_func_names = ("main", "cli", "app") app = typer.Typer() -utils_app = typer.Typer(help="Extra utility commands for Typer applications.") +utils_app = typer.Typer(help="Extra utility commands for Typer apps.") app.add_typer(utils_app, name="utils") @@ -75,7 +75,7 @@ def maybe_add_run(self, ctx: click.Context) -> None: obj._add_completion = False click_obj = typer.main.get_command(obj) if not click_obj.help: - click_obj.help = "Run the provided Typer application." + click_obj.help = "Run the provided Typer app." self.add_command(click_obj, "run") @@ -269,7 +269,7 @@ def docs( name: str = typer.Option("", help="The name of the CLI program to use in docs."), ) -> None: """ - Generate Markdown docs for a Typer application. + Generate Markdown docs for a Typer app. """ typer_obj = get_typer_from_state() if not typer_obj: From 2ac159f5fd749795a1b949ea03832d861f18e6b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Mar 2020 21:35:27 +0100 Subject: [PATCH 4/9] :art: Format code --- typer_cli/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer_cli/main.py b/typer_cli/main.py index e1f49c8..1ebdfc1 100644 --- a/typer_cli/main.py +++ b/typer_cli/main.py @@ -183,7 +183,7 @@ def callback( ) -> None: """ Typer CLI. - + Run Typer scripts with completion, without having to create a package. You probably want to install completion for the typer command: From 07b9f9d032cb6081152c754b1b11374c501df703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Mar 2020 22:00:30 +0100 Subject: [PATCH 5/9] :sparkles: Add CLI Option to save docs to file --- typer_cli/main.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/typer_cli/main.py b/typer_cli/main.py index 1ebdfc1..8bd0a67 100644 --- a/typer_cli/main.py +++ b/typer_cli/main.py @@ -267,6 +267,12 @@ def get_docs_for_click( def docs( ctx: typer.Context, name: str = typer.Option("", help="The name of the CLI program to use in docs."), + output: Path = typer.Option( + None, + help="An output file to write docs to, like README.md.", + file_okay=True, + dir_okay=False, + ), ) -> None: """ Generate Markdown docs for a Typer app. @@ -277,8 +283,12 @@ def docs( raise typer.Abort() click_obj = typer.main.get_command(typer_obj) docs = get_docs_for_click(obj=click_obj, ctx=ctx, name=name) - clean_docs = docs.strip() - typer.echo(clean_docs) + clean_docs = f"{docs.strip()}\n" + if output: + output.write_text(clean_docs) + typer.echo(f"Docs saved to: {output}") + else: + typer.echo(clean_docs) def main() -> Any: From c860b32728872922566055ca0e73b7cd44907a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Mar 2020 22:00:50 +0100 Subject: [PATCH 6/9] :white_check_mark: Add tests for docs --- tests/test_doc.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_doc.py b/tests/test_doc.py index 18277a3..71b8d04 100644 --- a/tests/test_doc.py +++ b/tests/test_doc.py @@ -24,6 +24,33 @@ def test_doc(): assert docs in result.stdout +def test_doc_output(tmp_path: Path): + out_file: Path = tmp_path / "out.md" + result = subprocess.run( + [ + "coverage", + "run", + "-m", + "typer_cli", + "tests.assets.multi_app", + "utils", + "docs", + "--name", + "multiapp", + "--output", + str(out_file), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + ) + docs_path: Path = Path(__file__).parent / "assets/multiapp-docs.md" + docs = docs_path.read_text() + written_docs = out_file.read_text() + assert docs in written_docs + assert "Docs saved to:" in result.stdout + + def test_doc_not_existing(): result = subprocess.run( ["coverage", "run", "-m", "typer_cli", "no_typer", "utils", "docs"], From 7b37cdb21dd44aa5676d97b702a4d08584de75c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Mar 2020 22:01:06 +0100 Subject: [PATCH 7/9] :memo: Update README --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b5a39c..f376b76 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ if __name__ == "__main__": You can also remove it if you are calling that script only with **Typer CLI** (using the `typer` command). -## What can it run +## Run other files **Typer CLI** can run any script with **Typer**, but the script doesn't even have to use **Typer** at all. @@ -167,6 +167,25 @@ Hello Camila And it will also have completion for things like the `--name` **CLI Option**. +## Run a package or module + +Instead of a file path you can pass a module (possibly in a package) to import. + +For example: + +```console +$ typer my_package.main run --help +Usage: typer run [OPTIONS] + +Options: + --name TEXT + --help Show this message and exit. + +$ typer my_package.main run --name Camila + +Hello Camila +``` + ## Options You can specify the following **CLI Options**: @@ -185,6 +204,33 @@ When your run a script with the **Typer CLI** (the `typer` command) it will use * A function in a variable with a name of `main`, `cli`, or `app`. * The first function in the file, with any name. +## Generate docs + +**Typer CLI** can also generate Markdown documentation for your **Typer** application. + +You can use the subcommand `utils`. + +And then the subcommand `docs`. + +For example: + +```console +$ typer some_script.py utils docs +``` + +**Options**: + +* `--name TEXT`: The name of the CLI program to use in docs. +* `--output FILE`: An output file to write docs to, like README.md. + +For example: + +```console +$ typer my_package.main utils docs --name myapp --output README.md + +Docs saved to: README.md +``` + ## License This project is licensed under the terms of the MIT license. From e43dfd5c2efa28bf6ece2c6a4beec05963c45d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Mar 2020 22:10:21 +0100 Subject: [PATCH 8/9] :recycle: Update subcommand titles to include parent command --- typer_cli/main.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/typer_cli/main.py b/typer_cli/main.py index 8bd0a67..dd08d1d 100644 --- a/typer_cli/main.py +++ b/typer_cli/main.py @@ -205,6 +205,8 @@ def get_docs_for_click( ) -> str: docs = "#" * (1 + indent) command_name = name or obj.name + if call_prefix: + command_name = f"{call_prefix} {command_name}" title = f"`{command_name}`" if command_name else "CLI" docs += f" {title}\n\n" if obj.help: @@ -214,8 +216,6 @@ def get_docs_for_click( docs += "**Usage**:\n\n" docs += "```console\n" docs += "$ " - if call_prefix: - docs += f"{call_prefix} " if command_name: docs += f"{command_name} " docs += f"{' '.join(usage_pieces)}\n" @@ -253,8 +253,6 @@ def get_docs_for_click( command_obj = group.get_command(ctx, command) assert command_obj use_prefix = "" - if call_prefix: - use_prefix += f"{call_prefix} " if command_name: use_prefix += f"{command_name}" docs += get_docs_for_click( From f50e414a432fed9899e9b5b76887005bbadfbffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Mar 2020 22:11:02 +0100 Subject: [PATCH 9/9] :white_check_mark: Update docs tests assets --- tests/assets/multiapp-docs.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/assets/multiapp-docs.md b/tests/assets/multiapp-docs.md index a181e01..40b0d9a 100644 --- a/tests/assets/multiapp-docs.md +++ b/tests/assets/multiapp-docs.md @@ -21,7 +21,7 @@ The end * `sub` * `top`: Top command -## `sub` +## `multiapp sub` **Usage**: @@ -38,7 +38,7 @@ $ multiapp sub [OPTIONS] COMMAND [ARGS]... * `bye`: Say bye * `hello`: Say Hello -### `bye` +### `multiapp sub bye` Say bye @@ -52,7 +52,7 @@ $ multiapp sub bye [OPTIONS] * `--help`: Show this message and exit. -### `hello` +### `multiapp sub hello` Say Hello @@ -67,7 +67,7 @@ $ multiapp sub hello [OPTIONS] * `--name TEXT` * `--help`: Show this message and exit. -## `top` +## `multiapp top` Top command