# Examples Below is a collection of standalone examples of specific argument/CLI behavior one might want, with comparison examples of how it might be done in each of Click/Typer/Argparse, where relevant. Please bring up any specific usage questions about comparisons between Cappa and other libraries as an [issue](https://github.com/dancardin/cappa/issues) or a [discussion](https://github.com/dancardin/cappa/discussion). The list below can and should grow in response to feature-comparisons which are not clear just from reading the cappa-specific documentation. ## Custom Option Value Name i.e. "FOO" in ``` $ prog --help Usage: prog --some-arg FOO ``` ````{admonition} Cappa :class: dropdown ```python from typing import Annotated from dataclasses import dataclass import cappa @dataclass class Args: some_arg: Annotated[str, cappa.Arg(value_name='FOO')] cappa.parse(Args) ``` ```` ````{admonition} Argparse :class: dropdown ```python import argparse parser = argparse.ArgumentParser() parser.add_argument("--some-arg", metavar="FOO") parser.parse_args() ``` ```` ````{admonition} Click :class: dropdown ```python import click @click.command() @click.option('--some-arg', metavar="FOO") def main(): ... main() ```` ````{admonition} Typer :class: dropdown ```python from typing_extensions import Annotated import typer def main(some_arg: Annotated[str, typer.Argument(metavar="FOO")]): ... typer.run(main) ``` ```` ## Unbounded option arguments i.e. `prog --foo 1 2 3 4 ...` ````{admonition} Cappa :class: dropdown ```python from typing import Annotated from dataclasses import dataclass import cappa @dataclass class Args: foo: Annotated[list[str], cappa.Arg(num_args=-1) print(cappa.parse(Args)) ``` ```` ````{admonition} Argparse :class: dropdown ```python import argparse parser = argparse.ArgumentParser() parser.add_argument("--foo", nargs="*") print(parser.parse_args()) ``` ```` ```{admonition} Click/Typer :class: dropdown Click's (and thus Typer) parser, at time of writing, appears to not support this feature ``` ## Interactive Prompt ``` $ prog What's your name?: Ron ``` ````{admonition} Cappa :class: dropdown ```python from typing import Annotated from dataclasses import dataclass from rich.prompt import Prompt import cappa @dataclass class Args: some_arg: Annotated[str, cappa.Arg(default=rich.Prompt("What's your name?"))] cappa.parse(Args) ``` ```` ```{admonition} Argparse :class: dropdown Argparse requires custom code or a [third party library](https://pypi.org/project/argparse-prompt/) to support this feature ``` ````{admonition} Click :class: dropdown ```python import click @click.command() @click.option('--name', prompt="What's your name?") def main(name): ... main() ```` ````{admonition} Typer :class: dropdown ```python import typer from typing_extensions import Annotated def main(name: str, lastname: Annotated[str, typer.Option(prompt="What's your name?")]): ... typer.run(main) ``` ```` ## Subcommands ``` $ prog double 123 246 $ prog print --title bar Bar ``` ````{admonition} Cappa :class: dropdown ```python from __future__ import annotations from dataclasses import dataclass import cappa @dataclass class Args: command: cappa.Subcommands[Double | Print] @dataclass class Double: arg: int def __call__(self): print(self.arg * 2) @dataclass class Print: text: str title: bool = False def __call__(self): text = self.text.title() if self.title else self.text print(text) cappa.invoke(Args) ``` Not the shortest, but (I think) it's still very intelligble. Certainly, the extra line or two dedicated to turning each command into a type can add some extra heft. The mechanism by which the subcommands are declared is (I think) the clearest of the 4. ```` ````{admonition} Argparse :class: dropdown ```python import argparse def double(arg: int): print(arg * 2) def print_command(text: str, title: bool): text = text.title() if title else text print(text) parser = argparse.ArgumentParser() subcommand = parser.add_subparsers(dest="command") double_subcommand = subcommand.add_parser(name="double") double_subcommand.add_argument("arg", type=int) print_subcommand = subcommand.add_parser(name="print") print_subcommand.add_argument("text") print_subcommand.add_argument("--title", action="store_true", default=False) args = parser.parse_args() if args.command == "double": double(args.arg) elif args.command == "print": print_command(args.text, args.title) ``` I had to look at the docs to implement, because the `parser.add_subparsers` -> `subparser.add_parser()` API is non-intuitive. Obviously the programmer needs to implement the subcommand dispatch that's handled for you in all the other options. Finally the resultant `args` namespace is completely untyped and you need to hope that the shape you described matches how you access it. ```` ````{admonition} Click :class: dropdown ```python import click @click.group() def main(): ... @main.command() @click.argument("arg", type=int) def double(arg: int): print(arg * 2) @main.command(name="print") @click.argument("text") @click.option("--title", is_flag=True, default=False) def print_command(text: str, title: str): text = text.title() if title else text print(text) main() ``` Subcommands (particularly nested ones beyond the complexity of this example) are click's strong suit. The main annoyance is that it's fairly repetitive. You need to manually choose the correct mapper/type, match the argument names yourself, and repeat the types if you want a typed function. ```` ````{admonition} Typer :class: dropdown ```python import typer app = typer.Typer(name="example") @app.command() def double(arg: int): print(arg * 2) @app.command("print") def print_command(text: str, title: bool = False): text = text.title() if title else text print(text) app() ``` Typer is certainly the shortest in this example, and has the least repetition. This is probably Typer's clearest case. It very simply and directly solves the repetition/mapping problem of click. ```` ## Console Output ``` $ prog World Hello World ``` ````{admonition} Cappa :class: dropdown ```python from __future__ import annotations from dataclasses import dataclass import cappa @dataclass class Args: text: str def __call__(self, output: cappa.Output): output(f"Hello {self.text}") cappa.invoke(Args) ``` In order to manually output text, depend on an `Output` instance to be provided a preconfigured output-able console object. Importantly, this will natively route output through rich, which gains easy styling. ```` ````{admonition} Argparse :class: dropdown ```python import argparse parser = argparse.ArgumentParser() parser.add_argument("text") args = parser.parse_args() print(f"Hello {args.text}") ``` Argparse just parses the arguments, so all choices of how to write output is left to the programmer. (Note, you can do this with cappa also, by using the `parse` interface) ```` ````{admonition} Click :class: dropdown ```python import click @main.command() @click.argument("text") def main(text: str): click.echo(f"Hello {text}) main() ``` ```` ````{admonition} Typer :class: dropdown ```python import typer def main(text: str): typer.echo(f"hello {text}") typer.run(app) ``` Typer is almost identical to click, because Typer is built on click. Typer also integrates well with rich, although it seems to encourage direct use of the rich Console-less API, which may(?) centralized control of styling within a CLI more challenging. ````