cmd2 Documentation

repository·main·Indexed 20 days ago

https://github.com/python-cmd2/cmd2

A Python framework for building immersive, interactive command-line applications. It extends the built-in cmd module to provide advanced features including deep tab completion, argparse integration, asynchronous command support, and automation via scripting and macros. Key components include the Cmd class for application initialization, CommandSet for modular command organization, and integration with the Rich library for enhanced terminal UI and formatting.

Tokens
64.1K
Snippets
208
Records
305
Agent score
70%

What's inside cmd2

  1. Overview of the cmd2 public API

    main

    The cmd2 public API is organized into several specialized modules. To ensure stability, only items explicitly documented in the API reference should be considered part of the public interface. Even if a class, method, or function does not start with an underscore, it may still be private and subject to change if it is not documented in the official API reference.

    cmd2 follows Semantic Versioning (SemVer) for all changes to its documented public API.

  2. Automatic features enabled by cmd2

    main

    Switching to cmd2 provides several high-level features automatically without additional configuration:

    • Enhanced History: Uses prompt-toolkit for a cross-platform experience. Includes a history command to edit prior commands in a text editor, re-run multiple commands, or save them as scripts.
    • Output Redirection & Piping: Supports redirecting output to files or piping to OS commands (provided you use self.stdout).
    • Scripting: Users can load and execute script files containing a series of commands.
    • Shortcuts, Aliases, and Macros: Built-in support for reducing repetitive typing.
    • Embedded Shells: Allows users to execute Python or IPython code directly from within the application.
    • Clipboard Integration: Enables saving command output to the operating system clipboard.
    • Command Timer: A built-in timer can display the execution duration of commands.
  3. Use cmd2.Cmd to build interactive command line applications

    main

    The cmd2.Cmd class is the primary entry point for building immersive interactive command line applications. By subclassing cmd2.Cmd, you can create a custom command loop that supports advanced features like command completion, command history, and sophisticated argument parsing. To start your application, you typically override the do_ methods for each command you wish to implement and then call the run() method to enter the interactive loop.

    import cmd2
    
    class MyApp(cmd2.Cmd):
        def do_hello(self, args):
            """Say hello to the user."""
            self.poutput(f"Hello, {args}!")
    
    if __name__ == '__main__':
        app = MyApp()
        app.cmdloop()
  4. Build parsers automatically with @with_annotated

    main

    The @with_annotated decorator is an experimental feature that builds an argparse parser automatically from the decorated function's Python type annotations. This eliminates the need for manual add_argument() calls.

    Warning: As this is experimental, the API may change in future releases.

  5. Understand the Command Processing Loop sequence

    main

    When cmd2.Cmd.cmdloop() is called, the following sequence is repeated for every command:

    1. Start prompt-toolkit event loop.
    2. Call pre_prompt.
    3. Output the prompt and accept input.
    4. Parse input into a cmd2.Statement object.
    5. Postparsing Hooks are called.
    6. Redirect output (if requested).
    7. Start timer.
    8. Precommand Hooks are called.
    9. Call precmd (for cmd.Cmd compatibility).
    10. Add statement to History.
    11. Call do_command (the actual command execution).
    12. Postcommand Hooks are called.
    13. Call postcmd (for cmd.Cmd compatibility).
    14. Stop timer and display elapsed time.
    15. Stop output redirection.
    16. Command Finalization Hooks are called.

    Error Handling: If a hook raises an exception, no further hooks (except command finalization hooks) are called, the command is not executed, and the exception message is displayed to the user.

  6. Use command history

    main

    Command history is enabled by default.

    User Interaction:

    • Use Control-p (previous) and Control-n (next) to navigate history.
    • Use Control-r to search through history.
    • These bindings follow Emacs-style conventions provided by the prompt-toolkit library.

    Built-in history command: Users can use the history command to:

    • View history by number, range, or string/regex search.
    • Re-run commands.
    • Edit commands in a text editor before execution.
    • Save commands and their output to files.
  7. Use Argument Blocks to reuse command arguments

    main

    When multiple commands share the same group of arguments, you can use cmd2.ArgumentBlock to avoid duplication. Subclass cmd2.ArgumentBlock on a @dataclass. Each field in the dataclass becomes a flat command-line argument. When the command is called, cmd2 reconstructs an instance of the dataclass and passes it as a parameter to the command method.

    Rules for Argument Blocks:

    • Trigger: The class must subclass cmd2.ArgumentBlock.
    • Usage: The block must be the bare annotation of a parameter (e.g., arg: MyBlock). Do not wrap it in Annotated, Optional, or use it with *args/**kwargs.
    • Defaults: Defaults must be defined on the dataclass field (e.g., field: int = 5) rather than in Option metadata. This ensures default_factory and __post_init__ work correctly.
    • No Recursion: A field within a block cannot itself be another ArgumentBlock.
    from dataclasses import dataclass
    from typing import Annotated
    from pathlib import Path
    import cmd2
    from cmd2 import with_annotated
    from cmd2.annotated import Option
    
    @dataclass
    class CommonArgs(cmd2.ArgumentBlock):
        verbose: Annotated[bool, Option("-v", "--verbose")] = False
        output: Annotated[Path | None, Option("--output")] = None
    
    class App(cmd2.Cmd):
        @with_annotated
        def do_build(self, target: str, common: CommonArgs):
            self.poutput(f"{target} verbose={common.verbose} output={common.output}")
  8. Modularize commands using CommandSet

    main

    Instead of defining all commands in a single cmd2.Cmd class, you can group them into cmd2.CommandSet objects. This allows for better organization, encapsulation of state, and easier plugin development.

    Key Rules for CommandSet Methods:

    • Command methods: Must be prefixed with do_.
    • Help methods: Must be prefixed with help_.
    • Completer methods: Must be prefixed with complete_.
    • self context: Inside a CommandSet, self refers to the CommandSet instance, not the main cmd2.Cmd instance. To access the main application, use self._cmd.

    Automatic Command Discovery

    To automatically discover and load all CommandSet objects that are imported into your application, initialize your cmd2.Cmd subclass with auto_load_commands=True.

    import cmd2
    from cmd2 import CommandSet
    
    class ExampleApp(cmd2.Cmd):
        def __init__(self, *args, **kwargs):
            # Setting auto_load_commands=True enables automatic discovery
            super().__init__(*args, auto_load_commands=True, **kwargs)
    
    class AutoLoadCommandSet(CommandSet[ExampleApp]):
        def do_hello(self, _: cmd2.Statement):
            """Hello Command."""
            # Access the main app via self._cmd
            self._cmd.poutput('Hello')
  9. Use Statement objects for advanced parsing

    main

    While command methods receive a string, cmd2 actually passes a cmd2.Statement object. This object is a subclass of str that provides access to parsed components of the user input, such as quoted arguments, output redirection, and piping. Using the argv attribute is a common way to handle arguments without manual parsing.

    Attributes of a Statement object:

    • command: The name of the command called.
    • args: The arguments string with output redirection or piping removed (quotes remain).
    • command_and_args: The command and arguments string with output redirection or piping removed.
    • argv: A list of arguments (like sys.argv), where argv[0] is the command. Quotes are stripped and redirection/piping is removed.
    • raw: The full input exactly as typed by the user.
    • terminator: The character used to end a multiline command.
  10. Design principles for `cmd2` API developers

    main

    When building a cmd2 application, consider the following design principles to ensure a good experience for both users and scripters:

    1. Design from the inside out: Implement core logic in low-level class libraries first. This makes the code easier to unit test and keeps the high-level API clean.
    2. Respect boundaries: A high-level Python script should interact with the command interface, not directly access low-level class libraries. This maintains encapsulation.
    3. Follow the Unix philosophy: Designing small, composable commands gives scripters the most flexibility to build complex workflows.
    4. Fail fast: Since cmd2 catches application exceptions, ensure your commands provide clear error messages in stderr so scripters can detect failures using the CommandResult object.
  11. Use Unions of Enums for multiple choice types

    main

    You can annotate a parameter as a Union of multiple Enum subclasses (e.g., Suit | Rank). The parser resolves the token using a first-match-wins strategy: the first member in the Union that accepts the token via its value, name, or _missing_ hook wins.

    Important Considerations:

    • Order matters: If a token is valid for multiple members, the first one listed in the Union is selected.
    • Exclusivity: Only Enum members are supported in the Union. Including Literal or non-Enum types will result in an ambiguity error.
    • Collisions: If value sets overlap significantly, consider using typed subcommands instead.
    @with_annotated
    def do_pick(self, choice: Suit | Rank) -> None:
        if isinstance(choice, Suit):
            self.poutput(f"suit {choice.name}")
        else:
            self.poutput(f"rank {choice.name}")