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    def emptyline(self):
184        ...
185
186
187def with_parser(
188    parser: Callable[..., ArgShellParser],
189    post_parsers: list[Callable[[Namespace], Namespace]] = [],
190) -> Callable[[Callable[[Any, Namespace], Any]], Callable[[Any, str], Any]]:
191    """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.
192
193    :param parser: A function that creates an argshell.ArgShellParser instance, adds arguments to it, and returns the parser.
194
195    :param post_parsers: An optional list of functions to execute where each function takes an argshell.Namespace instance and returns an argshell.Namespace instance.
196        'post_parser' functions are executed in the order they are supplied.
197
198    >>> def get_parser() -> argshell.ArgShellParser:
199    >>>     parser = argshell.ArgShellParser()
200    >>>     parser.add_argument("names", type=str, nargs="*", help="A list of first and last names to print.")
201    >>>     parser.add_argument("-i", "--initials", action="store_true", help="Print the initials instead of the full name.")
202    >>>     return parser
203    >>>
204    >>> # Convert list of first and last names to a list of tuples
205    >>> def names_list_to_tuples(args: argshell.Namespace) -> argshell.Namespace:
206    >>>     args.names = [(first, last) for first, last in zip(args.names[::2], args.names[1::2])]
207    >>>     if args.initials:
208    >>>         args.names = [(name[0][0], name[1][0]) for name in args.names]
209    >>>     return args
210    >>>
211    >>> def capitalize_names(args: argshell.Namespace) -> argshell.Namespace:
212    >>>     args.names = [name.capitalize() for name in args.names]
213    >>>     return args
214    >>>
215    >>> class NameShell(ArgShell):
216    >>>     intro = "Entering nameshell..."
217    >>>     prompt = "nameshell>"
218    >>>
219    >>>     @with_parser(get_parser, [capitalize_names, names_list_to_tuples])
220    >>>     def do_printnames(self, args: argshell.Namespace):
221    >>>         print(*[f"{name[0]} {name[1]}" for name in args.names], sep="\\n")
222    >>>
223    >>> NameShell().cmdloop()
224    >>> Entering nameshell...
225    >>> nameshell>printnames karl marx fred hampton emma goldman angela davis nestor makhno
226    >>> Karl Marx
227    >>> Fred Hampton
228    >>> Emma Goldman
229    >>> Angela Davis
230    >>> Nestor Makhno
231    >>> nameshell>printnames karl marx fred hampton emma goldman angela davis nestor makhno -i
232    >>> K M
233    >>> F H
234    >>> E G
235    >>> A D
236    >>> N M"""
237
238    def decorator(
239        func: Callable[[Any, Namespace], Any | None]
240    ) -> Callable[[Any, str], Any]:
241        @wraps(func)
242        def inner(self: Any, command: str) -> Any:
243            try:
244                args = parser().parse_args(shlex.split(command))
245            except Exception as e:
246                # On parser error, print help and skip post_parser and func execution
247                if "the following arguments are required" not in str(e):
248                    print(f"ERROR: {e}")
249                if "-h" not in command and "--help" not in command:
250                    try:
251                        args = parser().parse_args(["--help"])
252                    except Exception as e:
253                        pass
254                return None
255            # Don't execute function, only print parser help
256            if "-h" in command or "--help" in command:
257                return None
258            for post_parser in post_parsers:
259                args = post_parser(args)
260
261            return func(self, args)
262
263        return inner
264
265    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
183
184    def emptyline(self):
185        ...

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.

def emptyline(self):
184    def emptyline(self):
185        ...

Called when an empty line is entered in response to the prompt.

If this method is not overridden, it repeats the last nonempty command entered.

Inherited Members
cmd.Cmd
Cmd
precmd
postcmd
preloop
postloop
parseline
onecmd
default
completedefault
completenames
complete
get_names
complete_help
print_topics
columnize
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]]:
188def with_parser(
189    parser: Callable[..., ArgShellParser],
190    post_parsers: list[Callable[[Namespace], Namespace]] = [],
191) -> Callable[[Callable[[Any, Namespace], Any]], Callable[[Any, str], Any]]:
192    """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.
193
194    :param parser: A function that creates an argshell.ArgShellParser instance, adds arguments to it, and returns the parser.
195
196    :param post_parsers: An optional list of functions to execute where each function takes an argshell.Namespace instance and returns an argshell.Namespace instance.
197        'post_parser' functions are executed in the order they are supplied.
198
199    >>> def get_parser() -> argshell.ArgShellParser:
200    >>>     parser = argshell.ArgShellParser()
201    >>>     parser.add_argument("names", type=str, nargs="*", help="A list of first and last names to print.")
202    >>>     parser.add_argument("-i", "--initials", action="store_true", help="Print the initials instead of the full name.")
203    >>>     return parser
204    >>>
205    >>> # Convert list of first and last names to a list of tuples
206    >>> def names_list_to_tuples(args: argshell.Namespace) -> argshell.Namespace:
207    >>>     args.names = [(first, last) for first, last in zip(args.names[::2], args.names[1::2])]
208    >>>     if args.initials:
209    >>>         args.names = [(name[0][0], name[1][0]) for name in args.names]
210    >>>     return args
211    >>>
212    >>> def capitalize_names(args: argshell.Namespace) -> argshell.Namespace:
213    >>>     args.names = [name.capitalize() for name in args.names]
214    >>>     return args
215    >>>
216    >>> class NameShell(ArgShell):
217    >>>     intro = "Entering nameshell..."
218    >>>     prompt = "nameshell>"
219    >>>
220    >>>     @with_parser(get_parser, [capitalize_names, names_list_to_tuples])
221    >>>     def do_printnames(self, args: argshell.Namespace):
222    >>>         print(*[f"{name[0]} {name[1]}" for name in args.names], sep="\\n")
223    >>>
224    >>> NameShell().cmdloop()
225    >>> Entering nameshell...
226    >>> nameshell>printnames karl marx fred hampton emma goldman angela davis nestor makhno
227    >>> Karl Marx
228    >>> Fred Hampton
229    >>> Emma Goldman
230    >>> Angela Davis
231    >>> Nestor Makhno
232    >>> nameshell>printnames karl marx fred hampton emma goldman angela davis nestor makhno -i
233    >>> K M
234    >>> F H
235    >>> E G
236    >>> A D
237    >>> N M"""
238
239    def decorator(
240        func: Callable[[Any, Namespace], Any | None]
241    ) -> Callable[[Any, str], Any]:
242        @wraps(func)
243        def inner(self: Any, command: str) -> Any:
244            try:
245                args = parser().parse_args(shlex.split(command))
246            except Exception as e:
247                # On parser error, print help and skip post_parser and func execution
248                if "the following arguments are required" not in str(e):
249                    print(f"ERROR: {e}")
250                if "-h" not in command and "--help" not in command:
251                    try:
252                        args = parser().parse_args(["--help"])
253                    except Exception as e:
254                        pass
255                return None
256            # Don't execute function, only print parser help
257            if "-h" in command or "--help" in command:
258                return None
259            for post_parser in post_parsers:
260                args = post_parser(args)
261
262            return func(self, args)
263
264        return inner
265
266    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