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