Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 4.2.2 (August 25, 2026)

- Documentation Improvements
- Improved documentation in attempt at making some recommended best-practices more discoverable
- Fixed the broken `cmd_as_argument` example

## 4.2.1 (August 22, 2026)

- Enhancements
Expand Down Expand Up @@ -1894,7 +1900,7 @@ time reading the [rich documentation](https://rich.readthedocs.io/).
- **with_argument_list** decorator to change argument type from str to List[str]
- **do\_\*** commands get a single argument which is a list of strings, as pre-parsed by
shlex.split()
- **with_arparser** decorator for strict argparse-based argument parsing of command
- **with_argparser** decorator for strict argparse-based argument parsing of command
arguments
- **do\_\*** commands get a single argument which is the output of argparse.parse_args()
- **with_argparser_and_unknown_args** decorator for argparse-based argument parsing, but
Expand Down
54 changes: 21 additions & 33 deletions docs/features/os.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,25 +75,25 @@ Either of these methods will launch your program and enter the `cmd2` command lo
user to enter commands, which are then executed by your program.

You may want to execute commands in your program without prompting the user for any input. There are
several ways you might accomplish this task. The easiest one is to pipe commands and their arguments
into your program via standard input. You don't need to do anything to your program in order to use
this technique. Here's a demonstration using the `examples/cmd_as_argument.py` included in the
source code of `cmd2`:
several ways you might accomplish this task. One is to pipe commands and their arguments into your
program via standard input. You don't need to do anything to your program in order to use this
technique. Here's a demonstration using the `examples/cmd_as_argument.py` included in the source
code of `cmd2`:

$ echo "speak -p some words" | python examples/cmd_as_argument.py
$ echo "speak -p some words" | uv run examples/cmd_as_argument.py
omesay ordsway

Using this same approach you could create a text file containing the commands you would like to run,
one command per line in the file. Say your file was called `somecmds.txt`. To run the commands in
the text file using your `cmd2` program (from a Windows command prompt):

c:\cmd2> type somecmds.txt | python.exe examples/cmd_as_argument.py
c:\cmd2> type somecmds.txt | uv run examples/cmd_as_argument.py
omesay ordsway

By default, `cmd2` programs also look for commands passed as arguments from the operating system
shell, and execute those commands before entering the command loop:

$ python examples/cmd_as_argument.py help
$ uv run examples/cmd_as_argument.py help

Documented Commands
───────────────────
Expand All @@ -105,35 +105,23 @@ shell, and execute those commands before entering the command loop:
You may need more control over command line arguments passed from the operating system shell. For
example, you might have a command inside your `cmd2` program which itself accepts arguments, and
maybe even option strings. Say you wanted to run the `speak` command from the operating system
shell, but have it say it in pig latin:

$ python examples/cmd_as_argument.py speak -p hello there
python cmd_as_argument.py speak -p hello there
usage: speak [-h] [-p] [-s] [-r REPEAT] words [words ...]
speak: error: the following arguments are required: words
*** Unknown syntax: -p
*** Unknown syntax: hello
*** Unknown syntax: there
(Cmd)

Uh-oh, that's not what we wanted. `cmd2` treated `-p`, `hello`, and `there` as commands, which don't
exist in that program, thus the syntax errors.

There is an easy way around this, which is demonstrated in
[cmd_as_argument.py](https://github.com/python-cmd2/cmd2/blob/main/examples/cmd_as_argument.py)
example. By setting `allow_cli_args=False` you can do your own argument parsing of the command line:
shell, but have it say it in pig latin - just group the command and its arguments inside either
double or single quotes:

$ python examples/cmd_as_argument.py speak -p hello there
$ uv run examples/cmd_as_argument.py "speak -p hello there"
ellohay heretay
(Cmd)

Check the source code of this example, especially the `main()` function, to see the technique.
If you want to start your application using a custom `argparse` parser to collect high-level
application arguments but still want to be able to pass extra unknown arguments as commands at
invocation, then see the
[argparse_example.py](https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_example.py)
example. Using this methodology you can call it like so:

Alternatively you can simply wrap the command plus arguments in quotes (either single or double
quotes):
$ uv run examples/argparse_example.py -c blue help

$ python examples/cmd_as_argument.py "speak -p hello there"
ellohay heretay
(Cmd)
Check the source code of this example, especially the `if __name__ == "__main__":` block, to see the
technique.

### Automating cmd2 apps from other CLI/CLU tools

Expand All @@ -150,13 +138,13 @@ This is easily achieved by combining the following capabilities of `cmd2`:
Here is a simple example which doesn't require the quit command since the custom `exit` command
quits while returning an exit code:

$ python examples/exit_code.py "exit 23"
$ uv run examples/exit_code.py "exit 23"
'examples/exit_code.py' exiting with code: 23
$ echo $?
23

Here is another example using `quit`:

$ python examples/cmd_as_argument.py "speak -p hello there" quit
$ uv run examples/cmd_as_argument.py "speak -p hello there" quit
ellohay heretay
$
25 changes: 19 additions & 6 deletions docs/features/startup_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ initializing so they are guaranteed to run before any _Commands At Invocation_.

## Commands At Invocation

You can send commands to your app as you invoke it by including them as extra arguments to the
program. `cmd2` interprets each argument as a separate command, so you should enclose each command
in quotation marks if it is more than a one-word command. You can use either single or double quotes
for this purpose.
By default, users can send commands to your app as you invoke it by including them as extra
arguments to the program. `cmd2` interprets each argument as a separate command, so you should
enclose each command in quotation marks if it is more than a one-word command. You can use either
single or double quotes for this purpose.

$ python examples/cmd_as_argument.py "say hello" "say Gracie" quit
$ uv run examples/cmd_as_argument.py "say hello" "say Gracie" quit
hello
Gracie

Expand All @@ -26,7 +26,9 @@ application and easily used in automation.

!!! note

If you wish to disable cmd2's consumption of command-line arguments, you can do so by setting the `allow_cli_args` argument of your [cmd2.Cmd][] class instance to `False`. This would be useful, for example, if you wish to use something like [argparse](https://docs.python.org/3/library/argparse.html) to parse the overall command line arguments for your application:
If you wish to disable cmd2's consumption of command-line arguments, you can do so by setting the `allow_cli_args` argument of your [cmd2.Cmd][] class instance to `False`.
This would be useful, for example, if you wish to use something like [argparse](https://docs.python.org/3/library/argparse.html) to parse the overall command line arguments
for your application:

```py
from cmd2 import Cmd
Expand All @@ -35,6 +37,17 @@ application and easily used in automation.
super().__init__(allow_cli_args=False)
```

!!! tip

If you want to use something like [argparse](https://docs.python.org/3/library/argparse.html) to parse the overall command line arguments for your application
but still want to be able to pass any extra arguments to your application as commands, then see the `if __name__ == "__main__":` block at the end of the
[argparse_example.py](https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_example.py) example.

This can be run like so:
```sh
uv run examples/argparse_example.py -c blue help quit
```

## Startup Script

You can execute commands from an initialization script by passing a file path to the
Expand Down
6 changes: 4 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ each:
- Comprehensive example demonstrating various aspects of using
[argparse](https://docs.python.org/3/library/argparse.html) for command argument processing
via the `cmd2.with_argparser` decorator
- Also demonstrates how to use a custom `argparse` parser when invoking your application and
still pass commands at invocation
- [async_call.py](https://github.com/python-cmd2/cmd2/blob/main/examples/async_call.py)
- Shows how to make a call to an async function from a cmd2 command.
- [async_commands.py](https://github.com/python-cmd2/cmd2/blob/main/examples/async_commands.py)
Expand All @@ -26,8 +28,8 @@ each:
title
- [basic_completion.py](https://github.com/python-cmd2/cmd2/blob/main/examples/basic_completion.py)
- Show how to enable custom tab completion by assigning a completer function to `do_*` commands
- [cmd2_as_argument.py](https://github.com/python-cmd2/cmd2/blob/main/examples/cmd_as_argument.py)
- Demonstrates how to accept and parse command-line arguments when invoking a cmd2 application
- [cmd_as_argument.py](https://github.com/python-cmd2/cmd2/blob/main/examples/cmd_as_argument.py)
- Demonstrates how to accept command-line arguments when invoking a cmd2 application
- [color.py](https://github.com/python-cmd2/cmd2/blob/main/examples/color.py)
- Show the numerous colors available to use in your cmd2 applications
- [command_sets.py](https://github.com/python-cmd2/cmd2/blob/main/examples/command_sets.py)
Expand Down
44 changes: 13 additions & 31 deletions examples/cmd_as_argument.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#!/usr/bin/env python
"""A sample application for cmd2.

This example has additional code in main() that shows how to accept a command from
the command line at invocation:
This example relies on the `allow_cli_args` init parameter being `True` by default which allows passing commands on the
command line to execute when the application is invoked.

$ python cmd_as_argument.py speak -p hello there
This can be run like so:
$ python cmd_as_argument.py "speak -p hello there" help

By default, the application will enter the interactive shell mode after executing the commands passed in on the command line.
You can have it exit after executing by providing `quit` as the last command.
Commands and arguments can be grouped together by including in quotes.
"""

import argparse
import secrets

import cmd2
Expand All @@ -24,7 +28,7 @@ def __init__(self) -> None:
shortcuts = dict(cmd2.DEFAULT_SHORTCUTS)
shortcuts.update({"&": "speak"})
# Set include_ipy to True to enable the "ipy" command which runs an interactive IPython shell
super().__init__(allow_cli_args=False, include_ipy=True, multiline_commands=["orate"], shortcuts=shortcuts)
super().__init__(allow_cli_args=True, include_ipy=True, multiline_commands=["orate"], shortcuts=shortcuts)

self.self_in_py = True
self.maxrepeats = 3
Expand All @@ -45,8 +49,8 @@ def do_speak(self, args) -> None:
"""Repeats what you tell me to."""
words = []
for w in args.words:
word = w.copy()
if args.piglatin:
word = w.strip()
if args.piglatin and word:
word = f"{word[1:]}{word[0]}ay"
if args.shout:
word = word.upper()
Expand Down Expand Up @@ -80,30 +84,8 @@ def do_mumble(self, args) -> None:
self.poutput(" ".join(output))


def main(argv=None):
"""Run when invoked from the operating system shell."""
parser = cmd2.Cmd2ArgumentParser(description="Commands as arguments")
command_help = "optional command to run, if no command given, enter an interactive shell"
parser.add_argument("command", nargs="?", help=command_help)
arg_help = "optional arguments for command"
parser.add_argument("command_args", nargs=argparse.REMAINDER, help=arg_help)

args = parser.parse_args(argv)

c = CmdLineApp()

sys_exit_code = 0
if args.command:
# we have a command, run it and then exit
c.onecmd_plus_hooks("{} {}".format(args.command, " ".join(args.command_args)))
else:
# we have no command, drop into interactive mode
sys_exit_code = c.cmdloop()

return sys_exit_code


if __name__ == "__main__":
import sys

sys.exit(main())
app = CmdLineApp()
sys.exit(app.cmdloop())
Loading