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
class Namespace(argparse.Namespace):
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
class ArgShellParser(argparse.ArgumentParser):
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

def exit(self, status=0, message=None):
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.

def error(self, message):
47    def error(self, message):
48        raise Exception(f"prog: {self.prog}, message: {message}")

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.

def parse_args(self, *args, **kwargs) -> argshell.argshell.Namespace:
50    def parse_args(self, *args, **kwargs) -> Namespace:
51        parsed_args: Namespace = super().parse_args(*args, **kwargs)
52        return parsed_args
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
class ArgShell(cmd.Cmd):
 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.

def do_quit(self, command: str):
61    def do_quit(self, command: str):
62        """Quit shell."""
63        return True

Quit shell.

def do_sys(self, command: str):
65    def do_sys(self, command: str):
66        """Execute command with `os.system()`."""
67        os.system(command)

Execute command with os.system().

def do_help(self, arg):
 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.

def cmdloop(self, intro=None):
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
def with_parser( parser: Callable[..., argshell.argshell.ArgShellParser], post_parsers: list[typing.Callable[[argshell.argshell.Namespace], argshell.argshell.Namespace]] = []) -> Callable[[Callable[[Any, argshell.argshell.Namespace], Any]], Callable[[Any, str], Any]]:
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