argshell.argshell

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

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):
21class ArgShellParser(argparse.ArgumentParser):
22    """***Overrides exit, error, and parse_args methods***
23
24    Object for parsing command line strings into Python objects.
25
26    Keyword Arguments:
27        - prog -- The name of the program (default:
28            ``os.path.basename(sys.argv[0])``)
29        - usage -- A usage message (default: auto-generated from arguments)
30        - description -- A description of what the program does
31        - epilog -- Text following the argument descriptions
32        - parents -- Parsers whose arguments should be copied into this one
33        - formatter_class -- HelpFormatter class for printing help messages
34        - prefix_chars -- Characters that prefix optional arguments
35        - fromfile_prefix_chars -- Characters that prefix files containing
36            additional arguments
37        - argument_default -- The default value for all arguments
38        - conflict_handler -- String indicating how to handle conflicts
39        - add_help -- Add a -h/-help option
40        - allow_abbrev -- Allow long options to be abbreviated unambiguously
41        - exit_on_error -- Determines whether or not ArgumentParser exits with
42            error info when an error occurs
43    """
44
45    def exit(self, status=0, message=None):
46        """Override to prevent shell exit when passing -h/--help switches."""
47        if message:
48            self._print_message(message, sys.stderr)
49
50    def error(self, message):
51        raise Exception(f"prog: {self.prog}, message: {message}")
52
53    def parse_args(self, *args, **kwargs) -> Namespace:
54        parsed_args: Namespace = super().parse_args(*args, **kwargs)
55        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):
45    def exit(self, status=0, message=None):
46        """Override to prevent shell exit when passing -h/--help switches."""
47        if message:
48            self._print_message(message, sys.stderr)

Override to prevent shell exit when passing -h/--help switches.

def error(self, message):
50    def error(self, message):
51        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:
53    def parse_args(self, *args, **kwargs) -> Namespace:
54        parsed_args: Namespace = super().parse_args(*args, **kwargs)
55        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):
 58class ArgShell(cmd.Cmd):
 59    """Subclass this to create custom ArgShells."""
 60
 61    intro = "Entering argshell..."
 62    prompt = "argshell>"
 63
 64    def do_quit(self, _: str) -> bool:
 65        """Quit shell."""
 66        return True
 67
 68    def do_sys(self, command: str):
 69        """Execute command with `os.system()`."""
 70        os.system(command)
 71
 72    def do_reload(self, _: str):
 73        """Reload this shell."""
 74        args = [sys.executable, inspect.getsourcefile(type(self))]
 75        subprocess.run(args)
 76        sys.exit()
 77
 78    def do_help(self, arg):
 79        """List available commands with "help" or detailed help with "help cmd".
 80        If using 'help cmd' and the cmd is decorated with a parser, the parser help will also be printed.
 81        """
 82        if arg:
 83            # XXX check arg syntax
 84            try:
 85                func = getattr(self, "help_" + arg)
 86            except AttributeError:
 87                try:
 88                    func = getattr(self, "do_" + arg)
 89                    doc = func.__doc__
 90                    if doc:
 91                        self.stdout.write("%s\n" % str(doc))
 92                    # =========================Modification start=========================
 93                    # Check for decorator and call decorated function with "--help"
 94                    if hasattr(func, "__wrapped__"):
 95                        self.stdout.write(
 96                            f"Parser help for {func.__name__.replace('do_','')}:\n"
 97                        )
 98                        func("--help")
 99                    if doc or hasattr(func, "__wrapped__"):
100                        return
101                    # =========================Modification stop=========================
102                except AttributeError:
103                    pass
104                self.stdout.write("%s\n" % str(self.nohelp % (arg,)))
105                return
106            func()
107        else:
108            names = self.get_names()
109            cmds_doc = []
110            cmds_undoc = []
111            topics = set()
112            for name in names:
113                if name[:5] == "help_":
114                    topics.add(name[5:])
115            names.sort()
116            # There can be duplicates if routines overridden
117            prevname = ""
118            for name in names:
119                if name[:3] == "do_":
120                    if name == prevname:
121                        continue
122                    prevname = name
123                    cmd = name[3:]
124                    if cmd in topics:
125                        cmds_doc.append(cmd)
126                        topics.remove(cmd)
127                    elif getattr(self, name).__doc__:
128                        cmds_doc.append(cmd)
129                    else:
130                        cmds_undoc.append(cmd)
131            self.stdout.write("%s\n" % str(self.doc_leader))
132            self.print_topics(self.doc_header, cmds_doc, 15, 80)
133            self.print_topics(self.misc_header, sorted(topics), 15, 80)
134            self.print_topics(self.undoc_header, cmds_undoc, 15, 80)
135
136    def cmdloop(self, intro=None):
137        """Repeatedly issue a prompt, accept input, parse an initial prefix
138        off the received input, and dispatch to action methods, passing them
139        the remainder of the line as argument.
140
141        """
142
143        self.preloop()
144        if self.use_rawinput and self.completekey:
145            try:
146                import readline
147
148                self.old_completer = readline.get_completer()  # type: ignore
149                readline.set_completer(self.complete)  # type: ignore
150                readline.parse_and_bind(self.completekey + ": complete")  # type: ignore
151            except ImportError:
152                pass
153        try:
154            if intro is not None:
155                self.intro = intro
156            if self.intro:
157                self.stdout.write(str(self.intro) + "\n")
158            stop = None
159            while not stop:
160                if self.cmdqueue:
161                    line = self.cmdqueue.pop(0)
162                else:
163                    if self.use_rawinput:
164                        try:
165                            line = input(self.prompt)
166                        except EOFError:
167                            line = "EOF"
168                    else:
169                        self.stdout.write(self.prompt)
170                        self.stdout.flush()
171                        line = self.stdin.readline()
172                        if not len(line):
173                            line = "EOF"
174                        else:
175                            line = line.rstrip("\r\n")
176                # ===========Modification start===========
177                try:
178                    line = self.precmd(line)
179                    stop = self.onecmd(line)
180                    stop = self.postcmd(stop, line)
181                except Exception as e:
182                    traceback.print_exc()
183                # ===========Modification stop===========
184            self.postloop()
185        finally:
186            if self.use_rawinput and self.completekey:
187                try:
188                    import readline
189
190                    readline.set_completer(self.old_completer)  # type: ignore
191                except ImportError:
192                    pass
193
194    def emptyline(self):
195        ...

