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