From 2cce73796cb0bfedeca11873bbcf5c01ea7ef5ee Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 1 Jul 2026 15:31:35 -0400 Subject: [PATCH 1/7] docs: Add design spec for updating getting_started.py with decorators --- ...07-01-getting-started-decorators-design.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-01-getting-started-decorators-design.md diff --git a/docs/superpowers/specs/2026-07-01-getting-started-decorators-design.md b/docs/superpowers/specs/2026-07-01-getting-started-decorators-design.md new file mode 100644 index 000000000..98ed4b727 --- /dev/null +++ b/docs/superpowers/specs/2026-07-01-getting-started-decorators-design.md @@ -0,0 +1,42 @@ +# Design: Update `getting_started.py` with Argument Parsers + +## Purpose +Update `examples/getting_started.py` to demonstrate the recommended best practices for defining command arguments using the `@with_argparser` and `@with_annotated` decorators. + +## Current State +The `do_intro` and `do_echo` methods in `examples/getting_started.py` currently accept a single `cmd2.Statement` argument. + +## Planned Changes + +### 1. `do_intro` (using `@with_annotated`) +Update the `do_intro` method to use the `@cmd2.with_annotated` decorator to automatically derive an argument parser from Python type hints. + +* **Arguments:** + * `interactive: bool = False`: A boolean flag (`--interactive` or `--no-interactive`). + * `repeat: int = 1`: An integer option (`--repeat`) specifying how many times to display the intro. +* **Behavior:** + * The method will loop `repeat` times, printing the intro banner. + * If `interactive` is true, an interactive prompt or a simulated interactive message can be displayed after the intro. For the scope of this example, we will just print an extra line noting the interactive mode is enabled. + +### 2. `do_echo` (using `@with_argparser`) +Update the `do_echo` method to use the `@cmd2.with_argparser` decorator with a custom-defined `Cmd2ArgumentParser`. + +* **Parser Definition:** + ```python + echo_parser = cmd2.Cmd2ArgumentParser(description="Multiline command that echoes input.") + echo_parser.add_argument("-u", "--upper", action="store_true", help="uppercase the output") + echo_parser.add_argument("-r", "--repeat", type=int, default=1, help="output [n] times") + echo_parser.add_argument("words", nargs="+", help="words to print") + ``` +* **Arguments received:** `args: argparse.Namespace` +* **Behavior:** + * Join the `args.words` list into a string. + * If `args.upper` is true, convert the string to uppercase. + * Print the stylized string `args.repeat` times. + +### 3. Documentation +* Update the top-level docstring in `examples/getting_started.py` to include mentioning that the script demonstrates argument parsing with `@with_argparser` and `@with_annotated`. +* Ensure the docstrings for `do_intro` and `do_echo` clearly explain their usage of the respective decorators. + +## Testing Strategy +The changes are in an example script. We will verify the changes by running the script manually and observing the help output and command execution to ensure the decorators are functioning as expected. We will also run `make check` to ensure code style compliance. From 3d2bbd0d0ed755de7b4e73690df7efd9ba4c4b86 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 1 Jul 2026 18:06:16 -0400 Subject: [PATCH 2/7] examples: Update getting_started.py with with_annotated and with_argparser decorators --- examples/getting_started.py | 46 +++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/examples/getting_started.py b/examples/getting_started.py index dc4d6ecc0..c90546eed 100755 --- a/examples/getting_started.py +++ b/examples/getting_started.py @@ -17,8 +17,11 @@ 13) Right prompt which displays contextual information 14) Background thread to update the content displayed by the bottom toolbar outside of the UI thread to keep things responsive 15) Using preloop() and postloop() hooks to start and stop a background thread +16) Using the with_annotated decorator to parse typed command arguments +17) Using the with_argparser decorator to parse command arguments with a custom parser """ +import argparse import datetime import pathlib import sys @@ -156,18 +159,43 @@ def get_rprompt(self) -> AnyFormattedText: text = f"cwd={current_working_directory}" return [(style, text)] - def do_intro(self, _: cmd2.Statement) -> None: - """Display the intro banner.""" - self.poutput(self.intro) + @cmd2.with_annotated + def do_intro(self, interactive: bool = False, repeat: int = 1) -> None: + """Display the intro banner. + + :param interactive: If True, prints a simulated interactive setup message. + :param repeat: Number of times to repeat the intro banner. + """ + for _ in range(repeat): + self.poutput(self.intro) + if interactive: + self.poutput( + stylize( + "Interactive mode enabled! (Simulated interactive setup)", + style=Style(color=Color.YELLOW.value), + ) + ) + + # do_echo parser + echo_parser = cmd2.Cmd2ArgumentParser(description="Multiline command that echoes input.") + echo_parser.add_argument("-u", "--upper", action="store_true", help="uppercase the output") + echo_parser.add_argument("-r", "--repeat", type=int, default=1, help="output [n] times") + echo_parser.add_argument("words", nargs="+", help="words to print") - def do_echo(self, arg: cmd2.Statement) -> None: + @cmd2.with_argparser(echo_parser) + def do_echo(self, args: argparse.Namespace) -> None: """Multiline command.""" - self.poutput( - stylize( - arg, - style=Style(color=self.foreground_color), + output_str = " ".join(args.words) + if args.upper: + output_str = output_str.upper() + + for _ in range(args.repeat): + self.poutput( + stylize( + output_str, + style=Style(color=self.foreground_color), + ) ) - ) if __name__ == "__main__": From 53b236bdd6a7d5df6e8e672ad2257093b290b276 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 1 Jul 2026 18:17:39 -0400 Subject: [PATCH 3/7] examples: Enhance do_intro decorator with full Annotated type hints and help text --- examples/getting_started.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/getting_started.py b/examples/getting_started.py index c90546eed..a6258bd4d 100755 --- a/examples/getting_started.py +++ b/examples/getting_started.py @@ -26,6 +26,7 @@ import pathlib import sys import threading +from typing import Annotated from prompt_toolkit.application import get_app from prompt_toolkit.formatted_text import AnyFormattedText @@ -36,6 +37,7 @@ Color, stylize, ) +from cmd2.annotated import Option class BasicApp(cmd2.Cmd): @@ -160,7 +162,11 @@ def get_rprompt(self) -> AnyFormattedText: return [(style, text)] @cmd2.with_annotated - def do_intro(self, interactive: bool = False, repeat: int = 1) -> None: + def do_intro( + self, + interactive: Annotated[bool, Option(help_text="If True, prints a simulated interactive setup message")] = False, + repeat: Annotated[int, Option(help_text="Number of times to repeat the intro banner")] = 1, + ) -> None: """Display the intro banner. :param interactive: If True, prints a simulated interactive setup message. From 580233c67d0df878b50f4d867e83f635b372ea72 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 1 Jul 2026 18:20:17 -0400 Subject: [PATCH 4/7] Use more complete annotation syntax for the with_annotated decorator --- examples/getting_started.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/getting_started.py b/examples/getting_started.py index a6258bd4d..5de640ca1 100755 --- a/examples/getting_started.py +++ b/examples/getting_started.py @@ -164,7 +164,9 @@ def get_rprompt(self) -> AnyFormattedText: @cmd2.with_annotated def do_intro( self, - interactive: Annotated[bool, Option(help_text="If True, prints a simulated interactive setup message")] = False, + interactive: Annotated[ + bool, Option(help_text="If True, prints a simulated interactive setup message after the intro banner") + ] = False, repeat: Annotated[int, Option(help_text="Number of times to repeat the intro banner")] = 1, ) -> None: """Display the intro banner. From c6465593f2a918bee96361304e97292fe648ac4b Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 1 Jul 2026 18:23:31 -0400 Subject: [PATCH 5/7] getting_started.py now demonstrates how to use both the @with_argparser and @with_annotated decorators --- examples/getting_started.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/getting_started.py b/examples/getting_started.py index 5de640ca1..21df8a72c 100755 --- a/examples/getting_started.py +++ b/examples/getting_started.py @@ -164,10 +164,10 @@ def get_rprompt(self) -> AnyFormattedText: @cmd2.with_annotated def do_intro( self, - interactive: Annotated[ + interactive: Annotated[ # Full annotation for an optional argument with default value and help text bool, Option(help_text="If True, prints a simulated interactive setup message after the intro banner") ] = False, - repeat: Annotated[int, Option(help_text="Number of times to repeat the intro banner")] = 1, + repeat: int = 1, # Simple annotation for an optional argument with default value but no help text ) -> None: """Display the intro banner. From 60020a662d423ef27a25af354790075e37d044af Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 1 Jul 2026 18:26:56 -0400 Subject: [PATCH 6/7] Removed unintentional commit --- ...07-01-getting-started-decorators-design.md | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 docs/superpowers/specs/2026-07-01-getting-started-decorators-design.md diff --git a/docs/superpowers/specs/2026-07-01-getting-started-decorators-design.md b/docs/superpowers/specs/2026-07-01-getting-started-decorators-design.md deleted file mode 100644 index 98ed4b727..000000000 --- a/docs/superpowers/specs/2026-07-01-getting-started-decorators-design.md +++ /dev/null @@ -1,42 +0,0 @@ -# Design: Update `getting_started.py` with Argument Parsers - -## Purpose -Update `examples/getting_started.py` to demonstrate the recommended best practices for defining command arguments using the `@with_argparser` and `@with_annotated` decorators. - -## Current State -The `do_intro` and `do_echo` methods in `examples/getting_started.py` currently accept a single `cmd2.Statement` argument. - -## Planned Changes - -### 1. `do_intro` (using `@with_annotated`) -Update the `do_intro` method to use the `@cmd2.with_annotated` decorator to automatically derive an argument parser from Python type hints. - -* **Arguments:** - * `interactive: bool = False`: A boolean flag (`--interactive` or `--no-interactive`). - * `repeat: int = 1`: An integer option (`--repeat`) specifying how many times to display the intro. -* **Behavior:** - * The method will loop `repeat` times, printing the intro banner. - * If `interactive` is true, an interactive prompt or a simulated interactive message can be displayed after the intro. For the scope of this example, we will just print an extra line noting the interactive mode is enabled. - -### 2. `do_echo` (using `@with_argparser`) -Update the `do_echo` method to use the `@cmd2.with_argparser` decorator with a custom-defined `Cmd2ArgumentParser`. - -* **Parser Definition:** - ```python - echo_parser = cmd2.Cmd2ArgumentParser(description="Multiline command that echoes input.") - echo_parser.add_argument("-u", "--upper", action="store_true", help="uppercase the output") - echo_parser.add_argument("-r", "--repeat", type=int, default=1, help="output [n] times") - echo_parser.add_argument("words", nargs="+", help="words to print") - ``` -* **Arguments received:** `args: argparse.Namespace` -* **Behavior:** - * Join the `args.words` list into a string. - * If `args.upper` is true, convert the string to uppercase. - * Print the stylized string `args.repeat` times. - -### 3. Documentation -* Update the top-level docstring in `examples/getting_started.py` to include mentioning that the script demonstrates argument parsing with `@with_argparser` and `@with_annotated`. -* Ensure the docstrings for `do_intro` and `do_echo` clearly explain their usage of the respective decorators. - -## Testing Strategy -The changes are in an example script. We will verify the changes by running the script manually and observing the help output and command execution to ensure the decorators are functioning as expected. We will also run `make check` to ensure code style compliance. From e03b8b8a2b5ae5951fa6ec56cc17fc64fd7a443e Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 2 Jul 2026 12:30:22 -0400 Subject: [PATCH 7/7] Added a static factory method to build the parser for the echo command --- examples/getting_started.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/getting_started.py b/examples/getting_started.py index 21df8a72c..0077e63f9 100755 --- a/examples/getting_started.py +++ b/examples/getting_started.py @@ -184,13 +184,16 @@ def do_intro( ) ) - # do_echo parser - echo_parser = cmd2.Cmd2ArgumentParser(description="Multiline command that echoes input.") - echo_parser.add_argument("-u", "--upper", action="store_true", help="uppercase the output") - echo_parser.add_argument("-r", "--repeat", type=int, default=1, help="output [n] times") - echo_parser.add_argument("words", nargs="+", help="words to print") - - @cmd2.with_argparser(echo_parser) + @staticmethod + def _build_echo_parser() -> cmd2.Cmd2ArgumentParser: + """Parser factory method for use with the echo command.""" + echo_parser = cmd2.Cmd2ArgumentParser(description="Multiline command that echoes input.") + echo_parser.add_argument("-u", "--upper", action="store_true", help="uppercase the output") + echo_parser.add_argument("-r", "--repeat", type=int, default=1, help="output [n] times") + echo_parser.add_argument("words", nargs="+", help="words to print") + return echo_parser + + @cmd2.with_argparser(_build_echo_parser) def do_echo(self, args: argparse.Namespace) -> None: """Multiline command.""" output_str = " ".join(args.words)