@@ -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+
1720If you don't want to type as we go, here is the complete source (you can click to expand and then
1821click 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-
4036class 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
4444if __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%s ay" % (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%s ay" % (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