Skip to content

Commit 5d50825

Browse files
committed
Updated getting_started.md to more closely match the current state of the getting_started.py example
Also added a simple animated gif to that page.
1 parent 351b912 commit 5d50825

2 files changed

Lines changed: 146 additions & 166 deletions

File tree

62.6 KB
Loading

‎docs/examples/getting_started.md‎

Lines changed: 146 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ example application which demonstrates many features of `cmd2`:
1010
- [Generating Output](../features/generating_output.md)
1111
- [Help](../features/help.md)
1212
- [Shortcuts](../features/shortcuts_aliases_macros.md#shortcuts)
13-
- [Multiline Commands](../features/multiline_commands.md)
1413
- [History](../features/history.md)
1514
- [Bottom Toolbar](../features/prompt.md#bottom-toolbar)
1615

16+
The following animation shows the `cat`, `echo`, and `intro` commands in action:
17+
18+
![Animated demonstration of the getting started application](../assets/getting-started-demo.gif)
19+
1720
If you don't want to type as we go, here is the complete source (you can click to expand and then
1821
click the **Copy** button in the top-right):
1922

@@ -27,146 +30,182 @@ click the **Copy** button in the top-right):
2730

2831
## Basic Application
2932

30-
First we need to create a new `cmd2` application. Create a new file `getting_started.py` with the
31-
following contents:
33+
The example defines `BasicApp` as a subclass of [cmd2.Cmd][]:
3234

3335
```py
34-
#!/usr/bin/env python
35-
"""A basic cmd2 application."""
36-
37-
import cmd2
38-
39-
4036
class BasicApp(cmd2.Cmd):
4137
"""Cmd2 application to demonstrate many common features."""
38+
```
4239

40+
At the end of the file, the application creates an instance of that class and passes control to the
41+
[cmd2.Cmd.cmdloop][] method:
4342

43+
```py
4444
if __name__ == "__main__":
45-
import sys
46-
4745
app = BasicApp()
4846
sys.exit(app.cmdloop())
4947
```
5048

51-
We have a new class `BasicApp` which is a subclass of [cmd2.Cmd][]. When we tell Python to run our
52-
file like this:
49+
Run the example from the repository root:
5350

5451
```shell
55-
$ python getting_started.py
52+
$ uv run python examples/getting_started.py
5653
```
5754

58-
The application creates an instance of our class, and calls the [cmd2.Cmd.cmdloop][] method. This
59-
method accepts user input and runs commands based on that input. Because we subclassed `cmd2.Cmd`,
60-
our new app already has a bunch of built-in features.
61-
62-
Congratulations, you have a working `cmd2` app. You can run it, and then type `quit` to exit.
55+
The application displays its intro banner and the custom `myapp>` prompt. Because `BasicApp`
56+
subclasses `cmd2.Cmd`, it also includes `cmd2`'s built-in commands and features. Type `quit` to
57+
exit.
6358

64-
## Create a New Setting
59+
## Create a Setting
6560

66-
Before we create our first command, we are going to add a new setting to this app. `cmd2` includes
67-
robust support for [Settings](../features/settings.md). You configure settings during object
68-
initialization, so we need to add an initializer to our class:
61+
`cmd2` includes robust support for [Settings](../features/settings.md). The example stores the color
62+
used by the `echo` command in `foreground_color`, then exposes that attribute as a runtime setting.
63+
The choices are the color values supported by [cmd2.Color][]:
6964

7065
```py
71-
def __init__(self):
72-
super().__init__()
73-
74-
# Make maxrepeats settable at runtime
75-
self.maxrepeats = 3
76-
self.add_settable(cmd2.Settable("maxrepeats", int, "max repetitions for speak command", self))
66+
# Color to output text in with echo command
67+
self.foreground_color = Color.CYAN.value
68+
69+
# Make echo_fg settable at runtime
70+
fg_colors = [c.value for c in Color]
71+
self.add_settable(
72+
cmd2.Settable(
73+
"foreground_color",
74+
str,
75+
Text.assemble(
76+
"Foreground color to use with echo command ",
77+
"(Options: ",
78+
Text("Green", Style(color=Color.GREEN)),
79+
", ",
80+
Text("Red", Style(color=Color.RED)),
81+
", ",
82+
Text("Blue", Style(color=Color.BLUE)),
83+
", ...)",
84+
),
85+
self,
86+
choices=fg_colors,
87+
)
88+
)
7789
```
7890

