diff --git a/.github/images/graph.drawio.png b/.github/images/graph.drawio.png deleted file mode 100644 index ecc33ce99..000000000 Binary files a/.github/images/graph.drawio.png and /dev/null differ diff --git a/README.md b/README.md index 780d41b2e..4ffacde85 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

cmd2 : immersive interactive command line applications

+

cmd2: build feature-rich command-line applications in Python

[![Latest Version](https://img.shields.io/pypi/v/cmd2.svg?style=flat-square&label=latest%20stable%20version)](https://pypi.python.org/pypi/cmd2/) [![Tests](https://github.com/python-cmd2/cmd2/actions/workflows/tests.yml/badge.svg)](https://github.com/python-cmd2/cmd2/actions/workflows/tests.yml) @@ -7,77 +7,145 @@ Chat

- Developer's Toolbox • - Philosophy • + Quick start • + Why cmd2?InstallationDocumentation • - Tutorials • - Hello World • - Projects using cmd2 • + Projects using cmd2

-[![Screenshot](https://raw.githubusercontent.com/python-cmd2/cmd2/main/cmd2.png)](https://youtu.be/DDU_JH6cFsA) +`cmd2` makes it quick to build powerful, polished command-line applications and REPLs in Python. +Start with a subclass of the standard library's +[`cmd.Cmd`](https://docs.python.org/3/library/cmd.html), then add commands while `cmd2` handles the +details that make an application pleasant to build and use: argument parsing, generated help, tab +completion, command history, scripting, output styling, and much more. -cmd2 is a tool for building interactive command line applications in Python. Its goal is to make it -quick and easy for developers to build feature-rich and user-friendly interactive command line -applications. It provides a simple API which is an extension of Python's built-in -[cmd](https://docs.python.org/3/library/cmd.html) module. cmd2 provides a wealth of features on top -of cmd to make your life easier and eliminates much of the boilerplate code which would be necessary -when using cmd. +You can use `cmd2` for a small internal tool and grow the same application into an extensible, +scriptable interface without replacing the framework or writing the usual CLI boilerplate. -> :warning: **If you are upgrading from an older version of `cmd2`, both `3.x` and `4.x` have some -> significant backwards incompatibilities from previous versions. Please see the -> [CHANGELOG](./CHANGELOG.md) for info on what has changed and the -> [Migration Guide](https://cmd2.readthedocs.io/en/latest/upgrades/) for tips on upgrading from an -> older version of `cmd2` to `4.x`** +## Quick start -## The developers toolbox +Install `cmd2`: -![system schema](https://raw.githubusercontent.com/python-cmd2/cmd2/main/.github/images/graph.drawio.png) +```bash +pip install cmd2 +``` -When creating solutions developers have no shortage of tools to create rich and smart user -interfaces. System administrators have long been duct taping together brittle workflows based on a -menagerie of simple command line tools created by strangers on github and the guy down the hall. -Unfortunately, when CLIs become significantly complex the ease of command discoverability tends to -fade quickly. On the other hand, Web and traditional desktop GUIs are first in class when it comes -to easily discovering functionality. The price we pay for beautifully colored displays is complexity -required to aggregate disparate applications into larger systems. `cmd2` fills the niche between -high [ease of command discovery](https://clig.dev/#ease-of-discovery) applications and smart -workflow automation systems. +Then create `app.py`: -The `cmd2` framework provides a great mixture of both worlds. Application designers can easily -create complex applications and rely on the cmd2 library to offer effortless user facing help and -extensive tab completion. When users become comfortable with functionality, cmd2 turns into a -feature rich library enabling a smooth transition to full automation. If designed with enough -forethought, a well implemented cmd2 application can serve as a boutique workflow tool. `cmd2` pulls -off this flexibility based on two pillars of philosophy: +```python +from typing import Annotated -- Tab Completion -- Automation Transition +import cmd2 +from cmd2.annotated import Argument, Option -## Philosophy - +class App(cmd2.Cmd): + """A small interactive application.""" -Deep extensive tab completion and help text generation based on the argparse library create the -first pillar of 'ease of command discovery'. The following is a list of features in this category. + @cmd2.with_annotated + def do_greet( + self, + name: Annotated[str, Argument(help_text="person to greet")], + count: Annotated[int, Option(help_text="number of greetings")] = 1, + ) -> None: + """Greet a person.""" + for _ in range(count): + self.poutput(f"Hello, {name}!") -- Great tab completion of commands, subcommands, file system paths, and shell commands. -- Custom tab completion for user designed commands via simple function overloading. -- Tab completion from `persistent_history_file` sources added with very little friction. -- Automatic tab completion of `argparse` flags and optional arguments. -- Path completion easily enabled. -- When all else fails, custom tab completion based on `choices_provider` can fill any gaps. - +if __name__ == "__main__": + App().cmdloop() +``` + +`cmd2` supports [Typer](https://typer.tiangolo.com/)-style syntax for specifying command arguments +with type annotations. In this example, `@cmd2.with_annotated` turns `name` and `count` into a +positional argument and an option. -cmd2 creates the second pillar of 'ease of transition to automation' through alias/macro creation, -command line argument parsing and execution of cmd2 scripting. +Run it with `python app.py`. Your new `greet` command already has input validation, generated help, +and tab completion for its options. The application also includes discoverable help, command +history, aliases, macros, scripting, shell integration, and other built-in commands. -- Flexible alias and macro creation for quick abstraction of commands. -- Text file scripting of your application with `run_script` (`@`) and `_relative_run_script` (`@@`) -- Powerful and flexible built-in Python scripting of your application using the `run_pyscript` - command +```console +(Cmd) help greet +Usage: greet [-h] [--count COUNT] name + +(Cmd) greet --count 2 Ada +Hello, Ada! +Hello, Ada! +``` + +[![Animated demonstration of the cmd2 quick-start application](https://raw.githubusercontent.com/python-cmd2/cmd2/main/docs/assets/cmd2-readme-demo.gif)](https://raw.githubusercontent.com/python-cmd2/cmd2/main/docs/assets/cmd2-readme-demo.gif) + +See the [getting started tutorial](https://cmd2.readthedocs.io/en/latest/examples/getting_started/) +to build a more complete application. + +## Why cmd2? + +### Build more with less code + +- Define commands as Python methods and use familiar + [`argparse`](https://cmd2.readthedocs.io/en/latest/features/argument_processing/) parsers for + arguments and subcommands. A single parser definition drives validation, help, and completion. +- Render Rich tables and other styled objects, with consistent helpers for + [normal output, errors, warnings, and paging](https://cmd2.readthedocs.io/en/latest/features/generating_output/). +- Organize large applications into independently testable and dynamically loadable + [CommandSets](https://cmd2.readthedocs.io/en/latest/features/modular_commands/), and customize the + command lifecycle with [hooks](https://cmd2.readthedocs.io/en/latest/features/hooks/). +- Integrate existing Python code, shell tools, or asynchronous work instead of restructuring your + application around the framework. +- Run on Windows, macOS, and Linux with a pure-Python package and a small dependency footprint. + +### Give users a capable interface from day one + +- **Tab completion:** provide context-aware completion for commands, subcommands, options, choices, + file paths, and custom data sources, with descriptive hints for completion candidates. Learn more + about [completion](https://cmd2.readthedocs.io/en/latest/features/completion/). +- **History:** search, edit, rerun, save, and optionally persist previously entered commands. Users + also get familiar navigation and reverse search such as Ctrl+R. Learn more + about [history](https://cmd2.readthedocs.io/en/latest/features/history/). +- **Unicode:** accept Unicode in commands, arguments, file names, and output, and run UTF-8 command + scripts for applications used in any language. Learn more about + [command scripts](https://cmd2.readthedocs.io/en/latest/features/scripting/#command-scripts). +- **Shell shortcuts and hotkeys:** use familiar Readline-style keyboard shortcuts for navigating and + editing the command line, including Emacs-style bindings provided by `prompt-toolkit`. Learn more + about [keyboard shortcuts](https://cmd2.readthedocs.io/en/latest/features/history/#for-users). +- **Help:** generate discoverable, categorized help for commands, subcommands, and arguments + directly from their argument parsers. Learn more about + [help](https://cmd2.readthedocs.io/en/latest/features/help/). +- **Rich UI/UX:** offer syntax highlighting, Fish-style history suggestions, multiline input, + configurable prompts, completion menus, themes, and an optional bottom toolbar. Learn more about + [prompts and toolbars](https://cmd2.readthedocs.io/en/latest/features/prompt/) and + [themes](https://cmd2.readthedocs.io/en/latest/features/theme/). + +### Grow from exploration to automation + +- **Shell scripting:** automate the same commands users run interactively by passing them as command + arguments or standard input from shell scripts. Learn more about + [automating cmd2 applications](https://cmd2.readthedocs.io/en/latest/features/os/#automating-cmd2-apps-from-other-cliclu-tools). +- **Python scripting:** run Python scripts inside the application for loops, branching, complex + control flow, and integration with the application's commands and data. Learn more about + [Python scripts](https://cmd2.readthedocs.io/en/latest/features/scripting/#python-scripts). +- **Run shell commands:** execute operating-system commands without leaving the application, using + the built-in `shell` command or its `!` shortcut. Learn more about + [OS integration](https://cmd2.readthedocs.io/en/latest/features/os/#executing-os-commands-from-within-cmd2). +- **Redirect output:** send command output to files or the clipboard, or pipe it through one or more + shell commands. Learn more about + [output redirection and pipes](https://cmd2.readthedocs.io/en/latest/features/redirection/). +- **Aliases, macros, and shortcuts:** let users customize names, parameterized commands, and terse + shortcuts for repetitive workflows without changing the application. Learn more about + [aliases, macros, and shortcuts](https://cmd2.readthedocs.io/en/latest/features/shortcuts_aliases_macros/). +- **Startup scripts:** initialize an application consistently by running saved commands every time + it starts. Learn more about + [startup commands and scripts](https://cmd2.readthedocs.io/en/latest/features/startup_commands/). +- **Embedded Python and IPython shells:** drop into an interactive Python or IPython session for + experimentation, debugging, object introspection, and access to application state. Learn more + about + [embedded Python shells](https://cmd2.readthedocs.io/en/latest/features/embedded_python_shells/). +- **Color, style, and tables:** produce readable output with Rich colors and styles, custom themes, + paging, and flexible table layouts. Learn more about + [generating output](https://cmd2.readthedocs.io/en/latest/features/generating_output/) and + [creating tables](https://cmd2.readthedocs.io/en/latest/features/table_creation/). ## Installation @@ -94,14 +162,16 @@ For information on other installation options, see [Installation Instructions](https://cmd2.readthedocs.io/en/latest/overview/installation.html) in the cmd2 documentation. -## Documentation - -The latest documentation for cmd2 can be read online here: https://cmd2.readthedocs.io/en/latest/ +> [!IMPORTANT] Upgrading from an older release? Versions 3.x and 4.x include significant +> backwards-incompatible changes. Review the [changelog](./CHANGELOG.md) and +> [migration guide](https://cmd2.readthedocs.io/en/latest/upgrades/) before upgrading. -It is available in HTML, PDF, and ePub formats. +## Documentation -The best way to learn the cmd2 api is to delve into the example applications located in source under -examples. +Read the [latest documentation](https://cmd2.readthedocs.io/en/latest/) online or download it in +HTML, PDF, and ePub formats. The +[`examples`](https://github.com/python-cmd2/cmd2/tree/main/examples) directory contains focused, +runnable demonstrations of individual features. ## Tutorials @@ -116,29 +186,6 @@ examples. - Advanced cookiecutter template with external plugin support : https://github.com/jayrod/cookiecutter-python-cmd2-ext-plug -## Hello World - -```python -#!/usr/bin/env python -"""A simple cmd2 application.""" - -import cmd2 - - -class FirstApp(cmd2.Cmd): - """A simple cmd2 application.""" - - def do_hello_world(self, _: cmd2.Statement): - self.poutput("Hello World") - - -if __name__ == "__main__": - import sys - - c = FirstApp() - sys.exit(c.cmdloop()) -``` - ## Found a bug? If you think you've found a bug, please first read through the open diff --git a/cmd2.png b/cmd2.png deleted file mode 100644 index 31a6e921f..000000000 Binary files a/cmd2.png and /dev/null differ diff --git a/docs/assets/cmd2-readme-demo.gif b/docs/assets/cmd2-readme-demo.gif new file mode 100644 index 000000000..74e9d3ef4 Binary files /dev/null and b/docs/assets/cmd2-readme-demo.gif differ diff --git a/docs/assets/getting-started-demo.gif b/docs/assets/getting-started-demo.gif new file mode 100644 index 000000000..6a6b71171 Binary files /dev/null and b/docs/assets/getting-started-demo.gif differ diff --git a/docs/examples/getting_started.md b/docs/examples/getting_started.md index 3c4c14163..413648665 100644 --- a/docs/examples/getting_started.md +++ b/docs/examples/getting_started.md @@ -10,10 +10,13 @@ example application which demonstrates many features of `cmd2`: - [Generating Output](../features/generating_output.md) - [Help](../features/help.md) - [Shortcuts](../features/shortcuts_aliases_macros.md#shortcuts) -- [Multiline Commands](../features/multiline_commands.md) - [History](../features/history.md) - [Bottom Toolbar](../features/prompt.md#bottom-toolbar) +The following animation shows the `cat`, `echo`, and `intro` commands in action: + +![Animated demonstration of the getting started application](../assets/getting-started-demo.gif) + If you don't want to type as we go, here is the complete source (you can click to expand and then click the **Copy** button in the top-right): @@ -27,146 +30,182 @@ click the **Copy** button in the top-right): ## Basic Application -First we need to create a new `cmd2` application. Create a new file `getting_started.py` with the -following contents: +The example defines `BasicApp` as a subclass of [cmd2.Cmd][]: ```py -#!/usr/bin/env python -"""A basic cmd2 application.""" - -import cmd2 - - class BasicApp(cmd2.Cmd): """Cmd2 application to demonstrate many common features.""" +``` +At the end of the file, the application creates an instance of that class and passes control to the +[cmd2.Cmd.cmdloop][] method: +```py if __name__ == "__main__": - import sys - app = BasicApp() sys.exit(app.cmdloop()) ``` -We have a new class `BasicApp` which is a subclass of [cmd2.Cmd][]. When we tell Python to run our -file like this: +Run the example from the repository root: ```shell -$ python getting_started.py +$ uv run python examples/getting_started.py ``` -The application creates an instance of our class, and calls the [cmd2.Cmd.cmdloop][] method. This -method accepts user input and runs commands based on that input. Because we subclassed `cmd2.Cmd`, -our new app already has a bunch of built-in features. - -Congratulations, you have a working `cmd2` app. You can run it, and then type `quit` to exit. +The application displays its intro banner and the custom `myapp>` prompt. Because `BasicApp` +subclasses `cmd2.Cmd`, it also includes `cmd2`'s built-in commands and features. Type `quit` to +exit. -## Create a New Setting +## Create a Setting -Before we create our first command, we are going to add a new setting to this app. `cmd2` includes -robust support for [Settings](../features/settings.md). You configure settings during object -initialization, so we need to add an initializer to our class: +`cmd2` includes robust support for [Settings](../features/settings.md). The example stores the color +used by the `echo` command in `foreground_color`, then exposes that attribute as a runtime setting. +The choices are the color values supported by [cmd2.Color][]: ```py -def __init__(self): - super().__init__() - - # Make maxrepeats settable at runtime - self.maxrepeats = 3 - self.add_settable(cmd2.Settable("maxrepeats", int, "max repetitions for speak command", self)) +# Color to output text in with echo command +self.foreground_color = Color.CYAN.value + +# Make echo_fg settable at runtime +fg_colors = [c.value for c in Color] +self.add_settable( + cmd2.Settable( + "foreground_color", + str, + Text.assemble( + "Foreground color to use with echo command ", + "(Options: ", + Text("Green", Style(color=Color.GREEN)), + ", ", + Text("Red", Style(color=Color.RED)), + ", ", + Text("Blue", Style(color=Color.BLUE)), + ", ...)", + ), + self, + choices=fg_colors, + ) +) ``` -In that initializer, the first thing to do is to make sure we initialize `cmd2`. That's what the -`super().__init__()` line does. Next create an attribute to hold the setting. Finally, call the -[cmd2.Cmd.add_settable][] method with a new instance of a [cmd2.utils.Settable][] class. Now if you -run the script, and enter the `set` command to see the settings, like this: +The [cmd2.Cmd.add_settable][] method registers a [cmd2.utils.Settable][] that validates new values +against `fg_colors`. Use the built-in `set` command to inspect or change it: ```shell -$ python getting_started.py -(Cmd) set +myapp> set foreground_color +myapp> set foreground_color red ``` -you will see our `maxrepeats` setting show up with its default value of `3`. +The first command displays the current value. The second changes the color used by subsequent `echo` +output. + +## Commands + +Methods whose names start with `do_` become commands. `BasicApp` defines three commands: `cat`, +`echo`, and `intro`. Each one demonstrates a different way to process arguments. -## Create A Command +### cat -Now we will create our first command, called `speak`, which will echo back whatever we tell it to -say. We are going to use an [argument processor](../features/argument_processing.md) so the `speak` -command can shout and talk Pig Latin. We will also use some built in methods for -[generating output](../features/generating_output.md). Add this code to `getting_started.py`, so -that the `speak_parser` attribute and the `do_speak()` method are part of the `BasicApp()` class: +The `cat` command uses [cmd2.with_annotated][] to build its argument parser from type annotations. +The `pathlib.Path` annotation enables path completion, and [cmd2.annotated.Option][] defines the +optional `-n`/`--number` flag: ```py -speak_parser = cmd2.Cmd2ArgumentParser() -speak_parser.add_argument("-p", "--piglatin", action="store_true", help="atinLay") -speak_parser.add_argument("-s", "--shout", action="store_true", help="N00B EMULATION MODE") -speak_parser.add_argument("-r", "--repeat", type=int, help="output [n] times") -speak_parser.add_argument("words", nargs="+", help="words to say") - - -@cmd2.with_argparser(speak_parser) -def do_speak(self, args): - """Repeats what you tell me to.""" - words = [] - for word in args.words: - if args.piglatin: - word = "%s%say" % (word[1:], word[0]) - if args.shout: - word = word.upper() - words.append(word) - repetitions = args.repeat or 1 - for _ in range(min(repetitions, self.maxrepeats)): - # .poutput handles newlines, and accommodates output redirection too - self.poutput(" ".join(words)) +@cmd2.with_annotated +def do_cat( + self, + path: pathlib.Path, # Required positional argument with type annotation, tab-completes filesystem paths automatically + numbered: Annotated[ # Optional flag argument with type annotation, default value, and help text + bool, Option("-n", "--number", help_text="prefix each line with its number") + ] = False, +) -> None: + """Print a file's contents. `path` tab-completes filesystem paths automatically. + + Try: + cat # path completes files/dirs -- no completer wired + cat notes.txt + cat notes.txt -n # -n / --number, declared via Option metadata + cat notes.txt --no-number + """ + text = path.read_text() + lines = text.splitlines() + if numbered: + numbered_lines = [] + for index, line in enumerate(lines, start=1): + numbered_lines.append(f"{index}: {line}") + self.ppaged("\n".join(numbered_lines)) + else: + # Just print the contents using a pager + self.ppaged(path.read_text()) ``` -Up at the top of the script, you'll also need to add: +The command uses [cmd2.Cmd.ppaged][] so longer files can be viewed in a pager. Try it on the startup +script included with the example: -```py -import argparse +```shell +myapp> cat examples/.cmd2rc --number ``` -There's a bit to unpack here, so let's walk through it. We created `speak_parser`, which uses the -[argparse](https://docs.python.org/3/library/argparse.html) module from the Python standard library -to parse command line input from a user. So far, there is nothing specific to `cmd2`. +### echo -There is also a new method called `do_speak()`. In both -[cmd](https://docs.python.org/3/library/cmd.html) and `cmd2`, methods that start with `do_` become -new commands, so by defining this method we have created a command called `speak`. +The `echo` command demonstrates [cmd2.with_argparser][]. Its parser factory defines options for +uppercasing and repeating the output, plus one or more words to print: -Note the `cmd2.decorators.with_argparser` decorator on the `do_speak()` method. This decorator does -3 useful things for us: +```py +@staticmethod +def _build_echo_parser() -> cmd2.Cmd2ArgumentParser: + """Parser factory method for use with the echo command.""" + echo_parser = cmd2.Cmd2ArgumentParser(description="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: + """Command using with_argparser decorator for parsing arguments.""" + 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), + ) + ) +``` -1. It tells `cmd2` to process all input for the `speak` command using the argparser we defined. If - the user input doesn't meet the requirements defined by the argparser, then an error will be - displayed for the user. -1. It alters our `do_speak` method so that instead of receiving the raw user input as a parameter, - we receive the namespace from the argument parser. -1. It creates a help message for us based on the argparser. +The decorator parses the command line and passes an `argparse.Namespace` to `do_echo()`. It also +generates command help from the parser. The method styles the text with the configured foreground +color and writes it with [cmd2.Cmd.poutput][], which supports `cmd2` output redirection: -You can see in the body of the method how we use the namespace from the argparser (passed in as the -variable `args`). We build a list of words which we will output, honoring both the `--piglatin` and -`--shout` options. +```shell +myapp> echo --upper --repeat 2 hello cmd2 +HELLO CMD2 +HELLO CMD2 +myapp> help echo +``` -At the end of the method, we use our `maxrepeats` setting as an upper limit to the number of times -we will print the output. +### intro -The last thing you'll notice is that we used the `self.poutput()` method to display our output. -`poutput()` is a method provided by `cmd2`, which I strongly recommend you use any time you want to -[generate output](../features/generating_output.md). It provides the following benefits: +The `intro` command takes no arguments, so it demonstrates the raw [cmd2.Statement][] interface: -1. Allows the user to redirect output to a text file or pipe it to a shell process -1. Gracefully handles `BrokenPipeError` exceptions for redirected output -1. Honors the setting to [strip embedded ANSI sequences](../features/settings.md#allow_style) - (typically used for background and foreground colors) +```py +def do_intro(self, _: cmd2.Statement) -> None: + """Display the intro banner. + + This command uses raw statement parsing. In general, we strongly recommend against this approach. But since this + command effectively takes no arguments, it is safe to use raw statement parsing here. -Go run the script again, and try out the `speak` command. Try typing `help speak`, and you will see -a lovely usage message describing the various options for the command. + The & key is also used as a shortcut for this command, so you can also type & to display the intro banner. + """ + self.poutput(self.intro) +``` -With those few lines of code, we created a [command](../features/commands.md), used an -[Argument Processor](../features/argument_processing.md), added a nice -[help message](../features/help.md) for our users, and -[generated some output](../features/generating_output.md). +Typing `intro` displays the same banner that the application shows at startup. ## Shortcuts @@ -186,83 +225,24 @@ you can type this: (Cmd) !ls -al ``` -Let's add a shortcut for our `speak` command. Change the `__init__()` method so it looks like this: +The example adds `&` as a shortcut for the `intro` command: ```py -def __init__(self): - shortcuts = cmd2.DEFAULT_SHORTCUTS - shortcuts.update({"&": "speak"}) - super().__init__(shortcuts=shortcuts) - - # Make maxrepeats settable at runtime - self.maxrepeats = 3 - self.add_settable(cmd2.Settable("maxrepeats", int, "max repetitions for speak command", self)) +shortcuts = cmd2.DEFAULT_SHORTCUTS +shortcuts.update({"&": "intro"}) ``` -Shortcuts are passed to the `cmd2` initializer, and if you want the built-in shortcuts of `cmd2` you -have to pass them. These shortcuts are defined as a dictionary, with the key being the shortcut, and -the value containing the command. When using the default shortcuts and adding your own, it's a good -idea to use the `.update()` method to modify the dictionary. This way, if you add a shortcut that -happens to already be in the default set, yours will override, and you won't get any errors at -runtime. +The `shortcuts` dictionary is then passed to the `cmd2.Cmd` initializer with the rest of the +application configuration. Starting with [cmd2.DEFAULT_SHORTCUTS][] retains the built-in shortcuts; +calling `.update()` adds the new shortcut or overrides an existing one with the same key. -Run your app again, and type: +Use the built-in `shortcuts` command to list them, or type `&` to invoke `intro`: ```shell -(Cmd) shortcuts -``` - -to see the list of all the shortcuts, including the one for speak that we just created. - -## Multiline Commands - -Some use cases benefit from commands that span more than one line. For example, you might want the -ability for your user to type in a SQL command, which can often span lines and which are terminated -with a semicolon. Let's add a [multiline command](../features/multiline_commands.md) to our -application. First we'll create a new command called `orate`. This code shows both the definition of -our `speak` command, and the `orate` command: - -```py -@cmd2.with_argparser(speak_parser) -def do_speak(self, args): - """Repeats what you tell me to.""" - words = [] - for word in args.words: - if args.piglatin: - word = "%s%say" % (word[1:], word[0]) - if args.shout: - word = word.upper() - words.append(word) - repetitions = args.repeat or 1 - for _ in range(min(repetitions, self.maxrepeats)): - # .poutput handles newlines, and accommodates output redirection too - self.poutput(" ".join(words)) - - -# orate is a synonym for speak which takes multiline input -do_orate = do_speak +myapp> shortcuts +myapp> & ``` -With the new command created, we need to tell `cmd2` to treat that command as a multi-line command. -Modify the super initialization line to look like this: - -```py -super().__init__(multiline_commands=["orate"], shortcuts=shortcuts) -``` - -Now when you run the example, you can type something like this: - -```text -(Cmd) orate O for a Muse of fire, that would ascend -> The brightest heaven of invention, -> A kingdom for a stage, princes to act -> And monarchs to behold the swelling scene! ; -``` - -Notice the prompt changes to indicate that input is still ongoing. `cmd2` will continue prompting -for input until it sees an unquoted semicolon (the default multi-line command termination -character). - ## History `cmd2` tracks the history of the commands that users enter. As a developer, you don't need to do