Subclass this to create custom ArgShells.

def do_quit(self, _: str) -> bool:
64    def do_quit(self, _: str) -> bool:
65        """Quit shell."""
66        return True

Quit shell.

def do_sys(self, command: str):
68    def do_sys(self, command: str):
69        """Execute command with `os.system()`."""
70        os.system(command)

Execute command with os.system().

def do_reload(self, _: str):
72    def do_reload(self, _: str):
73        """Reload this shell."""
74        args = [sys.executable, inspect.getsourcefile(type(self))]
75        subprocess.run(args)
76        sys.exit()

Reload this shell.

def do_help(self, arg):
 78    def do_help(self, arg):
 79        """List available commands with "help" or detailed help with "help cmd".
 80        If using 'help cmd' and the cmd is decorated with a parser, the parser help will also be printed.
 81        """
 82        if arg:
 83            # XXX check arg syntax
 84            try:
 85                func = getattr(self, "help_" + arg)
 86            except AttributeError:
 87                try:
 88                    func = getattr(self, "do_" + arg)
 89                    doc = func.__doc__
 90                    if doc:
 91                        self.stdout.write("%s\n" % str(doc))
 92                    # =========================Modification start=========================
 93                    # Check for decorator and call decorated function with "--help"
 94                    if hasattr(func, "__wrapped__"):
 95                        self.stdout.write(
 96                            f"Parser help for {func.__name__.replace('do_','')}:\n"
 97                        )
 98                        func("--help")
 99                    if doc or hasattr(func, "__wrapped__"):
100                        return
101                    # =========================Modification stop=========================
102                except AttributeError:
103                    pass
104                self.stdout.write("%s\n" % str(self.nohelp % (arg,)))
105                return
106            func()
107        else:
108            names = self.get_names()
109            cmds_doc = []
110            cmds_undoc = []
111            topics = set()
112            for name in names:
113                if name[:5] == "help_":
114                    topics.add(name[5:])
115            names.sort()
116            # There can be duplicates if routines overridden
117            prevname = ""
118            for name in names:
119                if name[:3] == "do_":
120                    if name == prevname:
121                        continue
122                    prevname = name
123                    cmd = name[3:]
124                    if cmd in topics:
125                        cmds_doc.append(cmd)
126                        topics.remove(cmd)
127                    elif getattr(self, name).__doc__:
128                        cmds_doc.append(cmd)
129                    else:
130                        cmds_undoc.append(cmd)
131            self.stdout.write("%s\n" % str(self.doc_leader))
132            self.print_topics(self.doc_header, cmds_doc, 15, 80)
133            self.print_topics(self.misc_header, sorted(topics), 15, 80)
134            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):
136    def cmdloop(self, intro=None):
137        """Repeatedly issue a prompt, accept input, parse an initial prefix
138        off the received input, and dispatch to action methods, passing them
139        the remainder of the line as argument.
140
141        """
142
143        self.preloop()
144        if self.use_rawinput and self.completekey:
145            try:
146                import readline
147
148                self.old_completer = readline.get_completer()  # type: ignore
149                readline.set_completer(self.complete)  # type: ignore
150                readline.parse_and_bind(self.completekey + ": complete")  # type: ignore
151            except ImportError:
152                pass
153        try:
154            if intro is not None:
155                self.intro = intro
156            if self.intro:
157                self.stdout.write(str(self.intro) + "\n")
158            stop = None
159            while not stop:
160                if self.cmdqueue:
161                    line = self.cmdqueue.pop(0)
162                else:
163                    if self.use_rawinput:
164                        try:
165                            line = input(self.prompt)
166                        except EOFError:
167                            line = "EOF"
168                    else:
169                        self.stdout.write(self.prompt)
170                        self.stdout.flush()
171                        line = self.stdin.readline()
172                        if not len(line):
173                            line = "EOF"
174                        else:
175                            line = line.rstrip("\r\n")
176                # ===========Modification start===========
177                try:
178                    line = self.precmd(line)
179                    stop = self.onecmd(line)
180                    stop = self.postcmd(stop, line)
181                except Exception as e:
182                    traceback.print_exc()
183                # ===========Modification stop===========
184            self.postloop()
185        finally:
186            if self.use_rawinput and self.completekey:
187                try:
188                    import readline
189
190                    readline.set_completer(self.old_completer)  # type: ignore
191                except ImportError:
192                    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):
194    def emptyline(self):
195        ...

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