79-
In that initializer, the first thing to do is to make sure we initialize `cmd2`. That's what the
80-
`super().__init__()` line does. Next create an attribute to hold the setting. Finally, call the
81-
[cmd2.Cmd.add_settable][] method with a new instance of a [cmd2.utils.Settable][] class. Now if you
82-
run the script, and enter the `set` command to see the settings, like this:
91+
The [cmd2.Cmd.add_settable][] method registers a [cmd2.utils.Settable][] that validates new values
92+
against `fg_colors`. Use the built-in `set` command to inspect or change it:
8393

8494
```shell
85-
$ python getting_started.py
86-
(Cmd) set
95+
myapp> set foreground_color
96+
myapp> set foreground_color Red
8797
```
8898

89-
you will see our `maxrepeats` setting show up with its default value of `3`.
99+
The first command displays the current value. The second changes the color used by subsequent `echo`
100+
output.
101+
102+
## Commands
103+
104+
Methods whose names start with `do_` become commands. `BasicApp` defines three commands: `cat`,
105+
`echo`, and `intro`. Each one demonstrates a different way to process arguments.
90106

91-
## Create A Command
107+
### cat
92108

93-
Now we will create our first command, called `speak`, which will echo back whatever we tell it to
94-
say. We are going to use an [argument processor](../features/argument_processing.md) so the `speak`
95-
command can shout and talk Pig Latin. We will also use some built in methods for
96-
[generating output](../features/generating_output.md). Add this code to `getting_started.py`, so
97-
that the `speak_parser` attribute and the `do_speak()` method are part of the `BasicApp()` class:
109+
The `cat` command uses [cmd2.with_annotated][] to build its argument parser from type annotations.
110+
The `pathlib.Path` annotation enables path completion, and [cmd2.annotated.Option][] defines the
111+
optional `-n`/`--number` flag:
98112

99113
```py
100-
speak_parser = cmd2.Cmd2ArgumentParser()
101-
speak_parser.add_argument("-p", "--piglatin", action="store_true", help="atinLay")
102-
speak_parser.add_argument("-s", "--shout", action="store_true", help="N00B EMULATION MODE")
103-
speak_parser.add_argument("-r", "--repeat", type=int, help="output [n] times")
104-
speak_parser.add_argument("words", nargs="+", help="words to say")
105-
106-
107-
@cmd2.with_argparser(speak_parser)
108-
def do_speak(self, args):
109-
"""Repeats what you tell me to."""
110-
words = []
111-
for word in args.words:
112-
if args.piglatin:
113-
word = "%s%say" % (word[1:], word[0])
114-
if args.shout:
115-
word = word.upper()
116-
words.append(word)
117-
repetitions = args.repeat or 1
118-
for _ in range(min(repetitions, self.maxrepeats)):
119-
# .poutput handles newlines, and accommodates output redirection too
120-
self.poutput(" ".join(words))
114+
@cmd2.with_annotated
115+
def do_cat(
116+
self,
117+
path: pathlib.Path, # Required positional argument with type annotation, tab-completes filesystem paths automatically
118+
numbered: Annotated[ # Optional flag argument with type annotation, default value, and help text
119+
bool, Option("-n", "--number", help_text="prefix each line with its number")
120+
] = False,
121+
) -> None:
122+
"""Print a file's contents. `path` tab-completes filesystem paths automatically.
123+
124+
Try:
125+
cat <TAB> # path completes files/dirs -- no completer wired
126+
cat notes.txt
127+
cat notes.txt -n # -n / --number, declared via Option metadata
128+
cat notes.txt --no-number
129+
"""
130+
text = path.read_text()
131+
lines = text.splitlines()
132+
if numbered:
133+
numbered_lines = []
134+
for index, line in enumerate(lines, start=1):
135+
numbered_lines.append(f"{index}: {line}")
136+
self.ppaged("\n".join(numbered_lines))
137+
else:
138+
# Just print the contents using a pager
139+
self.ppaged(path.read_text())
121140
```
122141

123-
Up at the top of the script, you'll also need to add:
142+
The command uses [cmd2.Cmd.ppaged][] so longer files can be viewed in a pager. Try it on the startup
143+
script included with the example:
124144

