argshell.argshell
1import argparse 2import cmd 3import inspect 4import os 5import shlex 6import subprocess 7import sys 8import traceback 9from functools import wraps 10from typing import Any, Callable 11 12 13class Namespace(argparse.Namespace): 14 """Simple object for storing attributes. 15 16 Implements equality by attribute names and values, and provides a simple string representation. 17 """ 18 19 20class ArgShellParser(argparse.ArgumentParser): 21 """***Overrides exit, error, and parse_args methods*** 22 23 Object for parsing command line strings into Python objects. 24 25 Keyword Arguments: 26 - prog -- The name of the program (default: 27 ``os.path.basename(sys.argv[0])``) 28 - usage -- A usage message (default: auto-generated from arguments) 29 - description -- A description of what the program does 30 - epilog -- Text following the argument descriptions 31 - parents -- Parsers whose arguments should be copied into this one 32 - formatter_class -- HelpFormatter class for printing help messages 33 - prefix_chars -- Characters that prefix optional arguments 34 - fromfile_prefix_chars -- Characters that prefix files containing 35 additional arguments 36 - argument_default -- The default value for all arguments 37 - conflict_handler -- String indicating how to handle conflicts 38 - add_help -- Add a -h/-help option 39 - allow_abbrev -- Allow long options to be abbreviated unambiguously 40 - exit_on_error -- Determines whether or not ArgumentParser exits with 41 error info when an error occurs 42 """ 43 44 def exit(self, status=0, message=None): 45 """Override to prevent shell exit when passing -h/--help switches.""" 46 if message: 47 self._print_message(message, sys.stderr) 48 49 def error(self, message): 50 raise Exception(f"prog: {self.prog}, message: {message}") 51 52 def parse_args(self, *args, **kwargs) -> Namespace: 53 parsed_args: Namespace = super().parse_args(*args, **kwargs) 54 return parsed_args 55 56 57class ArgShell(cmd.Cmd): 58 """Subclass this to create custom ArgShells.""" 59 60 intro = "Entering argshell..." 61 prompt = "argshell>" 62 63 def do_quit(self, _: str) -> bool: 64 """Quit shell.""" 65 return True 66 67 def do_sys(self, command: str): 68 """Execute command with `os.system()`.""" 69 os.system(command) 70 71 def do_reload(self, _: str): 72 """Reload this shell.""" 73 args = [sys.executable, inspect.getsourcefile(type(self))] 74 subprocess.run(args) 75 sys.exit() 76 77 def do_help(self, arg): 78 """List available commands with "help" or detailed help with "help cmd". 79 If using 'help cmd' and the cmd is decorated with a parser, the parser help will also be printed. 80 """ 81 if arg: 82 # XXX check arg syntax 83 try: 84 func = getattr(self, "help_" + arg) 85 except AttributeError: 86 try: 87 func = getattr(self, "do_" + arg) 88 doc = func.__doc__ 89 if doc: 90 self.stdout.write("%s\n" % str(doc)) 91 # =========================Modification start========================= 92 # Check for decorator and call decorated function with "--help" 93 if hasattr(func, "__wrapped__"): 94 self.stdout.write( 95 f"Parser help for {func.__name__.replace('do_','')}:\n" 96 ) 97 func("--help") 98 if doc or hasattr(func, "__wrapped__"): 99 return 100 # =========================Modification stop========================= 101 except AttributeError: 102 pass 103 self.stdout.write("%s\n" % str(self.nohelp % (arg,))) 104 return 105 func() 106 else: 107 names = self.get_names() 108 cmds_doc = [] 109 cmds_undoc = [] 110 topics = set() 111 for name in names: 112 if name[:5] == "help_": 113 topics.add(name[5:]) 114 names.sort() 115 # There can be duplicates if routines overridden 116 prevname = "" 117 for name in names: 118 if name[:3] == "do_": 119 if name == prevname: 120 continue 121 prevname = name 122 cmd = name[3:] 123 if cmd in topics: 124 cmds_doc.append(cmd) 125 topics.remove(cmd) 126 elif getattr(self, name).__doc__: 127 cmds_doc.append(cmd) 128 else: 129 cmds_undoc.append(cmd) 130 self.stdout.write("%s\n" % str(self.doc_leader)) 131 self.print_topics(self.doc_header, cmds_doc, 15, 80) 132 self.print_topics(self.misc_header, sorted(topics), 15, 80) 133 self.print_topics(self.undoc_header, cmds_undoc, 15, 80) 134 135 def cmdloop(self, intro=None): 136 """Repeatedly issue a prompt, accept input, parse an initial prefix 137 off the received input, and dispatch to action methods, passing them 138 the remainder of the line as argument. 139 140 """ 141 142 self.preloop() 143 if self.use_rawinput and self.completekey: 144 try: 145 import readline 146 147 self.old_completer = readline.get_completer() # type: ignore 148 readline.set_completer(self.complete) # type: ignore 149 readline.parse_and_bind(self.completekey + ": complete") # type: ignore 150 except ImportError: 151 pass 152 try: 153 if intro is not None: 154 self.intro = intro 155 if self.intro: 156 self.stdout.write(str(self.intro) + "\n") 157 stop = None 158 while not stop: 159 if self.cmdqueue: 160 line = self.cmdqueue.pop(0) 161 else: 162 if self.use_rawinput: 163 try: 164 line = input(self.prompt) 165 except EOFError: 166 line = "EOF" 167 else: 168 self.stdout.write(self.prompt) 169 self.stdout.flush() 170 line = self.stdin.readline() 171 if not len(line): 172 line = "EOF" 173 else: 174 line = line.rstrip("\r\n") 175 # ===========Modification start=========== 176 try: 177 line = self.precmd(line) 178 stop = self.onecmd(line) 179 stop = self.postcmd(stop, line) 180 except Exception as e: 181 traceback.print_exc() 182 # ===========Modification stop=========== 183 self.postloop() 184 finally: 185 if self.use_rawinput and self.completekey: 186 try: 187 import readline 188 189 readline.set_completer(self.old_completer) # type: ignore 190 except ImportError: 191 pass 192 193 def emptyline(self): 194 ... 195 196 197def with_parser( 198 parser: Callable[..., ArgShellParser], 199 post_parsers: list[Callable[[Namespace], Namespace]] = [], 200) -> Callable[[Callable[[Any, Namespace], Any]], Callable[[Any, str], Any]]: 201 """Decorate a 'do_*' function in an argshell.ArgShell class with this function to pass an argshell.Namespace object to the decorated function instead of a string. 202 203 :param parser: A function that creates an argshell.ArgShellParser instance, adds arguments to it, and returns the parser. 204 205 :param post_parsers: An optional list of functions to execute where each function takes an argshell.Namespace instance and returns an argshell.Namespace instance. 206 'post_parser' functions are executed in the order they are supplied. 207 208 >>> def get_parser() -> argshell.ArgShellParser: 209 >>> parser = argshell.ArgShellParser() 210 >>> parser.add_argument("names", type=str, nargs="*", help="A list of first and last names to print.") 211 >>> parser.add_argument("-i", "--initials", action="store_true", help="Print the initials instead of the full name.") 212 >>> return parser 213 >>> 214 >>> # Convert list of first and last names to a list of tuples 215 >>> def names_list_to_tuples(args: argshell.Namespace) -> argshell.Namespace: 216 >>> args.names = [(first, last) for first, last in zip(args.names[::2], args.names[1::2])] 217 >>> if args.initials: 218 >>> args.names = [(name[0][0], name[1][0]) for name in args.names] 219 >>> return args 220 >>> 221 >>> def capitalize_names(args: argshell.Namespace) -> argshell.Namespace: 222 >>> args.names = [name.capitalize() for name in args.names] 223 >>> return args 224 >>> 225 >>> class NameShell(ArgShell): 226 >>> intro = "Entering nameshell..." 227 >>> prompt = "nameshell>" 228 >>> 229 >>> @with_parser(get_parser, [capitalize_names, names_list_to_tuples]) 230 >>> def do_printnames(self, args: argshell.Namespace): 231 >>> print(*[f"{name[0]} {name[1]}" for name in args.names], sep="\\n") 232 >>> 233 >>> NameShell().cmdloop() 234 >>> Entering nameshell... 235 >>> nameshell>printnames karl marx fred hampton emma goldman angela davis nestor makhno 236 >>> Karl Marx 237 >>> Fred Hampton 238 >>> Emma Goldman 239 >>> Angela Davis 240 >>> Nestor Makhno 241 >>> nameshell>printnames karl marx fred hampton emma goldman angela davis nestor makhno -i 242 >>> K M 243 >>> F H 244 >>> E G 245 >>> A D 246 >>> N M""" 247 248 def decorator( 249 func: Callable[[Any, Namespace], Any | None] 250 ) -> Callable[[Any, str], Any]: 251 @wraps(func) 252 def inner(self: Any, command: str) -> Any: 253 try: 254 args = parser().parse_args(shlex.split(command)) 255 except Exception as e: 256 # On parser error, print help and skip post_parser and func execution 257 if "the following arguments are required" not in str(e): 258 print(f"ERROR: {e}") 259 if "-h" not in command and "--help" not in command: 260 try: 261 args = parser().parse_args(["--help"]) 262 except Exception as e: 263 pass 264 return None 265 # Don't execute function, only print parser help 266 if "-h" in command or "--help" in command: 267 return None 268 for post_parser in post_parsers: 269 args = post_parser(args) 270 271 return func(self, args) 272 273 return inner 274 275 return decorator
14class Namespace(argparse.Namespace): 15 """Simple object for storing attributes. 16 17 Implements equality by attribute names and values, and provides a simple string representation. 18 """
Simple object for storing attributes.
Implements equality by attribute names and values, and provides a simple string representation.
Inherited Members
- argparse.Namespace
- Namespace
21class ArgShellParser(argparse.ArgumentParser): 22 """***Overrides exit, error, and parse_args methods*** 23 24 Object for parsing command line strings into Python objects. 25 26 Keyword Arguments: 27 - prog -- The name of the program (default: 28 ``os.path.basename(sys.argv[0])``) 29 - usage -- A usage message (default: auto-generated from arguments) 30 - description -- A description of what the program does 31 - epilog -- Text following the argument descriptions 32 - parents -- Parsers whose arguments should be copied into this one 33 - formatter_class -- HelpFormatter class for printing help messages 34 - prefix_chars -- Characters that prefix optional arguments 35 - fromfile_prefix_chars -- Characters that prefix files containing 36 additional arguments 37 - argument_default -- The default value for all arguments 38 - conflict_handler -- String indicating how to handle conflicts 39 - add_help -- Add a -h/-help option 40 - allow_abbrev -- Allow long options to be abbreviated unambiguously 41 - exit_on_error -- Determines whether or not ArgumentParser exits with 42 error info when an error occurs 43 """ 44 45 def exit(self, status=0, message=None): 46 """Override to prevent shell exit when passing -h/--help switches.""" 47 if message: 48 self._print_message(message, sys.stderr) 49 50 def error(self, message): 51 raise Exception(f"prog: {self.prog}, message: {message}") 52 53 def parse_args(self, *args, **kwargs) -> Namespace: 54 parsed_args: Namespace = super().parse_args(*args, **kwargs) 55 return parsed_args
Overrides exit, error, and parse_args methods
Object for parsing command line strings into Python objects.
Keyword Arguments:
- prog -- The name of the program (default:
os.path.basename(sys.argv[0])
)
- usage -- A usage message (default: auto-generated from arguments)
- description -- A description of what the program does
- epilog -- Text following the argument descriptions
- parents -- Parsers whose arguments should be copied into this one
- formatter_class -- HelpFormatter class for printing help messages
- prefix_chars -- Characters that prefix optional arguments
- fromfile_prefix_chars -- Characters that prefix files containing
additional arguments
- argument_default -- The default value for all arguments
- conflict_handler -- String indicating how to handle conflicts
- add_help -- Add a -h/-help option
- allow_abbrev -- Allow long options to be abbreviated unambiguously
- exit_on_error -- Determines whether or not ArgumentParser exits with
error info when an error occurs
45 def exit(self, status=0, message=None): 46 """Override to prevent shell exit when passing -h/--help switches.""" 47 if message: 48 self._print_message(message, sys.stderr)
Override to prevent shell exit when passing -h/--help switches.
error(message: string)
Prints a usage message incorporating the message to stderr and exits.
If you override this in a subclass, it should not return -- it should either exit or raise an exception.
Inherited Members
- argparse.ArgumentParser
- ArgumentParser
- add_subparsers
- parse_known_args
- convert_arg_line_to_args
- parse_intermixed_args
- parse_known_intermixed_args
- format_usage
- format_help
- print_usage
- print_help
- argparse._ActionsContainer
- register
- set_defaults
- get_default
- add_argument
- add_argument_group
- add_mutually_exclusive_group
58class ArgShell(cmd.Cmd): 59 """Subclass this to create custom ArgShells.""" 60 61 intro = "Entering argshell..." 62 prompt = "argshell>" 63 64 def do_quit(self, _: str) -> bool: 65 """Quit shell.""" 66 return True 67 68 def do_sys(self, command: str): 69 """Execute command with `os.system()`.""" 70 os.system(command) 71 72 def do_reload(self, _: str): 73 """Reload this shell.""" 74 args = [sys.executable, inspect.getsourcefile(type(self))] 75 subprocess.run(args) 76 sys.exit() 77 78 def do_help(self, arg): 79 """List available commands with "help" or detailed help with "help cmd". 80 If using 'help cmd' and the cmd is decorated with a parser, the parser help will also be printed. 81 """ 82 if arg: 83 # XXX check arg syntax 84 try: 85 func = getattr(self, "help_" + arg) 86 except AttributeError: 87 try: 88 func = getattr(self, "do_" + arg) 89 doc = func.__doc__ 90 if doc: 91 self.stdout.write("%s\n" % str(doc)) 92 # =========================Modification start========================= 93 # Check for decorator and call decorated function with "--help" 94 if hasattr(func, "__wrapped__"): 95 self.stdout.write( 96 f"Parser help for {func.__name__.replace('do_','')}:\n" 97 ) 98 func("--help") 99 if doc or hasattr(func, "__wrapped__"): 100 return 101 # =========================Modification stop========================= 102 except AttributeError: 103 pass 104 self.stdout.write("%s\n" % str(self.nohelp % (arg,))) 105 return 106 func() 107 else: 108 names = self.get_names() 109 cmds_doc = [] 110 cmds_undoc = [] 111 topics = set() 112 for name in names: 113 if name[:5] == "help_": 114 topics.add(name[5:]) 115 names.sort() 116 # There can be duplicates if routines overridden 117 prevname = "" 118 for name in names: 119 if name[:3] == "do_": 120 if name == prevname: 121 continue 122 prevname = name 123 cmd = name[3:] 124 if cmd in topics: 125 cmds_doc.append(cmd) 126 topics.remove(cmd) 127 elif getattr(self, name).__doc__: 128 cmds_doc.append(cmd) 129 else: 130 cmds_undoc.append(cmd) 131 self.stdout.write("%s\n" % str(self.doc_leader)) 132 self.print_topics(self.doc_header, cmds_doc, 15, 80) 133 self.print_topics(self.misc_header, sorted(topics), 15, 80) 134 self.print_topics(self.undoc_header, cmds_undoc, 15, 80) 135 136 def cmdloop(self, intro=None): 137 """Repeatedly issue a prompt, accept input, parse an initial prefix 138 off the received input, and dispatch to action methods, passing them 139 the remainder of the line as argument. 140 141 """ 142 143 self.preloop() 144 if self.use_rawinput and self.completekey: 145 try: 146 import readline 147 148 self.old_completer = readline.get_completer() # type: ignore 149 readline.set_completer(self.complete) # type: ignore 150 readline.parse_and_bind(self.completekey + ": complete") # type: ignore 151 except ImportError: 152 pass 153 try: 154 if intro is not None: 155 self.intro = intro 156 if self.intro: 157 self.stdout.write(str(self.intro) + "\n") 158 stop = None 159 while not stop: 160 if self.cmdqueue: 161 line = self.cmdqueue.pop(0) 162 else: 163 if self.use_rawinput: 164 try: 165 line = input(self.prompt) 166 except EOFError: 167 line = "EOF" 168 else: 169 self.stdout.write(self.prompt) 170 self.stdout.flush() 171 line = self.stdin.readline() 172 if not len(line): 173 line = "EOF" 174 else: 175 line = line.rstrip("\r\n") 176 # ===========Modification start=========== 177 try: 178 line = self.precmd(line) 179 stop = self.onecmd(line) 180 stop = self.postcmd(stop, line) 181 except Exception as e: 182 traceback.print_exc() 183 # ===========Modification stop=========== 184 self.postloop() 185 finally: 186 if self.use_rawinput and self.completekey: 187 try: 188 import readline 189 190 readline.set_completer(self.old_completer) # type: ignore 191 except ImportError: 192 pass 193 194 def emptyline(self): 195 ...
Subclass this to create custom ArgShells.
68 def do_sys(self, command: str): 69 """Execute command with `os.system()`.""" 70 os.system(command)
Execute command with os.system()
.
72 def do_reload(self, _: str): 73 """Reload this shell.""" 74 args = [sys.executable, inspect.getsourcefile(type(self))] 75 subprocess.run(args) 76 sys.exit()
Reload this shell.
78 def do_help(self, arg): 79 """List available commands with "help" or detailed help with "help cmd". 80 If using 'help cmd' and the cmd is decorated with a parser, the parser help will also be printed. 81 """ 82 if arg: 83 # XXX check arg syntax 84 try: 85 func = getattr(self, "help_" + arg) 86 except AttributeError: 87 try: 88 func = getattr(self, "do_" + arg) 89 doc = func.__doc__ 90 if doc: 91 self.stdout.write("%s\n" % str(doc)) 92 # =========================Modification start========================= 93 # Check for decorator and call decorated function with "--help" 94 if hasattr(func, "__wrapped__"): 95 self.stdout.write( 96 f"Parser help for {func.__name__.replace('do_','')}:\n" 97 ) 98 func("--help") 99 if doc or hasattr(func, "__wrapped__"): 100 return 101 # =========================Modification stop========================= 102 except AttributeError: 103 pass 104 self.stdout.write("%s\n" % str(self.nohelp % (arg,))) 105 return 106 func() 107 else: 108 names = self.get_names() 109 cmds_doc = [] 110 cmds_undoc = [] 111 topics = set() 112 for name in names: 113 if name[:5] == "help_": 114 topics.add(name[5:]) 115 names.sort() 116 # There can be duplicates if routines overridden 117 prevname = "" 118 for name in names: 119 if name[:3] == "do_": 120 if name == prevname: 121 continue 122 prevname = name 123 cmd = name[3:] 124 if cmd in topics: 125 cmds_doc.append(cmd) 126 topics.remove(cmd) 127 elif getattr(self, name).__doc__: 128 cmds_doc.append(cmd) 129 else: 130 cmds_undoc.append(cmd) 131 self.stdout.write("%s\n" % str(self.doc_leader)) 132 self.print_topics(self.doc_header, cmds_doc, 15, 80) 133 self.print_topics(self.misc_header, sorted(topics), 15, 80) 134 self.print_topics(self.undoc_header, cmds_undoc, 15, 80)
List available commands with "help" or detailed help with "help cmd". If using 'help cmd' and the cmd is decorated with a parser, the parser help will also be printed.
136 def cmdloop(self, intro=None): 137 """Repeatedly issue a prompt, accept input, parse an initial prefix 138 off the received input, and dispatch to action methods, passing them 139 the remainder of the line as argument. 140 141 """ 142 143 self.preloop() 144 if self.use_rawinput and self.completekey: 145 try: 146 import readline 147 148 self.old_completer = readline.get_completer() # type: ignore 149 readline.set_completer(self.complete) # type: ignore 150 readline.parse_and_bind(self.completekey + ": complete") # type: ignore 151 except ImportError: 152 pass 153 try: 154 if intro is not None: 155 self.intro = intro 156 if self.intro: 157 self.stdout.write(str(self.intro) + "\n") 158 stop = None 159 while not stop: 160 if self.cmdqueue: 161 line = self.cmdqueue.pop(0) 162 else: 163 if self.use_rawinput: 164 try: 165 line = input(self.prompt) 166 except EOFError: 167 line = "EOF" 168 else: 169 self.stdout.write(self.prompt) 170 self.stdout.flush() 171 line = self.stdin.readline() 172 if not len(line): 173 line = "EOF" 174 else: 175 line = line.rstrip("\r\n") 176 # ===========Modification start=========== 177 try: 178 line = self.precmd(line) 179 stop = self.onecmd(line) 180 stop = self.postcmd(stop, line) 181 except Exception as e: 182 traceback.print_exc() 183 # ===========Modification stop=========== 184 self.postloop() 185 finally: 186 if self.use_rawinput and self.completekey: 187 try: 188 import readline 189 190 readline.set_completer(self.old_completer) # type: ignore 191 except ImportError: 192 pass
Repeatedly issue a prompt, accept input, parse an initial prefix off the received input, and dispatch to action methods, passing them the remainder of the line as argument.
Called when an empty line is entered in response to the prompt.
If this method is not overridden, it repeats the last nonempty command entered.
Inherited Members
- cmd.Cmd
- Cmd
- precmd
- postcmd
- preloop
- postloop
- parseline
- onecmd
- default
- completedefault
- completenames
- complete
- get_names
- complete_help
- print_topics
- columnize
198def with_parser( 199 parser: Callable[..., ArgShellParser], 200 post_parsers: list[Callable[[Namespace], Namespace]] = [], 201) -> Callable[[Callable[[Any, Namespace], Any]], Callable[[Any, str], Any]]: 202 """Decorate a 'do_*' function in an argshell.ArgShell class with this function to pass an argshell.Namespace object to the decorated function instead of a string. 203 204 :param parser: A function that creates an argshell.ArgShellParser instance, adds arguments to it, and returns the parser. 205 206 :param post_parsers: An optional list of functions to execute where each function takes an argshell.Namespace instance and returns an argshell.Namespace instance. 207 'post_parser' functions are executed in the order they are supplied. 208 209 >>> def get_parser() -> argshell.ArgShellParser: 210 >>> parser = argshell.ArgShellParser() 211 >>> parser.add_argument("names", type=str, nargs="*", help="A list of first and last names to print.") 212 >>> parser.add_argument("-i", "--initials", action="store_true", help="Print the initials instead of the full name.") 213 >>> return parser 214 >>> 215 >>> # Convert list of first and last names to a list of tuples 216 >>> def names_list_to_tuples(args: argshell.Namespace) -> argshell.Namespace: 217 >>> args.names = [(first, last) for first, last in zip(args.names[::2], args.names[1::2])] 218 >>> if args.initials: 219 >>> args.names = [(name[0][0], name[1][0]) for name in args.names] 220 >>> return args 221 >>> 222 >>> def capitalize_names(args: argshell.Namespace) -> argshell.Namespace: 223 >>> args.names = [name.capitalize() for name in args.names] 224 >>> return args 225 >>> 226 >>> class NameShell(ArgShell): 227 >>> intro = "Entering nameshell..." 228 >>> prompt = "nameshell>" 229 >>> 230 >>> @with_parser(get_parser, [capitalize_names, names_list_to_tuples]) 231 >>> def do_printnames(self, args: argshell.Namespace): 232 >>> print(*[f"{name[0]} {name[1]}" for name in args.names], sep="\\n") 233 >>> 234 >>> NameShell().cmdloop() 235 >>> Entering nameshell... 236 >>> nameshell>printnames karl marx fred hampton emma goldman angela davis nestor makhno 237 >>> Karl Marx 238 >>> Fred Hampton 239 >>> Emma Goldman 240 >>> Angela Davis 241 >>> Nestor Makhno 242 >>> nameshell>printnames karl marx fred hampton emma goldman angela davis nestor makhno -i 243 >>> K M 244 >>> F H 245 >>> E G 246 >>> A D 247 >>> N M""" 248 249 def decorator( 250 func: Callable[[Any, Namespace], Any | None] 251 ) -> Callable[[Any, str], Any]: 252 @wraps(func) 253 def inner(self: Any, command: str) -> Any: 254 try: 255 args = parser().parse_args(shlex.split(command)) 256 except Exception as e: 257 # On parser error, print help and skip post_parser and func execution 258 if "the following arguments are required" not in str(e): 259 print(f"ERROR: {e}") 260 if "-h" not in command and "--help" not in command: 261 try: 262 args = parser().parse_args(["--help"]) 263 except Exception as e: 264 pass 265 return None 266 # Don't execute function, only print parser help 267 if "-h" in command or "--help" in command: 268 return None 269 for post_parser in post_parsers: 270 args = post_parser(args) 271 272 return func(self, args) 273 274 return inner 275 276 return decorator
Decorate a 'do_*' function in an argshell.ArgShell class with this function to pass an argshell.Namespace object to the decorated function instead of a string.
Parameters
parser: A function that creates an argshell.ArgShellParser instance, adds arguments to it, and returns the parser.
post_parsers: An optional list of functions to execute where each function takes an argshell.Namespace instance and returns an argshell.Namespace instance. 'post_parser' functions are executed in the order they are supplied.
>>> def get_parser() -> argshell.ArgShellParser:
>>> parser = argshell.ArgShellParser()
>>> parser.add_argument("names", type=str, nargs="*", help="A list of first and last names to print.")
>>> parser.add_argument("-i", "--initials", action="store_true", help="Print the initials instead of the full name.")
>>> return parser
>>>
>>> # Convert list of first and last names to a list of tuples
>>> def names_list_to_tuples(args: argshell.Namespace) -> argshell.Namespace:
>>> args.names = [(first, last) for first, last in zip(args.names[::2], args.names[1::2])]
>>> if args.initials:
>>> args.names = [(name[0][0], name[1][0]) for name in args.names]
>>> return args
>>>
>>> def capitalize_names(args: argshell.Namespace) -> argshell.Namespace:
>>> args.names = [name.capitalize() for name in args.names]
>>> return args
>>>
>>> class NameShell(ArgShell):
>>> intro = "Entering nameshell..."
>>> prompt = "nameshell>"
>>>
>>> @with_parser(get_parser, [capitalize_names, names_list_to_tuples])
>>> def do_printnames(self, args: argshell.Namespace):
>>> print(*[f"{name[0]} {name[1]}" for name in args.names], sep="\n")
>>>
>>> NameShell().cmdloop()
>>> Entering nameshell...
>>> nameshell>printnames karl marx fred hampton emma goldman angela davis nestor makhno
>>> Karl Marx
>>> Fred Hampton
>>> Emma Goldman
>>> Angela Davis
>>> Nestor Makhno
>>> nameshell>printnames karl marx fred hampton emma goldman angela davis nestor makhno -i
>>> K M
>>> F H
>>> E G
>>> A D
>>> N M