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