125-
```py
126-
import argparse
145+
```shell
146+
myapp> cat examples/.cmd2rc --number
127147
```
128148

129-
There's a bit to unpack here, so let's walk through it. We created `speak_parser`, which uses the
130-
[argparse](https://docs.python.org/3/library/argparse.html) module from the Python standard library
131-
to parse command line input from a user. So far, there is nothing specific to `cmd2`.
149+
### echo
132150

133-
There is also a new method called `do_speak()`. In both
134-
[cmd](https://docs.python.org/3/library/cmd.html) and `cmd2`, methods that start with `do_` become
135-
new commands, so by defining this method we have created a command called `speak`.
151+
The `echo` command demonstrates [cmd2.with_argparser][]. Its parser factory defines options for
152+
uppercasing and repeating the output, plus one or more words to print:
136153

137-
Note the `cmd2.decorators.with_argparser` decorator on the `do_speak()` method. This decorator does
138-
3 useful things for us:
154+
```py
155+
@staticmethod
156+
def _build_echo_parser() -> cmd2.Cmd2ArgumentParser:
157+
"""Parser factory method for use with the echo command."""
158+
echo_parser = cmd2.Cmd2ArgumentParser(description="Command that echoes input.")
159+
echo_parser.add_argument("-u", "--upper", action="store_true", help="uppercase the output")
160+
echo_parser.add_argument("-r", "--repeat", type=int, default=1, help="output [n] times")
161+
echo_parser.add_argument("words", nargs="+", help="words to print")
162+
return echo_parser
163+
164+
165+
@cmd2.with_argparser(_build_echo_parser)
166+
def do_echo(self, args: argparse.Namespace) -> None:
167+
"""Command using with_argparser decorator for parsing arguments."""
168+
output_str = " ".join(args.words)
169+
if args.upper:
170+
output_str = output_str.upper()
171+
172+
for _ in range(args.repeat):
173+
self.poutput(
174+
stylize(
175+
output_str,
176+
style=Style(color=self.foreground_color),
177+
)
178+
)
179+
```
139180

140-
1. It tells `cmd2` to process all input for the `speak` command using the argparser we defined. If
141-
the user input doesn't meet the requirements defined by the argparser, then an error will be
142-
displayed for the user.
143-
1. It alters our `do_speak` method so that instead of receiving the raw user input as a parameter,
144-
we receive the namespace from the argument parser.
145-
1. It creates a help message for us based on the argparser.
181+
The decorator parses the command line and passes an `argparse.Namespace` to `do_echo()`. It also
182+
generates command help from the parser. The method styles the text with the configured foreground
183+
color and writes it with [cmd2.Cmd.poutput][], which supports `cmd2` output redirection:
146184

147-
You can see in the body of the method how we use the namespace from the argparser (passed in as the
148-
variable `args`). We build a list of words which we will output, honoring both the `--piglatin` and
149-
`--shout` options.
185+
```shell
186+
myapp> echo --upper --repeat 2 hello cmd2
187+
HELLO CMD2
188+
HELLO CMD2
189+
myapp> help echo
190+
```
150191

151-
At the end of the method, we use our `maxrepeats` setting as an upper limit to the number of times
152-
we will print the output.
192+
### intro
153193

154-
The last thing you'll notice is that we used the `self.poutput()` method to display our output.
155-
`poutput()` is a method provided by `cmd2`, which I strongly recommend you use any time you want to
156-
[generate output](../features/generating_output.md). It provides the following benefits:
194+
The `intro` command takes no arguments, so it demonstrates the raw [cmd2.Statement][] interface:
157195

158-
1. Allows the user to redirect output to a text file or pipe it to a shell process
159-
1. Gracefully handles `BrokenPipeError` exceptions for redirected output
160-
1. Honors the setting to [strip embedded ANSI sequences](../features/settings.md#allow_style)
161-
(typically used for background and foreground colors)
196+
```py
197+
def do_intro(self, _: cmd2.Statement) -> None:
198+
"""Display the intro banner.
199+
200+
This command uses raw statement parsing. In general, we strongly recommend against this approach. But since this
201+
command effectively takes no arguments, it is safe to use raw statement parsing here.
162202
163-
Go run the script again, and try out the `speak` command. Try typing `help speak`, and you will see
164-
a lovely usage message describing the various options for the command.
203+
The & key is also used as a shortcut for this command, so you can also type & to display the intro banner.
204+
"""
205+
self.poutput(self.intro)
206+
```
165207

166-
With those few lines of code, we created a [command](../features/commands.md), used an
167-
[Argument Processor](../features/argument_processing.md), added a nice
168-
[help message](../features/help.md) for our users, and
169-
[generated some output](../features/generating_output.md).
208+
Typing `intro` displays the same banner that the application shows at startup.
170209

171210
## Shortcuts
172211

@@ -186,83 +225,24 @@ you can type this:
186225
(Cmd) !ls -al
187226
```
188227

