Coverage for /home/anselor/src/cmd2/cmd2/cmd2.py : 90%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
#!/usr/bin/env python # coding=utf-8
To use, simply import cmd2.Cmd instead of cmd.Cmd; use precisely as though you were using the standard library's cmd, while enjoying the extra features.
Searchable command history (commands: "history") Load commands from file, save to file, edit commands in file Multi-line commands Special-character shortcut commands (beyond cmd's "@" and "!") Settable environment parameters Parsing commands with `argparse` argument parsers (flags) Redirection to file with >, >>; input from file with < Easy transcript-based testing of applications (see examples/example.py) Bash-style ``select`` available
Note that redirection with > and | will only work if `self.poutput()` is used in place of `print`.
- Catherine Devlin, Jan 03 2008 - catherinedevlin.blogspot.com
Git repository on GitHub at https://github.com/python-cmd2/cmd2 """
# Set up readline rl_warning = "Readline features including tab completion have been disabled since no \n" \ "supported version of readline was found. To resolve this, install \n" \ "pyreadline on Windows or gnureadline on Mac.\n\n" sys.stderr.write(Fore.LIGHTYELLOW_EX + rl_warning + Fore.RESET) else:
# Save the original pyreadline display completion function since we need to override it and restore it # noinspection PyProtectedMember orig_pyreadline_display = readline.rl.mode._display_completions
# We need wcswidth to calculate display width of tab completions
# Get the readline lib so we can make changes to it
# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure # noinspection PyUnresolvedReferences
# Collection is a container that is sizable and iterable # It was introduced in Python 3.6. We will try to import it, otherwise use our implementation
# noinspection PyAbstractClass
# noinspection PyPep8Naming def __subclasshook__(cls, C): any("__iter__" in B.__dict__ for B in C.__mro__) and \ any("__contains__" in B.__dict__ for B in C.__mro__):
# Python 3.4 require contextlib2 for temporarily redirecting stderr and stdout from contextlib2 import redirect_stdout, redirect_stderr else:
# Detect whether IPython is installed to determine if the built-in "ipy" command should be included # noinspection PyUnresolvedReferences,PyPackageRequirements
# optional attribute, when tagged on a function, allows cmd2 to categorize commands
"""Categorize a function.
The help command output will group this function under the specified category heading
:param func: function to categorize :param category: category to put it in """ else:
"""Parse a quoted string into a list of arguments.""" # arguments are already a list, return the list we were passed else: # Use shlex to split the command line into a list of arguments based on shell rules # strip off outer quotes for convenience
"""A decorator to apply a category to a command function."""
"""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 using shlex.split().""" def cmd_wrapper(self, cmdline):
"""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 argparser: argparse.ArgumentParser - given instance of ArgumentParser :return: function that gets passed parsed args and a list of unknown args """ # noinspection PyProtectedMember def cmd_wrapper(instance, cmdline): else:
# argparser defaults the program name to sys.argv[0] # we want it to be the name of our command
# If the description has not been set, then use the method docstring if one exists
# Mark this function as having an argparse ArgumentParser
"""A decorator to alter a cmd2 method to populate its ``args`` argument by parsing arguments with the given instance of argparse.ArgumentParser.
:param argparser: argparse.ArgumentParser - given instance of ArgumentParser :return: function that gets passed parsed args """
# noinspection PyProtectedMember def cmd_wrapper(instance, cmdline): else:
# argparser defaults the program name to sys.argv[0] # we want it to be the name of our command
# If the description has not been set, then use the method docstring if one exists
# Mark this function as having an argparse ArgumentParser
# Can we access the clipboard? Should always be true on Windows and Mac, but only sometimes on Linux # noinspection PyUnresolvedReferences # Get the version of the pyperclip module as a float
# The extraneous output bug in pyperclip on Linux using xclip was fixed in more recent versions of pyperclip # Avoid extraneous output to stderr from xclip when clipboard is empty at cost of overwriting clipboard contents pyperclip.copy('') else: # Try getting the contents of the clipboard except PyperclipException: can_clip = False else:
""" Allows user of cmd2 to manually disable clipboard cut-and-paste functionality.""" global can_clip
"""Get the contents of the clipboard / paste buffer.
:return: contents of the clipboard """
"""Copy text to the clipboard / paste buffer.
:param txt: text to copy to the clipboard """
"""Custom exception class for use with the py command."""
"""Custom exception class for handling behavior when the user just presses <Enter>."""
"""An easy but powerful framework for writing line-oriented command interpreters.
Extends the Python Standard Library’s cmd package by adding a lot of useful features to the out of the box configuration.
Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes. """ # Attributes used to configure the StatementParser, best not to change these at runtime
# Attributes which are NOT dynamically settable at runtime
# Attributes which ARE dynamically settable at runtime editor = 'notepad' else: # Favor command-line editors first so we don't leave the terminal to edit
# To make an attribute settable with the "do_set" command, add it to this ... # This starts out as a dictionary but gets converted to an OrderedDict sorted alphabetically by key 'continuation_prompt': 'On 2nd+ line of input', 'debug': 'Show full error stack on error', 'echo': 'Echo command issued into output', 'editor': 'Program used by ``edit``', 'feedback_to_output': 'Include nonessentials in `|`, `>` results', 'locals_in_py': 'Allow access to your application in py via self', 'prompt': 'The prompt issued to solicit input', 'quiet': "Don't print nonessential feedback", 'timing': 'Report execution times'}
persistent_history_length=1000, startup_script=None, use_ipython=False, transcript_files=None): """An easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.
:param completekey: str - (optional) readline name of a completion key, default to Tab :param stdin: (optional) alternate input file object, if not specified, sys.stdin is used :param stdout: (optional) alternate output file object, if not specified, sys.stdout is used :param persistent_history_file: str - (optional) file path to load a persistent readline history from :param persistent_history_length: int - (optional) max number of lines which will be written to the history file :param startup_script: str - (optional) file path to a a script to load and execute at startup :param use_ipython: (optional) should the "ipy" command be included for an embedded IPython shell :param transcript_files: str - (optional) allows running transcript tests when allow_cli_args is False """ # If use_ipython is False, make sure the do_ipy() method doesn't exit
# If persistent readline history is enabled, then read history from file and register to write to file at exit persistent_history_file = os.path.expanduser(persistent_history_file) try: readline.read_history_file(persistent_history_file) # default history len is -1 (infinite), which may grow unruly readline.set_history_length(persistent_history_length) except FileNotFoundError: pass atexit.register(readline.write_history_file, persistent_history_file)
# Call super class constructor
# Commands to exclude from the help menu and tab completion
# Commands to exclude from the history command
allow_redirection=self.allow_redirection, terminators=self.terminators, multiline_commands=self.multiline_commands, aliases=self.aliases, shortcuts=self.shortcuts, )
# Used to enable the ability for a Python script to quit the application
# True if running inside a Python script or interactive console, False otherwise
# Stores results from the last command run to enable usage of results in a Python script or interactive console # Built-in commands don't make use of this. It is purely there for user-defined commands and convenience.
# Used to save state during a redirection
# Codes used for exit conditions
'cyan': {True: '\x1b[36m', False: '\x1b[39m'}, 'blue': {True: '\x1b[34m', False: '\x1b[39m'}, 'red': {True: '\x1b[31m', False: '\x1b[39m'}, 'magenta': {True: '\x1b[35m', False: '\x1b[39m'}, 'green': {True: '\x1b[32m', False: '\x1b[39m'}, 'underline': {True: '\x1b[4m', False: '\x1b[24m'}, 'yellow': {True: '\x1b[33m', False: '\x1b[39m'}}
# Used load command to store the current script dir as a LIFO queue to support _relative_load command
# Used when piping command output to a shell command
# Used by complete() for readline tab completion
# Used to keep track of whether we are redirecting or piping output
# If this string is non-empty, then this warning message will print if a broken pipe error occurs while printing
# If a startup script is provided, then add it in the queue to load startup_script = os.path.expanduser(startup_script) if os.path.exists(startup_script) and os.path.getsize(startup_script) > 0: self.cmdqueue.append('load {}'.format(startup_script))
############################################################################################################ # The following variables are used by tab-completion functions. They are reset each time complete() is run # using set_completion_defaults() and it is up to completer functions to set them before returning results. ############################################################################################################
# If true and a single match is returned to complete(), then a space will be appended # if the match appears at the end of the line
# If true and a single match is returned to complete(), then a closing quote # will be added if there is an unmatched opening quote
# Use this list if you are completing strings that contain a common delimiter and you only want to # display the final portion of the matches as the tab-completion suggestions. The full matches # still must be returned from your completer function. For an example, look at path_complete() # which uses this to show only the basename of paths as the suggestions. delimiter_complete() also # populates this list.
# ----- Methods related to presenting output to the user -----
def visible_prompt(self): """Read-only property to get the visible prompt with any ANSI escape codes stripped.
Used by transcript testing to make it easier and more reliable when users are doing things like coloring the prompt using ANSI color codes.
:return: str - prompt stripped of any ANSI escape codes """
# noinspection PyUnresolvedReferences
# Make sure settable parameters are sorted alphabetically by key
"""Convenient shortcut for self.stdout.write(); by default adds newline to end if not already present.
Also handles BrokenPipeError exceptions for when a commands's output has been piped to another process and that process terminates before the cmd2 command is finished executing.
:param msg: str - message to print to current stdout - anything convertible to a str with '{}'.format() is OK :param end: str - string appended after the end of the message if not already present, default a newline """ except BrokenPipeError: # This occurs if a command's output is being piped to another process and that process closes before the # command is finished. If you would like your application to print a warning message, then set the # broken_pipe_warning attribute to the message you want printed. if self.broken_pipe_warning: sys.stderr.write(self.broken_pipe_warning)
""" Print error message to sys.stderr and if debug is true, print an exception Traceback if one exists.
:param errmsg: str - error message to print out :param exception_type: str - (optional) type of exception which precipitated this error message :param traceback_war: bool - (optional) if True, print a message to let user know they can enable debug :return: """
else:
"""For printing nonessential feedback. Can be silenced with `quiet`. Inclusion in redirected output is controlled by `feedback_to_output`.""" else:
"""Print output using a pager if it would go off screen and stdout isn't currently being redirected.
Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when stdout or stdin are not a fully functional terminal.
:param msg: str - message to print to current stdout - anything convertible to a str with '{}'.format() is OK :param end: str - string appended after the end of the message if not already present, default a newline """
# Attempt to detect if we are not running within a fully functional terminal. # Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
if sys.platform.startswith('win') or os.environ.get('TERM') is not None: functional_terminal = True
# Don't attempt to use a pager that can block if redirecting or running a script (either text or Python) # Also only attempt to use a pager if actually running in a real fully functional terminal
if sys.platform.startswith('win'): pager_cmd = 'more' else: # Here is the meaning of the various flags we are using with the less command: # -S causes lines longer than the screen width to be chopped (truncated) rather than wrapped # -R causes ANSI "color" escape sequences to be output in raw form (i.e. colors are displayed) # -X disables sending the termcap initialization and deinitialization strings to the terminal # -F causes less to automatically exit if the entire file can be displayed on the first screen pager_cmd = 'less -SRXF' self.pipe_proc = subprocess.Popen(pager_cmd, shell=True, stdin=subprocess.PIPE) try: self.pipe_proc.stdin.write(msg_str.encode('utf-8', 'replace')) self.pipe_proc.stdin.close() except (IOError, KeyboardInterrupt): pass
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting search etc. inside less) while True: try: self.pipe_proc.wait() except KeyboardInterrupt: pass else: break self.pipe_proc = None else: except BrokenPipeError: # This occurs if a command's output is being piped to another process and that process closes before the # command is finished. If you would like your application to print a warning message, then set the # broken_pipe_warning attribute to the message you want printed. if self.broken_pipe_warning: sys.stderr.write(self.broken_pipe_warning)
"""Given a string (``val``), returns that string wrapped in UNIX-style special characters that turn on (and then off) text color and style. If the ``colors`` environment parameter is ``False``, or the application is running on Windows, will return ``val`` unchanged. ``color`` should be one of the supported strings (or styles): red/blue/green/cyan/magenta, bold, underline"""
# ----- Methods related to tab completion -----
""" Resets tab completion settings Needs to be called each time readline runs tab completion """
elif rl_type == RlType.PYREADLINE: readline.rl.mode._display_completions = self._display_matches_pyreadline
""" Used by tab completion functions to get all tokens through the one being completed :param line: str - the current input line with leading whitespace removed :param begidx: int - the beginning index of the prefix text :param endidx: int - the ending index of the prefix text :return: A 2 item tuple where the items are On Success tokens: list of unquoted tokens this is generally the list needed for tab completion functions raw_tokens: list of tokens with any quotes preserved this can be used to know if a token was quoted or is missing a closing quote
Both lists are guaranteed to have at least 1 item The last item in both lists is the token being tab completed
On Failure Both items are None """
# Parse the line into tokens # Use non-POSIX parsing to keep the quotes around the tokens
# If the cursor is at an empty token outside of a quoted string, # then that is the token being completed. Add it to the list. # ValueError can be caused by missing closing quote # Since we have no more quotes to try, something else # is causing the parsing error. Return None since # this means the line is malformed. return None, None
# Add a closing quote and try to parse again
# Since redirection is enabled, we need to treat redirection characters (|, <, >) # as word breaks when they are in unquoted strings. Go through each token # and further split them on these characters. Each run of redirect characters # is treated as a single token.
# Save tokens up to 1 character in length or quoted tokens. No need to parse these.
# Iterate over each character in this token
# Keep track of the token we are building
# Keep appending to cur_raw_token until we hit a redirect char else:
else:
# Keep appending to cur_raw_token until we hit something other than redirect_char else: break
# Save the current token
# Check if we've viewed all characters else:
# Save the unquoted tokens
# If the token being completed had an unclosed quote, we need # to remove the closing quote that was added in order for it # to match what was on the command line.
# noinspection PyUnusedLocal def basic_complete(text, line, begidx, endidx, match_against): """ Performs tab completion against a list
:param text: str - the string prefix we are attempting to match (all returned matches must begin with it) :param line: str - the current input line with leading whitespace removed :param begidx: int - the beginning index of the prefix text :param endidx: int - the ending index of the prefix text :param match_against: Collection - the list being matched against :return: List[str] - a list of possible tab completions """
""" Performs tab completion against a list but each match is split on a delimiter and only the portion of the match being tab completed is shown as the completion suggestions. This is useful if you match against strings that are hierarchical in nature and have a common delimiter.
An easy way to illustrate this concept is path completion since paths are just directories/files delimited by a slash. If you are tab completing items in /home/user you don't get the following as suggestions:
/home/user/file.txt /home/user/program.c /home/user/maps/ /home/user/cmd2.py
Instead you are shown:
file.txt program.c maps/ cmd2.py
For a large set of data, this can be visually more pleasing and easier to search.
Another example would be strings formatted with the following syntax: company::department::name In this case the delimiter would be :: and the user could easily narrow down what they are looking for if they were only shown suggestions in the category they are at in the string.
:param text: str - the string prefix we are attempting to match (all returned matches must begin with it) :param line: str - the current input line with leading whitespace removed :param begidx: int - the beginning index of the prefix text :param endidx: int - the ending index of the prefix text :param match_against: Collection - the list being matched against :param delimiter: str - what delimits each portion of the matches (ex: paths are delimited by a slash) :return: List[str] - a list of possible tab completions """
# Display only the portion of the match that's being completed based on delimiter
# Get the common beginning for the matches
# Calculate what portion of the match we are completing
# Get this portion for each match and store them in self.display_matches
display_token = delimiter
""" Tab completes based on a particular flag preceding the token being completed :param text: str - the string prefix we are attempting to match (all returned matches must begin with it) :param line: str - the current input line with leading whitespace removed :param begidx: int - the beginning index of the prefix text :param endidx: int - the ending index of the prefix text :param flag_dict: dict - dictionary whose structure is the following: keys - flags (ex: -c, --create) that result in tab completion for the next argument in the command line values - there are two types of values 1. iterable list of strings to match against (dictionaries, lists, etc.) 2. function that performs tab completion (ex: path_complete) :param all_else: Collection or function - an optional parameter for tab completing any token that isn't preceded by a flag in flag_dict :return: List[str] - a list of possible tab completions """ # Get all tokens through the one being completed return []
# Must have at least 2 args for a flag to precede the token being completed
# Perform tab completion using a Collection
# Perform tab completion using a function
""" Tab completes based on a fixed position in the input string :param text: str - the string prefix we are attempting to match (all returned matches must begin with it) :param line: str - the current input line with leading whitespace removed :param begidx: int - the beginning index of the prefix text :param endidx: int - the ending index of the prefix text :param index_dict: dict - dictionary whose structure is the following: keys - 0-based token indexes into command line that determine which tokens perform tab completion values - there are two types of values 1. iterable list of strings to match against (dictionaries, lists, etc.) 2. function that performs tab completion (ex: path_complete) :param all_else: Collection or function - an optional parameter for tab completing any token that isn't at an index in index_dict :return: List[str] - a list of possible tab completions """ # Get all tokens through the one being completed return []
# Get the index of the token being completed
# Check if token is at an index in the dictionary else:
# Perform tab completion using a Collection
# Perform tab completion using a function
# noinspection PyUnusedLocal """Performs completion of local file system paths
:param text: str - the string prefix we are attempting to match (all returned matches must begin with it) :param line: str - the current input line with leading whitespace removed :param begidx: int - the beginning index of the prefix text :param endidx: int - the ending index of the prefix text :param dir_exe_only: bool - only return directories and executables, not non-executable files :param dir_only: bool - only return directories :return: List[str] - a list of possible tab completions """
# Used to complete ~ and ~user strings
# We are returning ~user strings that resolve to directories, # so don't append a space or quote in the case of a single result.
# Windows lacks the pwd module so we can't get a list of users. # Instead we will add a slash once the user enters text that # resolves to an existing home directory. expanded_path = os.path.expanduser(text) if os.path.isdir(expanded_path): users.append(text + os.path.sep) else:
# Iterate through a list of users from the password database
# Check if the user has an existing home dir
# Add a ~ to the user to match against text
# Determine if a trailing separator should be appended to directory completions
# Used to replace cwd in the final results
# Used to replace expanded user path in final result
# If the search text is blank, then search in the CWD for * else: # Purposely don't match any path containing wildcards - what we are doing is complicated enough!
# Start the search string
# Handle tilde expansion and completion
# If there is no slash, then the user is still completing the user after the tilde
# Otherwise expand the user dir else:
# Get what we need to restore the original tilde path later
# If the search text does not have a directory, then use the cwd search_str = os.path.join(os.getcwd(), search_str) cwd_added = True
# Find all matching path completions
# Filter based on type matches = [c for c in matches if os.path.isdir(c) or os.access(c, os.X_OK)]
# Don't append a space or closing quote to directory
# Build display_matches and add a slash to directories
# Display only the basename of this path in the tab-completion suggestions
# Add a separator after directories if the next character isn't already a separator
# Remove cwd if it was added to match the text readline expects
# Restore the tilde string if we expanded one to match the text readline expects
def get_exes_in_path(starts_with): """ Returns names of executables in a user's path :param starts_with: str - what the exes should start with. leave blank for all exes in path. :return: List[str] - a list of matching exe names """ # Purposely don't match any executable containing wildcards
# Get a list of every directory in the PATH environment variable and ignore symbolic links
# Use a set to store exe names since there can be duplicates
# Find every executable file in the user's path that matches the pattern
"""Performs completion of executables either in a user's path or a given path :param text: str - the string prefix we are attempting to match (all returned matches must begin with it) :param line: str - the current input line with leading whitespace removed :param begidx: int - the beginning index of the prefix text :param endidx: int - the ending index of the prefix text :param complete_blank: bool - If True, then a blank will complete all shell commands in a user's path If False, then no completion is performed Defaults to False to match Bash shell behavior :return: List[str] - a list of possible tab completions """ # Don't tab complete anything if no shell command has been started
# If there are no path characters in the search text, then do shell command completion in the user's path
# Otherwise look for executables in the given path else: return self.path_complete(text, line, begidx, endidx, dir_exe_only=True)
""" Called by complete() as the first tab completion function for all commands It determines if it should tab complete for redirection (|, <, >, >>) or use the completer function for the current command
:param text: str - the string prefix we are attempting to match (all returned matches must begin with it) :param line: str - the current input line with leading whitespace removed :param begidx: int - the beginning index of the prefix text :param endidx: int - the ending index of the prefix text :param compfunc: Callable - the completer function for the current command this will be called if we aren't completing for redirection :return: List[str] - a list of possible tab completions """
# Get all tokens through the one being completed. We want the raw tokens # so we can tell if redirection strings are quoted and ignore them. return []
# Check if there are redirection strings prior to the token being completed
has_redirection = True
if cur_token == constants.REDIRECTION_PIPE: seen_pipe = True
# Get token prior to the one being completed
# If a pipe is right before the token being completed, complete a shell command as the piped process return self.shell_cmd_complete(text, line, begidx, endidx)
# Otherwise do path completion either as files to redirectors or arguments to the piped process return self.path_complete(text, line, begidx, endidx)
# If there were redirection strings anywhere on the command line, then we # are no longer tab completing for the current command return []
# Call the command's completer function
def _pad_matches_to_display(matches_to_display): # pragma: no cover """ Adds padding to the matches being displayed as tab completion suggestions. The default padding of readline/pyreadine is small and not visually appealing especially if matches have spaces. It appears very squished together.
:param matches_to_display: the matches being padded :return: the padded matches and length of padding that was added """ if rl_type == RlType.GNU: # Add 2 to the padding of 2 that readline uses for a total of 4. padding = 2 * ' '
elif rl_type == RlType.PYREADLINE: # Add 3 to the padding of 1 that pyreadline uses for a total of 4. padding = 3 * ' '
else: return matches_to_display, 0
return [cur_match + padding for cur_match in matches_to_display], len(padding)
def _display_matches_gnu_readline(self, substitution, matches, longest_match_length): # pragma: no cover """ Prints a match list using GNU readline's rl_display_match_list() This exists to print self.display_matches if it has data. Otherwise matches prints.
:param substitution: str - the substitution written to the command line :param matches: list[str] - the tab completion matches to display :param longest_match_length: int - longest printed length of the matches """ if rl_type == RlType.GNU:
# Check if we should show display_matches if self.display_matches: matches_to_display = self.display_matches
# Recalculate longest_match_length for display_matches longest_match_length = 0
for cur_match in matches_to_display: cur_length = wcswidth(cur_match) if cur_length > longest_match_length: longest_match_length = cur_length else: matches_to_display = matches
# Add padding for visual appeal matches_to_display, padding_length = self._pad_matches_to_display(matches_to_display) longest_match_length += padding_length
# We will use readline's display function (rl_display_match_list()), so we # need to encode our string as bytes to place in a C array. encoded_substitution = bytes(substitution, encoding='utf-8') encoded_matches = [bytes(cur_match, encoding='utf-8') for cur_match in matches_to_display]
# rl_display_match_list() expects matches to be in argv format where # substitution is the first element, followed by the matches, and then a NULL. # noinspection PyCallingNonCallable,PyTypeChecker strings_array = (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()
# Copy in the encoded strings and add a NULL to the end strings_array[0] = encoded_substitution strings_array[1:-1] = encoded_matches strings_array[-1] = None
# Call readline's display function # rl_display_match_list(strings_array, number of completion matches, longest match length) readline_lib.rl_display_match_list(strings_array, len(encoded_matches), longest_match_length)
# Redraw prompt and input line rl_force_redisplay()
def _display_matches_pyreadline(self, matches): # pragma: no cover """ Prints a match list using pyreadline's _display_completions() This exists to print self.display_matches if it has data. Otherwise matches prints.
:param matches: list[str] - the tab completion matches to display """ if rl_type == RlType.PYREADLINE:
# Check if we should show display_matches if self.display_matches: matches_to_display = self.display_matches else: matches_to_display = matches
# Add padding for visual appeal matches_to_display, _ = self._pad_matches_to_display(matches_to_display)
# Display matches using actual display function. This also redraws the prompt and line. orig_pyreadline_display(matches_to_display)
# ----- Methods which override stuff in cmd -----
"""Override of command method which returns the next possible completion for 'text'.
If a command has not been entered, then complete against command list. Otherwise try to call complete_<command> to get list of completions.
This method gets called directly by readline because it is set as the tab-completion function.
This completer function is called as complete(text, state), for state in 0, 1, 2, …, until it returns a non-string value. It should return the next possible completion starting with text.
:param text: str - the current word that user is typing :param state: int - non-negative integer """
# lstrip the original line
# Calculate new indexes for the stripped line. If the cursor is at a position before the end of a # line of spaces, then the following math could result in negative indexes. Enforce a max of 0.
# Shortcuts are not word break characters when tab completing. Therefore shortcuts become part # of the text variable if there isn't a word break, like a space, after it. We need to remove it # from text and update the indexes. This only applies if we are at the the beginning of the line. # Save the shortcut to restore later
# Adjust text and where it begins
# If begidx is greater than 0, then we are no longer completing the command
# Parse the command line
# We overwrote line with a properly formatted but fully stripped version # Restore the end spaces since line is only supposed to be lstripped when # passed to completer functions according to Python docs
# Fix the index values if expanded_line has a different size than line
# Overwrite line to pass into completers
# Get all tokens through the one being completed
# Either had a parsing error or are trying to complete the command token # The latter can happen if " or ' was entered as the command self.completion_matches = [] return None
# Text we need to remove from completions later
# Get the token being completed with any opening quote preserved
# Check if the token being completed has an opening quote
# Since the token is still being completed, we know the opening quote is unclosed
# readline still performs word breaks after a quote. Therefore something like quoted search # text with a space would have resulted in begidx pointing to the middle of the token we # we want to complete. Figure out where that token actually begins and save the beginning # portion of it that was not part of the text readline gave us. We will remove it from the # completions later since readline expects them to start with the original text.
# Adjust text and where it begins so the completer routines # get unbroken search text to complete on.
# Check if a valid command was entered # Get the completer function for this command # There's no completer function, next see if the command uses argparser # Command uses argparser, switch to the default argparse completer except AttributeError: compfunc = self.completedefault
# A valid command was not entered else: # Check if this command should be run as a shell command else:
# Attempt tab completion for redirection first, and if that isn't occurring, # call the completer function for the current command
# Eliminate duplicates
# Check if display_matches has been used. If so, then matches # on delimited strings like paths was done. else:
# Since self.display_matches is empty, set it to self.completion_matches # before we alter them. That way the suggestions will reflect how we parsed # the token being completed and not how readline did.
# Check if we need to add an opening quote
# This is the tab completion text that will appear on the command line.
# Check if any portion of the display matches appears in the tab completion
# For delimited matches, we check what appears before the display # matches (common_prefix) as well as the display matches themselves.
# If there is a tab completion and any match has a space, then add an opening quote
# Figure out what kind of quote to add and save it as the unclosed_quote unclosed_quote = "'" else:
# Check if we need to remove text from the beginning of tab completions [m.replace(text_to_remove, '', 1) for m in self.completion_matches]
# Check if we need to restore a shortcut in the tab completions # so it doesn't get erased from the command line [shortcut_to_restore + match for match in self.completion_matches]
else: # Complete token against aliases and command names
# Handle single result
# Add a closing quote if needed and allowed
# If we are at the end of the line, then add a space if allowed
# Otherwise sort matches
argparser: argparse.ArgumentParser) -> List[str]: """Default completion function for argparse commands."""
""" Returns a list of all commands """
""" Returns a list of commands that have not been hidden """
# Remove the hidden commands
""" Returns a list of help topics """
""" Override of parent class method to handle tab completing subcommands and not showing hidden commands Returns a list of possible tab completions """
# The command is the token at index 1 in the command line
# The subcommand is the token at index 2 in the command line
# Get all tokens through the one being completed return []
# Get the index of the token being completed
# Check if we are completing a command or help topic
# Complete token against topics and visible commands
# check if the command uses argparser except AttributeError: pass
# noinspection PyUnusedLocal """Signal handler for SIGINTs which typically come from Ctrl-C events.
If you need custom SIGINT behavior, then override this function.
:param signum: int - signal number :param frame """
# Save copy of pipe_proc since it could theoretically change while this is running pipe_proc = self.pipe_proc
if pipe_proc is not None: pipe_proc.terminate()
# Re-raise a KeyboardInterrupt so other parts of the code can catch it raise KeyboardInterrupt("Got a keyboard interrupt")
""""Hook method executed once when the cmdloop() method is called."""
# Register a default SIGINT signal handler for Ctrl+C
"""Hook method executed just before the command is processed by ``onecmd()`` and after adding it to the history.
:param statement: Statement - subclass of str which also contains the parsed input :return: Statement - a potentially modified version of the input Statement object """
# ----- Methods which are cmd2-specific lifecycle hooks which are not present in cmd -----
# noinspection PyMethodMayBeStatic """Hook method executed just before the command line is interpreted, but after the input prompt is generated.
:param raw: str - raw command line input :return: str - potentially modified raw command line input """ return raw
# noinspection PyMethodMayBeStatic """Hook that runs immediately after parsing the user input.
:param statement: Statement object populated by parsing :return: Statement - potentially modified Statement object """ return statement
# noinspection PyMethodMayBeStatic """This runs after parsing the command-line, but before anything else; even before adding cmd to history.
NOTE: This runs before precmd() and prior to any potential output redirection or piping.
If you wish to fatally fail this command and exit the application entirely, set stop = True.
If you wish to just fail this command you can do so by raising an exception:
- raise EmptyStatement - will silently fail and do nothing - raise <AnyOtherException> - will fail and print an error message
:param statement: - the parsed command-line statement as a Statement object :return: (bool, statement) - (stop, statement) containing a potentially modified version of the statement object """
# noinspection PyMethodMayBeStatic """This runs after everything else, including after postcmd().
It even runs when an empty line is entered. Thus, if you need to do something like update the prompt due to notifications from a background thread, then this is the method you want to override to do it.
:param stop: bool - True implies the entire application should exit. :return: bool - True implies the entire application should exit. """ # Fix those annoying problems that occur with terminal programs like "less" when you pipe to them
"""Parse the line into a command name and a string containing the arguments.
NOTE: This is an override of a parent class method. It is only used by other parent class methods.
Different from the parent class method, this ignores self.identchars.
:param line: str - line read by readline :return: (str, str, str) - tuple containing (command, args, line) """
"""Top-level function called by cmdloop() to handle parsing a line and running the command and all of its hooks.
:param line: str - line of text read from input :return: bool - True if cmdloop() should exit, False otherwise """
finally: # If shlex.split failed on syntax, let user know whats going on finally:
"""Convenience method to run multiple commands by onecmd_plus_hooks.
This method adds the given cmds to the command queue and processes the queue until completion or an error causes it to abort. Scripts that are loaded will have their commands added to the queue. Scripts may even load other scripts recursively. This means, however, that you should not use this method if there is a running cmdloop or some other event-loop. This method is only intended to be used in "one-off" scenarios.
NOTE: You may need this method even if you only have one command. If that command is a load, then you will need this command to fully process all the subsequent commands that are loaded from the script file. This is an improvement over onecmd_plus_hooks, which expects to be used inside of a command loop which does the processing of loaded commands.
Example: cmd_obj.runcmds_plus_hooks(['load myscript.txt'])
:param cmds: list - Command strings suitable for onecmd_plus_hooks. :return: bool - True implies the entire application should exit.
""" self.poutput('{}{}'.format(self.prompt, line))
finally: # Clear out the command queue and script directory stack, just in # case we hit an error and they were not completed. # NOTE: placing this return here inside the finally block will # swallow exceptions. This is consistent with what is done in # onecmd_plus_hooks and _cmdloop, although it may not be # necessary/desired here.
"""Keep accepting lines of input until the command is complete.
There is some pretty hacky code here to handle some quirks of self.pseudo_raw_input(). It returns a literal 'eof' if the input pipe runs out. We can't refactor it because we need to retain backwards compatibility with the standard library version of cmd. """ # they entered either a blank line, or we hit an EOF # for some other reason. Turn the literal 'eof' # into a blank line, which serves as a command # terminator newline = '\n' self.poutput(newline) except KeyboardInterrupt: self.poutput('^C') statement = self.statement_parser.parse('') break else: newline = self.pseudo_raw_input(self.continuation_prompt) if newline == 'eof': # they entered either a blank line, or we hit an EOF # for some other reason. Turn the literal 'eof' # into a blank line, which serves as a command # terminator newline = '\n' self.poutput(newline) line = '{}\n{}'.format(statement.raw, newline)
"""Handles output redirection for >, >>, and |.
:param statement: Statement - a parsed statement from the user """
# Create a pipe with read and write sides
# Open each side of the pipe and set stdout accordingly # noinspection PyTypeChecker # noinspection PyTypeChecker
# We want Popen to raise an exception if it fails to open the process. Thus we don't set shell to True. # Restore stdout to what it was and close the pipe
# Re-raise the exception # going to a file # statement.output can only contain # REDIRECTION_APPEND or REDIRECTION_OUTPUT else: # going to a paste buffer
"""Handles restoring state after output redirection as well as the actual pipe operation if present.
:param statement: Statement object which contains the parsed input from the user """ # If we have redirected output to a file or the clipboard or piped it to a shell command, then restore state # If we redirected output to the clipboard
# Close the file or pipe that stdout was redirected to except BrokenPipeError: pass finally: # Restore self.stdout
# If we were piping output to a shell command, then close the subprocess the shell command was running in
# Restore sys.stdout if need be
"""Gets the method name associated with a given command.
:param arg: str - command to look up method name which implements it :return: str - method name which implements the given command """
""" This executes the actual do_* method for a command.
If the command provided doesn't exist, then it executes _default() instead.
:param statement: Command - a parsed command from the input stream :return: bool - a flag indicating whether the interpretation of commands should stop """
# Since we have a valid command store it in the history
except AttributeError: return self.default(statement)
"""Executed when the command given isn't a recognized command implemented by a do_* method.
:param statement: Statement object with parsed input :return: """ # If os.system() succeeded, then don't print warning about unknown command
# Print out a message stating this is an unknown command
"""Overcome bug in GNU Readline in relation to calculation of prompt length in presence of ANSI escape codes.
:param prompt: str - original prompt :param start: str - start code to tell GNU Readline about beginning of invisible characters :param end: str - end code to tell GNU Readline about end of invisible characters :return: str - prompt safe to pass to GNU Readline """ # Windows terminals don't use ANSI escape codes and Windows readline isn't based on GNU Readline return prompt
else:
""" began life as a copy of cmd's cmdloop; like raw_input but
- accounts for changed stdin, stdout - if input is a pipe (instead of a tty), look at self.echo to decide whether to print the prompt and the input """
# Deal with the vagaries of readline and ANSI escape codes
else: else: # on a tty, print the prompt first, then read the line else: # we are reading from a pipe, read the line to see if there is # anything there, if so, then decide whether to print the # prompt or not # we read something, output the prompt and the something else:
"""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.
This serves the same role as cmd.cmdloop().
:return: bool - True implies the entire application should exit. """ # An almost perfect copy from Cmd; however, the pseudo_raw_input portion # has been split out so that it can be called separately
# Set up readline for our tab completion needs # Set GNU readline's rl_basic_quote_characters to NULL so it won't automatically add a closing quote # We don't need to worry about setting rl_completion_suppress_quote since we never declared # rl_completer_quote_characters.
# Break words on whitespace and quotes when tab completing
# If redirection is allowed, then break words on those characters too
# Enable tab completion
# Run command out of cmdqueue if nonempty (populated by load command or commands at invocation)
else: # Otherwise, read a command from stdin else:
# Run the command along with all associated pre and post hooks finally:
# Restore what we changed in readline
elif rl_type == RlType.PYREADLINE: readline.rl.mode._display_completions = orig_pyreadline_display
def do_alias(self, arglist): """Define or display aliases
Usage: Usage: alias [name] | [<name> <value>] Where: name - name of the alias being looked up, added, or replaced value - what the alias will be resolved to (if adding or replacing) this can contain spaces and does not need to be quoted
Without arguments, 'alias' prints a list of all aliases in a reusable form which can be outputted to a startup_script to preserve aliases across sessions.
With one argument, 'alias' shows the value of the specified alias. Example: alias ls (Prints the value of the alias called 'ls' if it exists)
With two or more arguments, 'alias' creates or replaces an alias.
Example: alias ls !ls -lF
If you want to use redirection or pipes in the alias, then either quote the tokens with these characters or quote the entire alias value.
Examples: alias save_results print_results ">" out.txt alias save_results print_results "> out.txt" alias save_results "print_results > out.txt" """ # If no args were given, then print a list of current aliases
# The user is looking up an alias else:
# The user is creating an alias else:
# Validate the alias to ensure it doesn't include weird characters # like terminators, output redirection, or whitespace # Set the alias else:
""" Tab completion for alias """ alias_names = set(self.aliases.keys()) visible_commands = set(self.get_visible_commands())
index_dict = \ { 1: alias_names, 2: list(alias_names | visible_commands) } return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
def do_unalias(self, arglist): """Unsets aliases
Usage: Usage: unalias [-a] name [name ...] Where: name - name of the alias being unset
Options: -a remove all alias definitions """ self.do_help('unalias')
else: # Get rid of duplicates
else:
""" Tab completion for unalias """ return self.basic_complete(text, line, begidx, endidx, self.aliases)
def do_help(self, arglist): """List available commands with "help" or detailed help with "help cmd".""" else: # Getting help for a specific command # Check to see if this function was decorated with an argparse ArgumentParser # Function has an argparser, so get help based on all the arguments in case there are sub-commands
# Temporarily redirect all argparse output to both sys.stdout and sys.stderr to self.stdout else: # No special behavior needed, delegate to cmd base class do_help() else: # This could be a help topic cmd.Cmd.do_help(self, arglist[0])
"""Show a list of commands which help can be displayed for. """ # Get a sorted list of help topics
# Get a sorted list of visible command names
else: else:
# No categories found, fall back to standard behavior else: # Categories found, Organize all commands by category
"""Customized version of print_topics that can switch between verbose or traditional output""" else: # measure the commands # add a 4-space pad
# Try to get the documentation string # first see if there's a help function implemented # Couldn't find a help function # Now see if help_summary has been set # Last, try to directly access the function's doc-string else: # we found the help function # try to redirect system stdout # save our internal stdout # redirect our internal stdout finally: # restore internal stdout
# Attempt to locate the first documentation block else:
col_width=widest, doc=doc_line))
"""Lists shortcuts (aliases) available."""
"""Called when <Ctrl>-D is pressed.""" # End of script should not exit app, but <Ctrl>-D should.
"""Exits this application."""
"""Presents a numbered menu to the user. Modelled after the bash shell's SELECT. Returns the item chosen.
Argument ``opts`` can be:
| a single string -> will be split into one-word options | a list of strings -> will be offered as options | a list of tuples -> interpreted as (value, text), so that the return value can differ from the text advertised to the user """ else:
readline.remove_history_item(hlen - 1)
len(fulloptions)))
"""Get a summary report of read-only settings which the user cannot modify at runtime.
:return: str - summary report of read-only settings which the user cannot modify at runtime """ Commands may be terminated with: {} Arguments at invocation allowed: {} Output redirection and pipes allowed: {}"""
else:
# If user has requested to see all settings, also show read-only settings else:
def do_set(self, args): """Sets a settable parameter or shows current settings of parameters.
Accepts abbreviated parameter names so long as there is no ambiguity. Call without arguments for a list of settable parameters with their values. """ else: val = val[1:-1] else: onchange_hook(old=current_val, new=val)
"""Execute a command as if at the OS prompt.
Usage: shell <command> [arguments]"""
# Use non-POSIX parsing to keep the quotes around the tokens except ValueError as err: self.perror(err, traceback_war=False) return
# Support expanding ~ in quoted paths # Check if the token is quoted. Since shlex.split() passed, there isn't # an unclosed quote, so we only need to check the first character. tokens[index] = utils.strip_quotes(tokens[index])
# Restore the quotes tokens[index] = first_char + tokens[index] + first_char
"""Handles tab completion of executable commands and local file system paths for the shell command
:param text: str - the string prefix we are attempting to match (all returned matches must begin with it) :param line: str - the current input line with leading whitespace removed :param begidx: int - the beginning index of the prefix text :param endidx: int - the ending index of the prefix text :return: List[str] - a list of possible tab completions """
# noinspection PyBroadException """ Invoke python command, shell, or script
py <command>: Executes a Python command. py: Enters interactive Python mode. End with ``Ctrl-D`` (Unix) / ``Ctrl-Z`` (Windows), ``quit()``, '`exit()``. Non-python commands can be issued with ``pyscript_name("your command")``. Run python code from external script files with ``run("script.py")`` """
# Support the run command even if called prior to invoking an interactive interpreter """Run a Python script file in the interactive console.
:param filename: str - filename of *.py script file to run """
"""Run a cmd2.Cmd command from a Python script or the interactive Python console.
:param cmd_plus_args: str - command line including command and arguments to run :return: bool - True if cmdloop() should exit once leaving the interactive Python console """ return self.onecmd_plus_hooks(cmd_plus_args + '\n')
self.pystate['self'] = self
else: # noinspection PyShadowingBuiltins """Function callable from the interactive Python console to exit that environment""" raise EmbeddedConsoleExit
(sys.version, sys.platform, cprt, self.__class__.__name__, docstr)) except EmbeddedConsoleExit: pass except Exception: pass finally:
def do_pyscript(self, arglist): """\nRuns a python script file inside the console
Usage: pyscript <script_path> [script_arguments]
Console commands can be executed inside this script with cmd("your command") However, you cannot run nested "py" or "pyscript" commands from within this script Paths or arguments that contain spaces must be enclosed in quotes """
# Get the absolute path of the script
# Save current command line arguments
# Overwrite sys.argv to allow the script to take command line arguments
# Run the script - use repr formatting to escape things which need to be escaped to prevent issues on Windows
# Restore command line arguments to original state
# Enable tab-completion for pyscript command index_dict = {1: self.path_complete} return self.index_based_complete(text, line, begidx, endidx, index_dict)
# Only include the do_ipy() method if IPython is available on the system # noinspection PyMethodMayBeStatic,PyUnusedLocal def do_ipy(self, arg): """Enters an interactive IPython shell.
Run python code from external files with ``run filename.py`` End with ``Ctrl-D`` (Unix) / ``Ctrl-Z`` (Windows), ``quit()``, '`exit()``. """ from .pyscript_bridge import PyscriptBridge bridge = PyscriptBridge(self)
if self.locals_in_py: def load_ipy(self, app): banner = 'Entering an embedded IPython shell type quit() or <Ctrl>-d to exit ...' exit_msg = 'Leaving IPython, back to {}'.format(sys.argv[0]) embed(banner1=banner, exit_msg=exit_msg) load_ipy(self, bridge) else: def load_ipy(app): banner = 'Entering an embedded IPython shell type quit() or <Ctrl>-d to exit ...' exit_msg = 'Leaving IPython, back to {}'.format(sys.argv[0]) embed(banner1=banner, exit_msg=exit_msg) load_ipy(bridge)
help='edit and then run selected history items') a one history item by number a..b, a:b, a:, ..b items by indices (inclusive) [string] items containing string /regex/ items matching regular expression"""
def do_history(self, args): """View, run, edit, and save previously entered commands.""" # If an argument was supplied, then retrieve partial contents of the history # If a character indicating a slice is present, retrieve # a slice of the history # Get a slice of history else: # Get item(s) from history by index or string search else: # If no arg given, then retrieve the entire history # Get a copy of the history so it doesn't get mutated while we are using it
traceback_war=False) else: except Exception: raise finally: except Exception as e: self.perror('Saving {!r} - {}'.format(args.output_file, e), traceback_war=False) else: # Display the history items retrieved else:
"""Generate a transcript file from a given history of commands.""" # Save the current echo state, and turn it off. We inject commands into the # output using a different mechanism
# Redirect stdout to the transcript file
# The problem with supporting regular expressions in transcripts # is that they shouldn't be processed in the command, just the output. # In addition, when we generate a transcript, any slashes in the output # are not really intended to indicate regular expressions, so they should # be escaped. # # We have to jump through some hoops here in order to catch the commands # separately from the output and escape the slashes in the output. # build the command, complete with prompts. When we replay # the transcript, we look for the prompts to separate # the command from the output else: # create a new string buffer and set it to stdout to catch the output # of the command # then run the command and let the output go into our buffer # rewind the buffer to the beginning # get the output out of the buffer # and add the regex-escaped output to the transcript
# Restore stdout to its original state # Set echo back to its original state
# finally, we can write the transcript out to the file
# and let the user know what we did else: plural = 'command and its output'
def do_edit(self, arglist): """Edit a file in a text editor.
Usage: edit [file_path] Where: * file_path - path to a file to open in editor
The editor used is determined by the ``editor`` settable parameter. "set editor (program-name)" to change or set the EDITOR environment variable. """ else:
# Enable tab-completion for edit command index_dict = {1: self.path_complete} return self.index_based_complete(text, line, begidx, endidx, index_dict)
def _current_script_dir(self): """Accessor to get the current script directory from the _script_dir LIFO queue.""" else:
def do__relative_load(self, arglist): """Runs commands in script file that is encoded as either ASCII or UTF-8 text.
Usage: _relative_load <file_path>
optional argument: file_path a file path pointing to a script
Script should contain one command per line, just like command would be typed in console.
If this is called from within an already-running script, the filename will be interpreted relative to the already-running script's directory.
NOTE: This command is intended to only be used within text file scripts. """ # If arg is None or arg is an empty string this is an error
# NOTE: Relative path is an absolute path, it is just relative to the current script directory
"""Handles cleanup when a script has finished executing."""
def do_load(self, arglist): """Runs commands in script file that is encoded as either ASCII or UTF-8 text.
Usage: load <file_path>
* file_path - a file path pointing to a script
Script should contain one command per line, just like command would be typed in console. """ # If arg is None or arg is an empty string this is an error
# Make sure expanded_path points to a file
# Make sure the file is not empty
# Make sure the file is ASCII or UTF-8 encoded text
# Read all lines of the script and insert into the head of the # command queue. Add an "end of script (eos)" command to cleanup the # self._script_dir list when done. except IOError as e: # pragma: no cover self.perror('Problem accessing script from {}:\n{}'.format(expanded_path, e)) return
# Enable tab-completion for load command index_dict = {1: self.path_complete} return self.index_based_complete(text, line, begidx, endidx, index_dict)
def is_text_file(file_path): """ Returns if a file contains only ASCII or UTF-8 encoded text :param file_path: path to the file being checked """
# Check if the file is ASCII # Make sure the file has at least one line of text # noinspection PyUnusedLocal except IOError: # pragma: no cover pass # The file is not ASCII. Check if it is UTF-8. # Make sure the file has at least one line of text # noinspection PyUnusedLocal except IOError: # pragma: no cover pass # Not UTF-8
"""Runs transcript tests for provided file(s).
This is called when either -t is provided on the command line or the transcript_files argument is provided during construction of the cmd2.Cmd instance.
:param callargs: List[str] - list of transcript test file names """
"""This is an outer wrapper around _cmdloop() which deals with extra features provided by cmd2.
_cmdloop() provides the main loop equivalent to cmd.cmdloop(). This is a wrapper around that which deals with the following extra features provided by cmd2: - commands at invocation - transcript testing - intro banner
:param intro: str - if provided this overrides self.intro and serves as the intro banner printed once at start """ help='Test against transcript(s) in FILE (wildcards OK)')
# If transcript testing was called for, use other arguments as transcript files
# If commands were supplied at invocation, then add them to the command queue
# Always run the preloop first
# If transcript-based regression testing was requested, then do that instead of the main loop else: # If an intro was supplied in the method call, allow it to override the default
# Print the intro, if there is one, right after the preloop
# And then call _cmdloop() to enter the main loop
# Run the postloop() no matter what
"""Class used to represent an item in the History list.
Thin wrapper around str class which adds a custom format for printing. It also keeps track of its index in the list as well as a lowercase representation of itself for convenience/efficiency.
"""
# noinspection PyUnusedLocal
"""Represent a HistoryItem in a pretty fashion suitable for printing.
:return: str - pretty print string version of a HistoryItem """
""" A list of HistoryItems that knows how to respond to user requests. """
# noinspection PyMethodMayBeStatic
else:
"""Parses the input string search for a span pattern and if if found, returns a slice from the History list.
:param raw: str - string potentially containing a span of the forms a..b, a:b, a:, ..b :return: List[HistoryItem] - slice from the History list """
"""Append a HistoryItem to end of the History list
:param new: str - command line to convert to HistoryItem and add to the end of the History list """
"""Get an item or items from the History list using 1-based indexing.
:param getme: int or str - item(s) to get - either an integer index or string to search for :return: List[str] - list of HistoryItems matching the retrieval criteria """ else:
# noinspection PyUnresolvedReferences
"""Listcomp filter function for doing a regular expression search of History.
:param hi: HistoryItem :return: bool - True if search matches """ else: """Listcomp filter function for doing a case-insensitive string search of History.
:param hi: HistoryItem :return: bool - True if search matches """
"""Tries to force a new value into the same type as the current when trying to set the value for a parameter.
:param current: current value for the parameter, type varies :param new: str - new value :return: new value with same type as current, or the current value if there was an error casting """ else:
"""Class used to save and restore state during load and py commands as well as when redirecting output or pipes.""" """Use the instance attributes as a generic key-value store to copy instance attributes from outer object.
:param obj: instance of cmd2.Cmd derived class (your application instance) :param attribs: Tuple[str] - tuple of strings listing attributes of obj to save a copy of """
"""Create copies of attributes from self.obj inside this Statekeeper instance."""
"""Overwrite attributes in self.obj with the saved values stored in this Statekeeper instance."""
"""Instantiate an OutputTrap to divert/capture ALL stdout output. For use in transcript testing."""
"""Add text to the internal contents.
:param txt: str """
"""Read from the internal contents and then clear them out.
:return: str - text from the internal contents """
"""Subclass this, setting CmdApp, to make a unittest.TestCase class that will execute the commands in a transcript file and expect the results shown. See example.py"""
raise Exception("No test files found - nothing to test.")
# Trap stdout
# Scroll forward to where actual commands begin except StopIteration: finished = True break # Read the entirety of a multi-line command except StopIteration: raise (StopIteration, 'Transcript broke off while reading command beginning at line {} with\n{}'.format(line_num, command[0]) ) # Send the command into the application and capture the resulting output # TODO: Should we get the return value and act if stop == True? # Read the expected result from transcript message = '\nFile {}, line {}\nCommand was:\n{}\nExpected: (nothing)\nGot:\n{}\n'.format( fname, line_num, command, result) self.assert_(not (result.strip()), message) continue
# transform the expected text into a valid regular expression fname, line_num, command, expected, result)
"""parse the string with slashed regexes into a valid regex
Given a string like:
Match a 10 digit phone number: /\d{3}-\d{3}-\d{4}/
Turn it into a valid regular expression which matches the literal text of the string and the regular expression. We have to remove the slashes because they differentiate between plain text and a regular expression. Unless the slashes are escaped, in which case they are interpreted as plain text, or there is only one slash, which is treated as plain text also.
Check the tests in tests/test_transcript.py to see all the edge cases. """
# no more slashes, add the rest of the string and bail else: # there is a slash, add everything we have found so far # add stuff before the first slash as plain text # and go find the next one # add everything between the slashes (but not the slashes) # as a regular expression # and change where we start looking for slashed on the # turn through the loop else: # No closing slash, we have to add the first slash, # and the rest of the text
def _escaped_find(regex, s, start, in_regex): """ Find the next slash in {s} after {start} that is not preceded by a backslash.
If we find an escaped slash, add everything up to and including it to regex, updating {start}. {start} therefore serves two purposes, tells us where to start looking for the next thing, and also tells us where in {s} we have already added things to {regex}
{in_regex} specifies whether we are currently searching in a regex, we behave differently if we are or if we aren't. """
# no match, return to caller # slash at the beginning of the string, so it can't be # escaped. We found it. else: # check if the slash is preceeded by a backslash # it is. # add everything up to the backslash as a # regular expression # skip the backslash, and add the slash else: # add everything up to the backslash as escaped # plain text # and then add the slash as escaped # plain text # update start to show we have handled everything # before it # and continue to look else: # slash is not escaped, this is what we are looking for
# Restore stdout
"""Wrapper around namedtuple which lets you treat the last value as optional.
:param typename: str - type name for the Named tuple :param field_names: List[str] or space-separated string of field names :param default_values: (optional) 2-element tuple containing the default values for last 2 parameters in named tuple Defaults to an empty string for both of them :return: namedtuple type """ # noinspection PyUnresolvedReferences
"""Derive a class to store results from a named tuple so we can tweak dunder methods for convenience.
This is provided as a convenience and an example for one possible way for end users to store results in the self._last_result attribute of cmd2.Cmd class instances. See the "python_scripting.py" example for how it can be used to enable conditional control flow.
Named tuple attributes ---------------------- out - this is intended to store normal output data from the command and can be of any type that makes sense err: str - (optional) this is intended to store an error message and it being non-empty indicates there was an error Defaults to an empty string war: str - (optional) this is intended to store a warning message which isn't quite an error, but of note Defaults to an empty string.
NOTE: Named tuples are immutable. So the contents are there for access, not for modification. """ """If err is an empty string, treat the result as a success; otherwise treat it as a failure."""
|