|
17 | 17 | 13) Right prompt which displays contextual information |
18 | 18 | 14) Background thread to update the content displayed by the bottom toolbar outside of the UI thread to keep things responsive |
19 | 19 | 15) Using preloop() and postloop() hooks to start and stop a background thread |
| 20 | +16) Using the with_annotated decorator to parse typed command arguments |
| 21 | +17) Using the with_argparser decorator to parse command arguments with a custom parser |
20 | 22 | """ |
21 | 23 |
|
| 24 | +import argparse |
22 | 25 | import datetime |
23 | 26 | import pathlib |
24 | 27 | import sys |
25 | 28 | import threading |
| 29 | +from typing import Annotated |
26 | 30 |
|
27 | 31 | from prompt_toolkit.application import get_app |
28 | 32 | from prompt_toolkit.formatted_text import AnyFormattedText |
|
33 | 37 | Color, |
34 | 38 | stylize, |
35 | 39 | ) |
| 40 | +from cmd2.annotated import Option |
36 | 41 |
|
37 | 42 |
|
38 | 43 | class BasicApp(cmd2.Cmd): |
@@ -156,18 +161,66 @@ def get_rprompt(self) -> AnyFormattedText: |
156 | 161 | text = f"cwd={current_working_directory}" |
157 | 162 | return [(style, text)] |
158 | 163 |
|
| 164 | + @cmd2.with_annotated |
| 165 | + def do_cat( |
| 166 | + self, |
| 167 | + path: pathlib.Path, # Required positional argument with type annotation, tab-completes filesystem paths automatically |
| 168 | + numbered: Annotated[ # Optional flag argument with type annotation, default value, and help text |
| 169 | + bool, Option("-n", "--number", help_text="prefix each line with its number") |
| 170 | + ] = False, |
| 171 | + ) -> None: |
| 172 | + """Print a file's contents. `path` tab-completes filesystem paths automatically. |
| 173 | +
|
| 174 | + Try: |
| 175 | + cat <TAB> # path completes files/dirs -- no completer wired |
| 176 | + cat notes.txt |
| 177 | + cat notes.txt -n # -n / --number, declared via Option metadata |
| 178 | + cat notes.txt --no-number |
| 179 | + """ |
| 180 | + text = path.read_text() |
| 181 | + lines = text.splitlines() |
| 182 | + if numbered: |
| 183 | + numbered_lines = [] |
| 184 | + for index, line in enumerate(lines, start=1): |
| 185 | + numbered_lines.append(f"{index}: {line}") |
| 186 | + self.ppaged("\n".join(numbered_lines)) |
| 187 | + else: |
| 188 | + # Just print the contents using a pager |
| 189 | + self.ppaged(path.read_text()) |
| 190 | + |
159 | 191 | def do_intro(self, _: cmd2.Statement) -> None: |
160 | | - """Display the intro banner.""" |
| 192 | + """Display the intro banner. |
| 193 | +
|
| 194 | + This command uses raw statement parsing. In general, we strongly recommend against this approach. But since this |
| 195 | + command effectively takes no arguments, it is safe to use raw statement parsing here. |
| 196 | +
|
| 197 | + The & key is also used as a shortcut for this command, so you can also type & to display the intro banner. |
| 198 | + """ |
161 | 199 | self.poutput(self.intro) |
162 | 200 |
|
163 | | - def do_echo(self, arg: cmd2.Statement) -> None: |
| 201 | + @staticmethod |
| 202 | + def _build_echo_parser() -> cmd2.Cmd2ArgumentParser: |
| 203 | + """Parser factory method for use with the echo command.""" |
| 204 | + echo_parser = cmd2.Cmd2ArgumentParser(description="Multiline command that echoes input.") |
| 205 | + echo_parser.add_argument("-u", "--upper", action="store_true", help="uppercase the output") |
| 206 | + echo_parser.add_argument("-r", "--repeat", type=int, default=1, help="output [n] times") |
| 207 | + echo_parser.add_argument("words", nargs="+", help="words to print") |
| 208 | + return echo_parser |
| 209 | + |
| 210 | + @cmd2.with_argparser(_build_echo_parser) |
| 211 | + def do_echo(self, args: argparse.Namespace) -> None: |
164 | 212 | """Multiline command.""" |
165 | | - self.poutput( |
166 | | - stylize( |
167 | | - arg, |
168 | | - style=Style(color=self.foreground_color), |
| 213 | + output_str = " ".join(args.words) |
| 214 | + if args.upper: |
| 215 | + output_str = output_str.upper() |
| 216 | + |
| 217 | + for _ in range(args.repeat): |
| 218 | + self.poutput( |
| 219 | + stylize( |
| 220 | + output_str, |
| 221 | + style=Style(color=self.foreground_color), |
| 222 | + ) |
169 | 223 | ) |
170 | | - ) |
171 | 224 |
|
172 | 225 |
|
173 | 226 | if __name__ == "__main__": |
|
0 commit comments