189-
Let's add a shortcut for our `speak` command. Change the `__init__()` method so it looks like this:
228+
The example adds `&` as a shortcut for the `intro` command:
190229

191230
```py
192-
def __init__(self):
193-
shortcuts = cmd2.DEFAULT_SHORTCUTS
194-
shortcuts.update({"&": "speak"})
195-
super().__init__(shortcuts=shortcuts)
196-
197-
# Make maxrepeats settable at runtime
198-
self.maxrepeats = 3
199-
self.add_settable(cmd2.Settable("maxrepeats", int, "max repetitions for speak command", self))
231+
shortcuts = cmd2.DEFAULT_SHORTCUTS
232+
shortcuts.update({"&": "intro"})
200233
```
201234

202-
Shortcuts are passed to the `cmd2` initializer, and if you want the built-in shortcuts of `cmd2` you
203-
have to pass them. These shortcuts are defined as a dictionary, with the key being the shortcut, and
204-
the value containing the command. When using the default shortcuts and adding your own, it's a good
205-
idea to use the `.update()` method to modify the dictionary. This way, if you add a shortcut that
206-
happens to already be in the default set, yours will override, and you won't get any errors at
207-
runtime.
235+
The `shortcuts` dictionary is then passed to the `cmd2.Cmd` initializer with the rest of the
236+
application configuration. Starting with [cmd2.DEFAULT_SHORTCUTS][] retains the built-in shortcuts;
237+
calling `.update()` adds the new shortcut or overrides an existing one with the same key.
208238

209-
Run your app again, and type:
239+
Use the built-in `shortcuts` command to list them, or type `&` to invoke `intro`:
210240

211241
```shell
212-
(Cmd) shortcuts
213-
```
214-
215-
to see the list of all the shortcuts, including the one for speak that we just created.
216-
217-
## Multiline Commands
218-
219-
Some use cases benefit from commands that span more than one line. For example, you might want the
220-
ability for your user to type in a SQL command, which can often span lines and which are terminated
221-
with a semicolon. Let's add a [multiline command](../features/multiline_commands.md) to our
222-
application. First we'll create a new command called `orate`. This code shows both the definition of
223-
our `speak` command, and the `orate` command:
224-
225-
```py
226-
@cmd2.with_argparser(speak_parser)
227-
def do_speak(self, args):
228-
"""Repeats what you tell me to."""
229-
words = []
230-
for word in args.words:
231-
if args.piglatin:
232-
word = "%s%say" % (word[1:], word[0])
233-
if args.shout:
234-
word = word.upper()
235-
words.append(word)
236-
repetitions = args.repeat or 1
237-
for _ in range(min(repetitions, self.maxrepeats)):
238-
# .poutput handles newlines, and accommodates output redirection too
239-
self.poutput(" ".join(words))
240-
241-
242-
# orate is a synonym for speak which takes multiline input
243-
do_orate = do_speak
242+
myapp> shortcuts
243+
myapp> &
244244
```
245245

246-
With the new command created, we need to tell `cmd2` to treat that command as a multi-line command.
247-
Modify the super initialization line to look like this:
248-
249-
```py
250-
super().__init__(multiline_commands=["orate"], shortcuts=shortcuts)
251-
```
252-
253-
Now when you run the example, you can type something like this:
254-
255-
```text
256-
(Cmd) orate O for a Muse of fire, that would ascend
257-
> The brightest heaven of invention,
258-
> A kingdom for a stage, princes to act
259-
> And monarchs to behold the swelling scene! ;
260-
```
261-
262-
Notice the prompt changes to indicate that input is still ongoing. `cmd2` will continue prompting
263-
for input until it sees an unquoted semicolon (the default multi-line command termination
264-
character).
265-
266246
## History
267247

268248
`cmd2` tracks the history of the commands that users enter. As a developer, you don't need to do

0 commit comments

Comments
 (0)