diff options
author | Todd Leonhardt <todd.leonhardt@gmail.com> | 2020-02-24 17:30:34 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-24 17:30:34 -0500 |
commit | d25fd7146131688e934290a3c5bf0407e904dbb2 (patch) | |
tree | 5fd4b905157eafbf3ea0071bfd625763b109ee78 /cmd2 | |
parent | fea1bc15f4a53aa72d16c2985377fe3987b6b348 (diff) | |
parent | 803f71a04d7f7290ca1390a164808679f5943178 (diff) | |
download | cmd2-git-d25fd7146131688e934290a3c5bf0407e904dbb2.tar.gz |
Merge pull request #899 from python-cmd2/api_docs
API documentation
Diffstat (limited to 'cmd2')
-rw-r--r-- | cmd2/ansi.py | 32 | ||||
-rw-r--r-- | cmd2/argparse_custom.py | 315 | ||||
-rw-r--r-- | cmd2/cmd2.py | 98 | ||||
-rw-r--r-- | cmd2/constants.py | 10 | ||||
-rw-r--r-- | cmd2/decorators.py | 55 | ||||
-rw-r--r-- | cmd2/history.py | 42 | ||||
-rwxr-xr-x | cmd2/parsing.py | 186 | ||||
-rw-r--r-- | cmd2/plugin.py | 4 | ||||
-rw-r--r-- | cmd2/py_bridge.py | 42 | ||||
-rw-r--r-- | cmd2/utils.py | 14 |
10 files changed, 474 insertions, 324 deletions
diff --git a/cmd2/ansi.py b/cmd2/ansi.py index eac2bd3a..27c9e87a 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -17,11 +17,33 @@ colorama.init(strip=False) # Values for allow_style setting STYLE_NEVER = 'Never' +""" +Constant for ``cmd2.ansi.allow_style`` to indicate ANSI sequences +should be removed from all output. +""" STYLE_TERMINAL = 'Terminal' +""" +Constant for ``cmd2.ansi.allow_style`` to indicate ANSI sequences +should be removed if the output is not going to the terminal. +""" STYLE_ALWAYS = 'Always' +""" +Constant for ``cmd2.ansi.allow_style`` to indicate ANSI sequences +should alwyas be output. +""" # Controls when ANSI style style sequences are allowed in output allow_style = STYLE_TERMINAL +"""When using outside of a cmd2 app, set this variable to one of: + +- ``STYLE_NEVER`` - remove ANSI sequences from all output +- ``STYLE_TERMINAL`` - remove ANSI sequences if the output is not going to the terminal +- ``STYLE_ALWAYS`` - always output ANSI sequences + +to control the output of ANSI sequences by methods in this module. + +The default is ``STYLE_TERMINAL``. +""" # Regular expression to match ANSI style sequences (including 8-bit and 24-bit colors) ANSI_STYLE_RE = re.compile(r'\x1b\[[^m]*m') @@ -32,7 +54,9 @@ class ColorBase(Enum): Base class used for defining color enums. See fg and bg classes for examples. Child classes should define enums in the follow structure: + key: color name (e.g. black) + value: anything that when cast to a string returns an ANSI sequence """ def __str__(self) -> str: @@ -111,17 +135,25 @@ class bg(ColorBase): FG_RESET = fg.reset.value +"""ANSI sequence to reset the foreground attributes""" BG_RESET = bg.reset.value +"""ANSI sequence to reset the terminal background attributes""" RESET_ALL = Style.RESET_ALL +"""ANSI sequence to reset all terminal attributes""" # Text intensities INTENSITY_BRIGHT = Style.BRIGHT +"""ANSI sequence to make the text bright""" INTENSITY_DIM = Style.DIM +"""ANSI sequence to make the text dim""" INTENSITY_NORMAL = Style.NORMAL +"""ANSI sequence to make the text normal""" # ANSI style sequences not provided by colorama UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4) +"""ANSI sequence to turn on underline""" UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24) +"""ANSI sequence to turn off underline""" def strip_style(text: str) -> str: diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 81fec013..02e23f17 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1,127 +1,150 @@ # coding=utf-8 """ -This module adds capabilities to argparse by patching a few of its functions. It also defines a parser -class called Cmd2ArgumentParser which improves error and help output over normal argparse. All cmd2 code uses -this parser and it is recommended that developers of cmd2-based apps either use it or write their own parser -that inherits from it. This will give a consistent look-and-feel between the help/error output of built-in -cmd2 commands and the app-specific commands. If you wish to override the parser used by cmd2's built-in -commands, see override_parser.py example. +This module adds capabilities to argparse by patching a few of its functions. +It also defines a parser class called Cmd2ArgumentParser which improves error +and help output over normal argparse. All cmd2 code uses this parser and it is +recommended that developers of cmd2-based apps either use it or write their own +parser that inherits from it. This will give a consistent look-and-feel between +the help/error output of built-in cmd2 commands and the app-specific commands. +If you wish to override the parser used by cmd2's built-in commands, see +override_parser.py example. -Since the new capabilities are added by patching at the argparse API level, they are available whether or not -Cmd2ArgumentParser is used. However, the help and error output of Cmd2ArgumentParser is customized to notate -nargs ranges whereas any other parser class won't be as explicit in their output. +Since the new capabilities are added by patching at the argparse API level, +they are available whether or not Cmd2ArgumentParser is used. However, the help +and error output of Cmd2ArgumentParser is customized to notate nargs ranges +whereas any other parser class won't be as explicit in their output. -############################################################################################################ -# Added capabilities -############################################################################################################ -Extends argparse nargs functionality by allowing tuples which specify a range (min, max). To specify a max -value with no upper bound, use a 1-item tuple (min,) - - Example: - # -f argument expects at least 3 values - parser.add_argument('-f', nargs=(3,)) - - # -f argument expects 3 to 5 values - parser.add_argument('-f', nargs=(3, 5)) - -Tab Completion: - cmd2 uses its ArgparseCompleter class to enable argparse-based tab completion on all commands that use the - @with_argparse wrappers. Out of the box you get tab completion of commands, subcommands, and flag names, - as well as instructive hints about the current argument that print when tab is pressed. In addition, - you can add tab completion for each argument's values using parameters passed to add_argument(). - - Below are the 5 add_argument() parameters for enabling tab completion of an argument's value. Only one - can be used at a time. - - choices - Pass a list of values to the choices parameter. - Example: - parser.add_argument('-o', '--options', choices=['An Option', 'SomeOtherOption']) - parser.add_argument('-o', '--options', choices=my_list) - - choices_function - Pass a function that returns choices. This is good in cases where the choice list is dynamically - generated when the user hits tab. - - Example: - def my_choices_function(): - ... - return my_generated_list - - parser.add_argument('-o', '--options', choices_function=my_choices_function) - - choices_method - This is exactly like choices_function, but the function needs to be an instance method of a cmd2-based class. - When ArgparseCompleter calls the method, it will pass the app instance as the self argument. This is good in - cases where the list of choices being generated relies on state data of the cmd2-based app - - Example: - def my_choices_method(self): - ... - return my_generated_list - - completer_function - Pass a tab completion function that does custom completion. Since custom tab completion operations commonly - need to modify cmd2's instance variables related to tab completion, it will be rare to need a completer - function. completer_method should be used in those cases. - - Example: - def my_completer_function(text, line, begidx, endidx): - ... - return completions - parser.add_argument('-o', '--options', completer_function=my_completer_function) - - completer_method - This is exactly like completer_function, but the function needs to be an instance method of a cmd2-based class. - When ArgparseCompleter calls the method, it will pass the app instance as the self argument. cmd2 provides - a few completer methods for convenience (e.g., path_complete, delimiter_complete) - - Example: - This adds file-path completion to an argument - parser.add_argument('-o', '--options', completer_method=cmd2.Cmd.path_complete) - - - You can use functools.partial() to prepopulate values of the underlying choices and completer functions/methods. - - Example: - This says to call path_complete with a preset value for its path_filter argument. - completer_method = functools.partial(path_complete, - path_filter=lambda path: os.path.isdir(path)) - parser.add_argument('-o', '--options', choices_method=completer_method) - - Of the 5 tab completion parameters, choices is the only one where argparse validates user input against items - in the choices list. This is because the other 4 parameters are meant to tab complete data sets that are viewed - as dynamic. Therefore it is up to the developer to validate if the user has typed an acceptable value for these - arguments. - - The following functions exist in cases where you may want to manually add a choice-providing function/method to - an existing argparse action. For instance, in __init__() of a custom action class. - - set_choices_function(action, func) - set_choices_method(action, method) - set_completer_function(action, func) - set_completer_method(action, method) - - There are times when what's being tab completed is determined by a previous argument on the command line. - In theses cases, Autocompleter can pass a dictionary that maps the command line tokens up through the one - being completed to their argparse argument name. To receive this dictionary, your choices/completer function - should have an argument called arg_tokens. - - Example: - def my_choices_method(self, arg_tokens) - def my_completer_method(self, text, line, begidx, endidx, arg_tokens) - - All values of the arg_tokens dictionary are lists, even if a particular argument expects only 1 token. Since - ArgparseCompleter is for tab completion, it does not convert the tokens to their actual argument types or validate - their values. All tokens are stored in the dictionary as the raw strings provided on the command line. It is up to - the developer to determine if the user entered the correct argument type (e.g. int) and validate their values. - -CompletionItem Class: - This class was added to help in cases where uninformative data is being tab completed. For instance, - tab completing ID numbers isn't very helpful to a user without context. Returning a list of CompletionItems - instead of a regular string for completion results will signal the ArgparseCompleter to output the completion - results in a table of completion tokens with descriptions instead of just a table of tokens. +**Added capabilities** + +Extends argparse nargs functionality by allowing tuples which specify a range +(min, max). To specify a max value with no upper bound, use a 1-item tuple +(min,) + +Example:: + + # -f argument expects at least 3 values + parser.add_argument('-f', nargs=(3,)) + + # -f argument expects 3 to 5 values + parser.add_argument('-f', nargs=(3, 5)) + + +**Tab Completion** + +cmd2 uses its ArgparseCompleter class to enable argparse-based tab completion +on all commands that use the @with_argparse wrappers. Out of the box you get +tab completion of commands, subcommands, and flag names, as well as instructive +hints about the current argument that print when tab is pressed. In addition, +you can add tab completion for each argument's values using parameters passed +to add_argument(). + +Below are the 5 add_argument() parameters for enabling tab completion of an +argument's value. Only one can be used at a time. + +``choices`` - pass a list of values to the choices parameter. + + Example:: + + parser.add_argument('-o', '--options', choices=['An Option', 'SomeOtherOption']) + parser.add_argument('-o', '--options', choices=my_list) + +``choices_function`` - pass a function that returns choices. This is good in +cases where the choice list is dynamically generated when the user hits tab. + + Example:: + + def my_choices_function(): + ... + return my_generated_list + + parser.add_argument('-o', '--options', choices_function=my_choices_function) + +``choices_method`` - this is exactly like choices_function, but the function +needs to be an instance method of a cmd2-based class. When ArgparseCompleter +calls the method, it will pass the app instance as the self argument. This is +good in cases where the list of choices being generated relies on state data of +the cmd2-based app + + Example:: + + def my_choices_method(self): + ... + return my_generated_list + +``completer_function`` - pass a tab completion function that does custom +completion. Since custom tab completion operations commonly need to modify +cmd2's instance variables related to tab completion, it will be rare to need a +completer function. completer_method should be used in those cases. + + Example:: + + def my_completer_function(text, line, begidx, endidx): + ... + return completions + parser.add_argument('-o', '--options', completer_function=my_completer_function) + +``completer_method`` - this is exactly like completer_function, but the +function needs to be an instance method of a cmd2-based class. When +ArgparseCompleter calls the method, it will pass the app instance as the self +argument. cmd2 provides a few completer methods for convenience (e.g., +path_complete, delimiter_complete) + + Example:: + + # this adds file-path completion to an argument + parser.add_argument('-o', '--options', completer_method=cmd2.Cmd.path_complete) + + + You can use functools.partial() to prepopulate values of the underlying + choices and completer functions/methods. + + Example:: + + # this says to call path_complete with a preset value for its path_filter argument. + completer_method = functools.partial(path_complete, + path_filter=lambda path: os.path.isdir(path)) + parser.add_argument('-o', '--options', choices_method=completer_method) + +Of the 5 tab completion parameters, choices is the only one where argparse +validates user input against items in the choices list. This is because the +other 4 parameters are meant to tab complete data sets that are viewed as +dynamic. Therefore it is up to the developer to validate if the user has typed +an acceptable value for these arguments. + +The following functions exist in cases where you may want to manually add a +choice-providing function/method to an existing argparse action. For instance, +in __init__() of a custom action class. + + - set_choices_function(action, func) + - set_choices_method(action, method) + - set_completer_function(action, func) + - set_completer_method(action, method) + +There are times when what's being tab completed is determined by a previous +argument on the command line. In theses cases, Autocompleter can pass a +dictionary that maps the command line tokens up through the one being completed +to their argparse argument name. To receive this dictionary, your +choices/completer function should have an argument called arg_tokens. + + Example:: + + def my_choices_method(self, arg_tokens) + def my_completer_method(self, text, line, begidx, endidx, arg_tokens) + +All values of the arg_tokens dictionary are lists, even if a particular +argument expects only 1 token. Since ArgparseCompleter is for tab completion, +it does not convert the tokens to their actual argument types or validate their +values. All tokens are stored in the dictionary as the raw strings provided on +the command line. It is up to the developer to determine if the user entered +the correct argument type (e.g. int) and validate their values. + +CompletionItem Class - This class was added to help in cases where +uninformative data is being tab completed. For instance, tab completing ID +numbers isn't very helpful to a user without context. Returning a list of +CompletionItems instead of a regular string for completion results will signal +the ArgparseCompleter to output the completion results in a table of completion +tokens with descriptions instead of just a table of tokens:: Instead of this: 1 2 3 @@ -133,42 +156,50 @@ CompletionItem Class: 3 Yet another item - The left-most column is the actual value being tab completed and its header is that value's name. - The right column header is defined using the descriptive_header parameter of add_argument(). The right - column values come from the CompletionItem.description value. +The left-most column is the actual value being tab completed and its header is +that value's name. The right column header is defined using the +descriptive_header parameter of add_argument(). The right column values come +from the CompletionItem.description value. + +Example:: - Example: - token = 1 - token_description = "My Item" - completion_item = CompletionItem(token, token_description) + token = 1 + token_description = "My Item" + completion_item = CompletionItem(token, token_description) - Since descriptive_header and CompletionItem.description are just strings, you can format them in - such a way to have multiple columns. +Since descriptive_header and CompletionItem.description are just strings, you +can format them in such a way to have multiple columns:: ITEM_ID Item Name Checked Out Due Date 1 My item True 02/02/2022 2 Another item False 3 Yet another item False - To use CompletionItems, just return them from your choices or completer functions. +To use CompletionItems, just return them from your choices or completer +functions. - To avoid printing a ton of information to the screen at once when a user presses tab, there is - a maximum threshold for the number of CompletionItems that will be shown. Its value is defined - in cmd2.Cmd.max_completion_items. It defaults to 50, but can be changed. If the number of completion - suggestions exceeds this number, they will be displayed in the typical columnized format and will - not include the description value of the CompletionItems. +To avoid printing a ton of information to the screen at once when a user +presses tab, there is a maximum threshold for the number of CompletionItems +that will be shown. Its value is defined in cmd2.Cmd.max_completion_items. It +defaults to 50, but can be changed. If the number of completion suggestions +exceeds this number, they will be displayed in the typical columnized format +and will not include the description value of the CompletionItems. -############################################################################################################ -# Patched argparse functions: -########################################################################################################### -argparse._ActionsContainer.add_argument - adds arguments related to tab completion and enables nargs range parsing - See _add_argument_wrapper for more details on these argument + +**Patched argparse functions** + +argparse._ActionsContainer.add_argument - adds arguments related to tab + completion and enables nargs range + parsing See _add_argument_wrapper for + more details on these argument argparse.ArgumentParser._get_nargs_pattern - adds support to for nargs ranges - See _get_nargs_pattern_wrapper for more details + See _get_nargs_pattern_wrapper for + more details -argparse.ArgumentParser._match_argument - adds support to for nargs ranges - See _match_argument_wrapper for more details +argparse.ArgumentParser._match_argument - adds support to for nargs ranges See + _match_argument_wrapper for more + details """ import argparse diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index cc80c906..b2d745ac 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -135,28 +135,41 @@ class Cmd(cmd.Cmd): allow_cli_args: bool = True, transcript_files: Optional[List[str]] = None, allow_redirection: bool = True, multiline_commands: Optional[List[str]] = None, terminators: Optional[List[str]] = None, shortcuts: Optional[Dict[str, str]] = None) -> None: - """An easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package. + """An easy but powerful framework for writing line-oriented command + interpreters. Extends Python's cmd package. :param completekey: readline name of a completion key, default to Tab :param stdin: alternate input file object, if not specified, sys.stdin is used :param stdout: alternate output file object, if not specified, sys.stdout is used :param persistent_history_file: file path to load a persistent cmd2 command history from - :param persistent_history_length: max number of history items to write to the persistent history file + :param persistent_history_length: max number of history items to write + to the persistent history file :param startup_script: file path to a script to execute at startup :param use_ipython: should the "ipy" command be included for an embedded IPython shell - :param allow_cli_args: if True, then cmd2 will process command line arguments as either - commands to be run or, if -t is specified, transcript files to run. - This should be set to False if your application parses its own arguments. - :param transcript_files: allow running transcript tests when allow_cli_args is False - :param allow_redirection: should output redirection and pipes be allowed. this is only a security setting - and does not alter parsing behavior. + :param allow_cli_args: if ``True``, then :meth:`cmd2.Cmd.__init__` will process command + line arguments as either commands to be run or, if ``-t`` or + ``--test`` are given, transcript files to run. This should be + set to ``False`` if your application parses its own command line + arguments. + :param transcript_files: pass a list of transcript files to be run on initialization. + This allows running transcript tests when ``allow_cli_args`` + is ``False``. If ``allow_cli_args`` is ``True`` this parameter + is ignored. + :param allow_redirection: If ``False``, prevent output redirection and piping to shell + commands. This parameter prevents redirection and piping, but + does not alter parsing behavior. A user can still type + redirection and piping tokens, and they will be parsed as such + but they won't do anything. :param multiline_commands: list of commands allowed to accept multi-line input - :param terminators: list of characters that terminate a command. These are mainly intended for terminating - multiline commands, but will also terminate single-line commands. If not supplied, then - defaults to semicolon. If your app only contains single-line commands and you want - terminators to be treated as literals by the parser, then set this to an empty list. - :param shortcuts: dictionary containing shortcuts for commands. If not supplied, then defaults to - constants.DEFAULT_SHORTCUTS. + :param terminators: list of characters that terminate a command. These are mainly + intended for terminating multiline commands, but will also + terminate single-line commands. If not supplied, the default + is a semicolon. If your app only contains single-line commands + and you want terminators to be treated as literals by the parser, + then set this to an empty list. + :param shortcuts: dictionary containing shortcuts for commands. If not supplied, + then defaults to constants.DEFAULT_SHORTCUTS. If you do not want + any shortcuts, pass an empty dictionary. """ # If use_ipython is False, make sure the ipy command isn't available in this instance if not use_ipython: @@ -192,7 +205,7 @@ class Cmd(cmd.Cmd): # A dictionary mapping settable names to their Settable instance self.settables = dict() - self.build_settables() + self._build_settables() # Use as prompt for multiline commands on the 2nd+ line of input self.continuation_prompt = '> ' @@ -374,24 +387,26 @@ class Cmd(cmd.Cmd): def add_settable(self, settable: Settable) -> None: """ - Convenience method to add a settable parameter to self.settables + Convenience method to add a settable parameter to ``self.settables`` + :param settable: Settable object being added """ self.settables[settable.name] = settable def remove_settable(self, name: str) -> None: """ - Convenience method for removing a settable parameter from self.settables + Convenience method for removing a settable parameter from ``self.settables`` + :param name: name of the settable being removed - :raises: KeyError if the no Settable matches this name + :raises: KeyError if the Settable matches this name """ try: del self.settables[name] except KeyError: raise KeyError(name + " is not a settable parameter") - def build_settables(self): - """Populates self.add_settable with parameters that can be edited via the set command""" + def _build_settables(self): + """Construct the default settings""" self.add_settable(Settable('allow_style', str, 'Allow ANSI text style sequences in output (valid values: ' '{}, {}, {})'.format(ansi.STYLE_TERMINAL, @@ -1508,13 +1523,54 @@ class Cmd(cmd.Cmd): raise KeyboardInterrupt("Got a keyboard interrupt") def precmd(self, statement: Statement) -> Statement: - """Hook method executed just before the command is processed by ``onecmd()`` and after adding it to the history. + """Hook method executed just before the command is executed by + :meth:`~cmd2.Cmd.onecmd` and after adding it to history. :param statement: subclass of str which also contains the parsed input :return: a potentially modified version of the input Statement object + + See :meth:`~cmd2.Cmd.register_postparsing_hook` and + :meth:`~cmd2.Cmd.register_precmd_hook` for more robust ways + to run hooks before the command is executed. See + :ref:`features/hooks:Postparsing Hooks` and + :ref:`features/hooks:Precommand Hooks` for more information. """ return statement + def postcmd(self, stop: bool, statement: Statement) -> bool: + """Hook method executed just after a command is executed by + :meth:`~cmd2.Cmd.onecmd`. + + :param stop: return `True` to request the command loop terminate + :param statement: subclass of str which also contains the parsed input + + See :meth:`~cmd2.Cmd.register_postcmd_hook` and :meth:`~cmd2.Cmd.register_cmdfinalization_hook` for more robust ways + to run hooks after the command is executed. See + :ref:`features/hooks:Postcommand Hooks` and + :ref:`features/hooks:Command Finalization Hooks` for more information. + """ + return stop + + def preloop(self): + """Hook method executed once when the :meth:`~.cmd2.Cmd.cmdloop()` + method is called. + + See :meth:`~cmd2.Cmd.register_preloop_hook` for a more robust way + to run hooks before the command loop begins. See + :ref:`features/hooks:Application Lifecycle Hooks` for more information. + """ + pass + + def postloop(self): + """Hook method executed once when the :meth:`~.cmd2.Cmd.cmdloop()` + method is about to return. + + See :meth:`~cmd2.Cmd.register_postloop_hook` for a more robust way + to run hooks after the command loop completes. See + :ref:`features/hooks:Application Lifecycle Hooks` for more information. + """ + pass + def parseline(self, line: str) -> Tuple[str, str, str]: """Parse the line into a command name and a string containing the arguments. diff --git a/cmd2/constants.py b/cmd2/constants.py index bc72817f..d7e52cc9 100644 --- a/cmd2/constants.py +++ b/cmd2/constants.py @@ -1,6 +1,10 @@ # # coding=utf-8 -"""Constants and definitions""" +"""This module contains constants used throughout ``cmd2``.""" + +# Unless documented in https://cmd2.readthedocs.io/en/latest/api/index.html +# nothing here should be considered part of the public API of this module + # Used for command parsing, output redirection, tab completion and word # breaks. Do not change. @@ -32,9 +36,9 @@ HELP_FUNC_PREFIX = 'help_' # All command completer functions start with this COMPLETER_FUNC_PREFIX = 'complete_' -############################################################################################################ +############################################################################## # The following are optional attributes added to do_* command functions -############################################################################################################ +############################################################################## # The custom help category a command belongs to CMD_ATTR_HELP_CATEGORY = 'help_category' diff --git a/cmd2/decorators.py b/cmd2/decorators.py index ee5db140..e9aff0eb 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -8,7 +8,21 @@ from .parsing import Statement def with_category(category: str) -> Callable: - """A decorator to apply a category to a command function.""" + """A decorator to apply a category to a ``do_*`` command method. + + :param category: the name of the category in which this command should + be grouped when displaying the list of commands. + + :Example: + + >>> class MyApp(cmd2.Cmd): + >>> @cmd2.with_category('Text Functions') + >>> def do_echo(self, args) + >>> self.poutput(args) + + For an alternative approach to categorizing commands using a function, see + :func:`~cmd2.utils.categorize` + """ def cat_decorator(func): from .utils import categorize categorize(func, category) @@ -17,12 +31,22 @@ def with_category(category: str) -> Callable: def with_argument_list(*args: List[Callable], preserve_quotes: bool = False) -> Callable[[List], Optional[bool]]: - """A decorator to alter the arguments passed to a do_* cmd2 method. Default passes a string of whatever the user - typed. With this decorator, the decorated method will receive a list of arguments parsed from user input. + """ + A decorator to alter the arguments passed to a ``do_*`` method. Default + passes a string of whatever the user typed. With this decorator, the + decorated method will receive a list of arguments parsed from user input. - :param args: Single-element positional argument list containing do_* method this decorator is wrapping - :param preserve_quotes: if True, then argument quotes will not be stripped + :param args: Single-element positional argument list containing ``do_*`` method + this decorator is wrapping + :param preserve_quotes: if ``True``, then argument quotes will not be stripped :return: function that gets passed a list of argument strings + + :Example: + + >>> class MyApp(cmd2.Cmd): + >>> @cmd2.with_argument_list + >>> def do_echo(self, arglist): + >>> self.poutput(' '.join(arglist) """ import functools @@ -75,18 +99,19 @@ def with_argparser_and_unknown_args(parser: argparse.ArgumentParser, *, ns_provider: Optional[Callable[..., argparse.Namespace]] = None, preserve_quotes: bool = False) -> \ Callable[[argparse.Namespace, List], Optional[bool]]: - """A decorator to alter a cmd2 method to populate its ``args`` argument by parsing arguments with the given - instance of argparse.ArgumentParser, but also returning unknown args as a list. + """A decorator to alter a cmd2 method to populate its ``args`` argument by parsing + arguments with the given instance of argparse.ArgumentParser, but also returning + unknown args as a list. :param parser: unique instance of ArgumentParser - :param ns_provider: An optional function that accepts a cmd2.Cmd object as an argument and returns an - argparse.Namespace. This is useful if the Namespace needs to be prepopulated with - state data that affects parsing. - :param preserve_quotes: if True, then arguments passed to argparse maintain their quotes - :return: function that gets passed argparse-parsed args in a Namespace and a list of unknown argument strings - A member called __statement__ is added to the Namespace to provide command functions access to the - Statement object. This can be useful if the command function needs to know the command line. - + :param ns_provider: An optional function that accepts a cmd2.Cmd object as an argument + and returns an argparse.Namespace. This is useful if the Namespace + needs to be prepopulated with state data that affects parsing. + :param preserve_quotes: if ``True``, then arguments passed to argparse maintain their quotes + :return: function that gets passed argparse-parsed args in a ``Namespace`` and a list + of unknown argument strings. A member called ``__statement__`` is added to the + ``Namespace`` to provide command functions access to the :class:`cmd2.Statement` + object. This can be useful if the command function needs to know the command line. """ import functools diff --git a/cmd2/history.py b/cmd2/history.py index 3b18fbeb..7b52aa16 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -15,7 +15,7 @@ from .parsing import Statement @attr.s(frozen=True) class HistoryItem(): - """Class used to represent one command in the History list""" + """Class used to represent one command in the history list""" _listformat = ' {:>4} {}' _ex_listformat = ' {:>4}x {}' @@ -28,16 +28,23 @@ class HistoryItem(): @property def raw(self) -> str: - """Return the raw input from the user for this item""" + """The raw input from the user for this item. + + Proxy property for ``self.statement.raw`` + """ return self.statement.raw @property def expanded(self) -> str: - """Return the command as run which includes shortcuts and aliases resolved plus any changes made in hooks""" + """Return the command as run which includes shortcuts and aliases resolved + plus any changes made in hooks + + Proxy property for ``self.statement.expanded_command_line`` + """ return self.statement.expanded_command_line def pr(self, script=False, expanded=False, verbose=False) -> str: - """Represent a HistoryItem in a pretty fashion suitable for printing. + """Represent this item in a pretty fashion suitable for printing. If you pass verbose=True, script and expanded will be ignored @@ -72,15 +79,17 @@ class HistoryItem(): class History(list): - """A list of HistoryItems that knows how to respond to user requests. + """A list of :class:`~cmd2.history.HistoryItem` objects with additional methods + for searching and managing the list. + + :class:`~cmd2.Cmd` instantiates this class into the :data:`~cmd2.Cmd.history` + attribute, and adds commands to it as a user enters them. - Here are some key methods: + See :ref:`features/history:History` for information about the built-in command + which allows users to view, search, run, and save previously entered commands. - select() - parse user input and return a list of relevant history items - str_search() - return a list of history items which contain the given string - regex_search() - return a list of history items which match a given regex - get() - return a single element of the list, using 1 based indexing - span() - given a 1-based slice, return the appropriate list of history items + Developers interested in accessing previously entered commands can use this + class to gain access to the historical record. """ def __init__(self, seq=()) -> None: super().__init__(seq) @@ -99,9 +108,10 @@ class History(list): return result def append(self, new: Statement) -> None: - """Append a HistoryItem to end of the History list. + """Append a new statement to the end of the History list. - :param new: command line to convert to HistoryItem and add to the end of the History list + :param new: Statement object which will be composed into a HistoryItem + and added to the end of the list """ history_item = HistoryItem(new, len(self) + 1) super().append(history_item) @@ -115,7 +125,7 @@ class History(list): """Get item from the History list using 1-based indexing. :param index: optional item to get (index as either integer or string) - :return: a single HistoryItem + :return: a single :class:`~cmd2.history.HistoryItem` """ index = int(index) if index == 0: @@ -171,8 +181,8 @@ class History(list): Different from native python indexing and slicing of arrays, this method uses 1-based array numbering. Users who are not programmers can't grok - 0 based numbering. Programmers can usually grok either. Which reminds me, - there are only two hard problems in programming: + zero based numbering. Programmers can sometimes grok zero based numbering. + Which reminds me, there are only two hard problems in programming: - naming - cache invalidation diff --git a/cmd2/parsing.py b/cmd2/parsing.py index cef0b088..078b1860 100755 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -76,68 +76,32 @@ class Macro: class Statement(str): """String subclass with additional attributes to store the results of parsing. - The cmd module in the standard library passes commands around as a - string. To retain backwards compatibility, cmd2 does the same. However, we - need a place to capture the additional output of the command parsing, so we add - our own attributes to this subclass. + The ``cmd`` module in the standard library passes commands around as a + string. To retain backwards compatibility, ``cmd2`` does the same. However, + we need a place to capture the additional output of the command parsing, so + we add our own attributes to this subclass. Instances of this class should not be created by anything other than the - `StatementParser.parse()` method, nor should any of the attributes be modified - once the object is created. - - The string portion of the class contains the arguments, but not the command, nor - the output redirection clauses. - - Here's some suggestions and best practices for how to use the attributes of this - object: - - command - the name of the command, shortcuts and aliases have already been - expanded - - args - the arguments to the command, excluding output redirection and command - terminators. If the user used quotes in their input, they remain here, - and you will have to handle them on your own. - - arg_list - the arguments to the command, excluding output redirection and - command terminators. Each argument is represented as an element - in the list. Quoted arguments remain quoted. If you want to - remove the quotes, use `cmd2.utils.strip_quotes()` or use - `argv[1:]` - - command_and_args - join the args and the command together with a space. Output - redirection is excluded. - - argv - this is a list of arguments in the style of `sys.argv`. The first element - of the list is the command. Subsequent elements of the list contain any - additional arguments, with quotes removed, just like bash would. This - is very useful if you are going to use `argparse.parse_args()`: - ``` - def do_mycommand(stmt): - mycommand_argparser.parse_args(stmt.argv) - ... - ``` - - raw - if you want full access to exactly what the user typed at the input prompt - you can get it, but you'll have to parse it on your own, including: - - shortcuts and aliases - - quoted commands and arguments - - output redirection - - multi-line command terminator handling - if you use multiline commands, all the input will be passed to you in - this string, but there will be embedded newlines where - the user hit return to continue the command on the next line. + :meth:`cmd2.parsing.StatementParser.parse` method, nor should any of the + attributes be modified once the object is created. + + The string portion of the class contains the arguments, but not the + command, nor the output redirection clauses. Tips: - 1. `argparse` is your friend for anything complex. `cmd2` has two decorators - (`with_argparser`, and `with_argparser_and_unknown_args`) which you can use - to make your command method receive a namespace of parsed arguments, whether - positional or denoted with switches. + 1. `argparse <https://docs.python.org/3/library/argparse.html>`_ is your + friend for anything complex. ``cmd2`` has two decorators + (:func:`~cmd2.decorators.with_argparser`, and + :func:`~cmd2.decorators.with_argparser_and_unknown_args`) which you can + use to make your command method receive a namespace of parsed arguments, + whether positional or denoted with switches. - 2. For commands with simple positional arguments, use `args` or `arg_list` + 2. For commands with simple positional arguments, use + :attr:`~cmd2.Statement.args` or :attr:`~cmd2.Statement.arg_list` - 3. If you don't want to have to worry about quoted arguments, use - argv[1:], which strips them all off for you. + 3. If you don't want to have to worry about quoted arguments, see + :attr:`argv` for a trick which strips quotes off for you. """ # the arguments, but not the command, nor the output redirection clauses. args = attr.ib(default='', validator=attr.validators.instance_of(str)) @@ -219,15 +183,20 @@ class Statement(str): @property def expanded_command_line(self) -> str: - """Combines command_and_args and post_command""" + """Concatenate :meth:`~cmd2.Statement.command_and_args` + and :meth:`~cmd2.Statement.post_command`""" return self.command_and_args + self.post_command @property def argv(self) -> List[str]: - """a list of arguments a la sys.argv. + """a list of arguments a-la ``sys.argv``. + + The first element of the list is the command after shortcut and macro + expansion. Subsequent elements of the list contain any additional + arguments, with quotes removed, just like bash would. This is very + useful if you are going to use ``argparse.parse_args()``. - Quotes, if any, are removed from the elements of the list, and aliases - and shortcuts are expanded + If you want to strip quotes from the input, you can use ``argv[1:]``. """ if self.command: rtn = [utils.strip_quotes(self.command)] @@ -240,11 +209,7 @@ class Statement(str): class StatementParser: - """Parse raw text into command components. - - Shortcuts is a list of tuples with each tuple containing the shortcut and - the expansion. - """ + """Parse user input as a string into discrete command components.""" def __init__(self, terminators: Optional[Iterable[str]] = None, multiline_commands: Optional[Iterable[str]] = None, @@ -253,9 +218,7 @@ class StatementParser: """Initialize an instance of StatementParser. The following will get converted to an immutable tuple before storing internally: - * terminators - * multiline commands - * shortcuts + terminators, multiline commands, and shortcuts. :param terminators: iterable containing strings which should terminate commands :param multiline_commands: iterable containing the names of commands that accept multiline input @@ -321,13 +284,16 @@ class StatementParser: or termination characters. They also cannot start with a shortcut. - If word is not a valid command, return False and error text - This string is suitable for inclusion in an error message of your - choice: + :param word: the word to check as a command + :return: a tuple of a boolean and an error string + + If word is not a valid command, return ``False`` and an error string + suitable for inclusion in an error message of your choice:: - valid, errmsg = statement_parser.is_valid_command('>') - if not valid: - errmsg = "Alias {}".format(errmsg) + checkit = '>' + valid, errmsg = statement_parser.is_valid_command(checkit) + if not valid: + errmsg = "alias: {}".format(errmsg) """ valid = False @@ -359,11 +325,12 @@ class StatementParser: def tokenize(self, line: str) -> List[str]: """ - Lex a string into a list of tokens. Shortcuts and aliases are expanded and comments are removed + Lex a string into a list of tokens. Shortcuts and aliases are expanded and + comments are removed. :param line: the command line being lexed :return: A list of tokens - :raises ValueError if there are unclosed quotation marks. + :raises ValueError: if there are unclosed quotation marks """ # expand shortcuts and aliases @@ -382,13 +349,13 @@ class StatementParser: def parse(self, line: str) -> Statement: """ - Tokenize the input and parse it into a Statement object, stripping - comments, expanding aliases and shortcuts, and extracting output + Tokenize the input and parse it into a :class:`~cmd2.Statement` object, + stripping comments, expanding aliases and shortcuts, and extracting output redirection directives. :param line: the command line being parsed - :return: the created Statement - :raises ValueError if there are unclosed quotation marks + :return: a new :class:`~cmd2.Statement` object + :raises ValueError: if there are unclosed quotation marks """ # handle the special case/hardcoded terminator of a blank line @@ -526,7 +493,7 @@ class StatementParser: return statement def parse_command_only(self, rawinput: str) -> Statement: - """Partially parse input into a Statement object. + """Partially parse input into a :class:`~cmd2.Statement` object. The command is identified, and shortcuts and aliases are expanded. Multiline commands are identified, but terminators and output @@ -535,22 +502,21 @@ class StatementParser: This method is used by tab completion code and therefore must not generate an exception if there are unclosed quotes. - The `Statement` object returned by this method can at most contain values - in the following attributes: - - args - - raw - - command - - multiline_command + The :class:`~cmd2.Statement` object returned by this method can at most + contain values in the following attributes: + :attr:`~cmd2.Statement.args`, :attr:`~cmd2.Statement.raw`, + :attr:`~cmd2.Statement.command`, + :attr:`~cmd2.Statement.multiline_command` - `Statement.args` includes all output redirection clauses and command - terminators. + :attr:`~cmd2.Statement.args` will include all output redirection + clauses and command terminators. - Different from parse(), this method does not remove redundant whitespace - within args. However, it does ensure args has no leading or trailing - whitespace. + Different from :meth:`~cmd2.parsing.StatementParser.parse` this method + does not remove redundant whitespace within args. However, it does + ensure args has no leading or trailing whitespace. :param rawinput: the command line as entered by the user - :return: the created Statement + :return: a new :class:`~cmd2.Statement` object """ line = rawinput @@ -590,22 +556,26 @@ class StatementParser: def get_command_arg_list(self, command_name: str, to_parse: Union[Statement, str], preserve_quotes: bool) -> Tuple[Statement, List[str]]: """ - Called by the argument_list and argparse wrappers to retrieve just the arguments being - passed to their do_* methods as a list. + Convenience method used by the argument parsing decorators. + + Retrieves just the arguments being passed to their ``do_*`` methods as a list. :param command_name: name of the command being run - :param to_parse: what is being passed to the do_* method. It can be one of two types: - 1. An already parsed Statement - 2. An argument string in cases where a do_* method is explicitly called - e.g.: Calling do_help('alias create') would cause to_parse to be 'alias create' - - In this case, the string will be converted to a Statement and returned along - with the argument list. - - :param preserve_quotes: if True, then quotes will not be stripped from the arguments - :return: A tuple containing: - The Statement used to retrieve the arguments - The argument list + :param to_parse: what is being passed to the ``do_*`` method. It can be one of two types: + + 1. An already parsed :class:`~cmd2.Statement` + 2. An argument string in cases where a ``do_*`` method is + explicitly called. Calling ``do_help('alias create')`` would + cause ``to_parse`` to be 'alias create'. + + In this case, the string will be converted to a + :class:`~cmd2.Statement` and returned along with + the argument list. + + :param preserve_quotes: if ``True``, then quotes will not be stripped from + the arguments + :return: A tuple containing the :class:`~cmd2.Statement` and a list of + strings representing the arguments """ # Check if to_parse needs to be converted to a Statement if not isinstance(to_parse, Statement): @@ -669,14 +639,14 @@ class StatementParser: return command, args def split_on_punctuation(self, tokens: List[str]) -> List[str]: - """Further splits tokens from a command line using punctuation characters + """Further splits tokens from a command line using punctuation characters. Punctuation characters are treated as word breaks when they are in unquoted strings. Each run of punctuation characters is treated as a single token. :param tokens: the tokens as parsed by shlex - :return: the punctuated tokens + :return: a new list of tokens, further split using punctuation """ punctuation = [] punctuation.extend(self.terminators) diff --git a/cmd2/plugin.py b/cmd2/plugin.py index dc9ec297..83093ee1 100644 --- a/cmd2/plugin.py +++ b/cmd2/plugin.py @@ -6,22 +6,26 @@ import attr @attr.s class PostparsingData: + """Data class containing information passed to postparsing hook methods""" stop = attr.ib() statement = attr.ib() @attr.s class PrecommandData: + """Data class containing information passed to precommand hook methods""" statement = attr.ib() @attr.s class PostcommandData: + """Data class containing information passed to postcommand hook methods""" stop = attr.ib() statement = attr.ib() @attr.s class CommandFinalizationData: + """Data class containing information passed to command finalization hook methods""" stop = attr.ib() statement = attr.ib() diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index b7346d22..6624d7ad 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -1,7 +1,7 @@ # coding=utf-8 """ -Bridges calls made inside of a Python environment to the Cmd2 host app while maintaining a reasonable -degree of isolation between the two +Bridges calls made inside of a Python environment to the Cmd2 host app +while maintaining a reasonable degree of isolation between the two. """ import sys @@ -14,32 +14,38 @@ from .utils import namedtuple_with_defaults, StdSim class CommandResult(namedtuple_with_defaults('CommandResult', ['stdout', 'stderr', 'stop', 'data'])): """Encapsulates the results from a cmd2 app command - Named tuple attributes - ---------------------- - stdout: str - output captured from stdout while this command is executing - stderr: str - output captured from stderr while this command is executing. None if no error captured. - stop: bool - return value of onecmd_plus_hooks after it runs the given command line. - data - possible data populated by the command. + :stdout: str - output captured from stdout while this command is executing + :stderr: str - output captured from stderr while this command is executing + None if no error captured. + :stop: bool - return value of onecmd_plus_hooks after it runs the given + command line. + :data: possible data populated by the command. - Any combination of these fields can be used when developing a scripting API for a given command. - By default stdout, stderr, and stop will be captured for you. If there is additional command specific data, - then write that to cmd2's last_result member. That becomes the data member of this tuple. + Any combination of these fields can be used when developing a scripting API + for a given command. By default stdout, stderr, and stop will be captured + for you. If there is additional command specific data, then write that to + cmd2's last_result member. That becomes the data member of this tuple. - In some cases, the data member may contain everything needed for a command and storing stdout - and stderr might just be a duplication of data that wastes memory. In that case, the StdSim can - be told not to store output with its pause_storage member. While this member is True, any output - sent to StdSim won't be saved in its buffer. + In some cases, the data member may contain everything needed for a command + and storing stdout and stderr might just be a duplication of data that + wastes memory. In that case, the StdSim can be told not to store output + with its pause_storage member. While this member is True, any output sent + to StdSim won't be saved in its buffer. + + The code would look like this:: - The code would look like this: if isinstance(self.stdout, StdSim): self.stdout.pause_storage = True if isinstance(sys.stderr, StdSim): sys.stderr.pause_storage = True - See StdSim class in utils.py for more information + See :class:`~cmd2.utils.StdSim` for more information. + + .. note:: - NOTE: Named tuples are immutable. So the contents are there for access, not for modification. + Named tuples are immutable. The contents are there for access, + not for modification. """ def __bool__(self) -> bool: """Returns True if the command succeeded, otherwise False""" diff --git a/cmd2/utils.py b/cmd2/utils.py index 237a6d1e..f119999a 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -969,10 +969,22 @@ def get_styles_in_text(text: str) -> Dict[int, str]: def categorize(func: Union[Callable, Iterable[Callable]], category: str) -> None: """Categorize a function. - The help command output will group this function under the specified category heading + The help command output will group the passed function under the + specified category heading :param func: function or list of functions to categorize :param category: category to put it in + + :Example: + + >>> class MyApp(cmd2.Cmd): + >>> def do_echo(self, arglist): + >>> self.poutput(' '.join(arglist) + >>> + >>> utils.categorize(do_echo, "Text Processing") + + For an alternative approach to categorizing commands using a decorator, see + :func:`~cmd2.decorators.with_category` """ if isinstance(func, Iterable): for item in func: |