diff options
author | Eric Lin <anselor@gmail.com> | 2018-04-27 22:57:47 -0400 |
---|---|---|
committer | Eric Lin <anselor@gmail.com> | 2018-04-27 22:57:47 -0400 |
commit | 452c396c1b3b417a1e085d5b4ab192bbc13d34b8 (patch) | |
tree | c1dce51fc4253a664c474bcff6e9b3801f1cf977 | |
parent | ae86103f6b8acf7765804382237564356f095b74 (diff) | |
parent | 1306eebade58d7ffe5d0ab4008006b7fb3501b54 (diff) | |
download | cmd2-git-452c396c1b3b417a1e085d5b4ab192bbc13d34b8.tar.gz |
Merge remote-tracking branch 'origin/master' into bash_completion
Updated argcomplete_bridge to use new constants/utils.
-rw-r--r-- | cmd2/__init__.py | 18 | ||||
-rw-r--r-- | cmd2/argcomplete_bridge.py | 7 | ||||
-rwxr-xr-x | cmd2/cmd2.py | 59 | ||||
-rw-r--r-- | cmd2/constants.py | 12 | ||||
-rw-r--r-- | cmd2/utils.py | 27 | ||||
-rw-r--r-- | tests/test_completion.py | 11 |
6 files changed, 72 insertions, 62 deletions
diff --git a/cmd2/__init__.py b/cmd2/__init__.py index cea82e57..d3d193cf 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -1,22 +1,4 @@ # # -*- coding: utf-8 -*- -# -# from .cmd2 import __version__, Cmd, set_posix_shlex, set_strip_quotes, AddSubmenu, CmdResult, categorize -# from .cmd2 import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category - -# Used for tab completion and word breaks. Do not change. -QUOTES = ['"', "'"] -REDIRECTION_CHARS = ['|', '<', '>'] - - -def strip_quotes(arg: str) -> str: - """ Strip outer quotes from a string. - Applies to both single and double quotes. - :param arg: string to strip outer quotes from - :return: same string with potentially outer quotes stripped - """ - if len(arg) > 1 and arg[0] == arg[-1] and arg[0] in QUOTES: - arg = arg[1:-1] - return arg diff --git a/cmd2/argcomplete_bridge.py b/cmd2/argcomplete_bridge.py index 46950cc6..5383ebec 100644 --- a/cmd2/argcomplete_bridge.py +++ b/cmd2/argcomplete_bridge.py @@ -18,7 +18,8 @@ else: import shlex import sys - from . import strip_quotes, QUOTES + from . import constants + from . import utils def tokens_for_completion(line, endidx): @@ -42,7 +43,7 @@ else: Both items are None """ unclosed_quote = '' - quotes_to_try = copy.copy(QUOTES) + quotes_to_try = copy.copy(constants.QUOTES) tmp_line = line[:endidx] tmp_endidx = endidx @@ -86,7 +87,7 @@ else: raw_tokens = initial_tokens # Save the unquoted tokens - tokens = [strip_quotes(cur_token) for cur_token in raw_tokens] + tokens = [utils.strip_quotes(cur_token) for cur_token in raw_tokens] # If the token being completed had an unclosed quote, we need # to remove the closing quote that was added in order for it diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index e4f8c7c8..e7c553ac 100755 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -49,6 +49,9 @@ from code import InteractiveConsole import pyparsing import pyperclip +from . import constants +from . import utils + # Set up readline from .rl_utils import rl_force_redisplay, readline, rl_type, RlType from .argparse_completer import AutoCompleter, ACArgumentParser @@ -134,9 +137,6 @@ POSIX_SHLEX = False # Strip outer quotes for convenience if POSIX_SHLEX = False STRIP_QUOTES_FOR_NON_POSIX = True -# Used for tab completion and word breaks. Do not change. -from . import strip_quotes, QUOTES, REDIRECTION_CHARS - # optional attribute, when tagged on a function, allows cmd2 to categorize commands HELP_CATEGORY = 'help_category' HELP_SUMMARY = 'help_summary' @@ -196,7 +196,7 @@ def parse_quoted_string(cmdline: str) -> List[str]: if not POSIX_SHLEX and STRIP_QUOTES_FOR_NON_POSIX: temp_arglist = [] for arg in lexed_arglist: - temp_arglist.append(strip_quotes(arg)) + temp_arglist.append(utils.strip_quotes(arg)) lexed_arglist = temp_arglist return lexed_arglist @@ -362,7 +362,7 @@ def replace_with_file_contents(fname: str) -> str: """ try: # Any outer quotes are not part of the filename - unquoted_file = strip_quotes(fname[0]) + unquoted_file = utils.strip_quotes(fname[0]) with open(os.path.expanduser(unquoted_file)) as source_file: result = source_file.read() except IOError: @@ -382,19 +382,6 @@ class EmptyStatement(Exception): pass -# Regular expression to match ANSI escape codes -ANSI_ESCAPE_RE = re.compile(r'\x1b[^m]*m') - - -def strip_ansi(text: str) -> str: - """Strip ANSI escape codes from a string. - - :param text: string which may contain ANSI escape codes - :return: the same string with any ANSI escape codes removed - """ - return ANSI_ESCAPE_RE.sub('', text) - - def _pop_readline_history(clear_history: bool=True) -> List[str]: """Returns a copy of readline's history and optionally clears it (default)""" # noinspection PyArgumentList @@ -839,7 +826,7 @@ class Cmd(cmd.Cmd): :return: str - prompt stripped of any ANSI escape codes """ - return strip_ansi(self.prompt) + return utils.strip_ansi(self.prompt) def _finalize_app_parameters(self): self.commentGrammars.ignore(pyparsing.quotedString).setParseAction(lambda x: '') @@ -1005,7 +992,7 @@ class Cmd(cmd.Cmd): Both items are None """ unclosed_quote = '' - quotes_to_try = copy.copy(QUOTES) + quotes_to_try = copy.copy(constants.QUOTES) tmp_line = line[:endidx] tmp_endidx = endidx @@ -1048,7 +1035,7 @@ class Cmd(cmd.Cmd): for cur_initial_token in initial_tokens: # Save tokens up to 1 character in length or quoted tokens. No need to parse these. - if len(cur_initial_token) <= 1 or cur_initial_token[0] in QUOTES: + if len(cur_initial_token) <= 1 or cur_initial_token[0] in constants.QUOTES: raw_tokens.append(cur_initial_token) continue @@ -1060,10 +1047,10 @@ class Cmd(cmd.Cmd): cur_raw_token = '' while True: - if cur_char not in REDIRECTION_CHARS: + if cur_char not in constants.REDIRECTION_CHARS: # Keep appending to cur_raw_token until we hit a redirect char - while cur_char not in REDIRECTION_CHARS: + while cur_char not in constants.REDIRECTION_CHARS: cur_raw_token += cur_char cur_index += 1 if cur_index < len(cur_initial_token): @@ -1094,7 +1081,7 @@ class Cmd(cmd.Cmd): raw_tokens = initial_tokens # Save the unquoted tokens - tokens = [strip_quotes(cur_token) for cur_token in raw_tokens] + tokens = [utils.strip_quotes(cur_token) for cur_token in raw_tokens] # If the token being completed had an unclosed quote, we need # to remove the closing quote that was added in order for it @@ -1469,7 +1456,7 @@ class Cmd(cmd.Cmd): if len(raw_tokens) > 1: # Build a list of all redirection tokens - all_redirects = REDIRECTION_CHARS + ['>>'] + all_redirects = constants.REDIRECTION_CHARS + ['>>'] # Check if there are redirection strings prior to the token being completed seen_pipe = False @@ -1679,7 +1666,7 @@ class Cmd(cmd.Cmd): raw_completion_token = raw_tokens[-1] # Check if the token being completed has an opening quote - if raw_completion_token and raw_completion_token[0] in QUOTES: + if raw_completion_token and raw_completion_token[0] in constants.QUOTES: # Since the token is still being completed, we know the opening quote is unclosed unclosed_quote = raw_completion_token[0] @@ -2375,11 +2362,11 @@ class Cmd(cmd.Cmd): readline.set_completer(self.complete) # Break words on whitespace and quotes when tab completing - completer_delims = " \t\n" + ''.join(QUOTES) + completer_delims = " \t\n" + ''.join(constants.QUOTES) if self.allow_redirection: # If redirection is allowed, then break words on those characters too - completer_delims += ''.join(REDIRECTION_CHARS) + completer_delims += ''.join(constants.REDIRECTION_CHARS) readline.set_completer_delims(completer_delims) @@ -2824,13 +2811,13 @@ Usage: Usage: unalias [-a] name [name ...] # 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. first_char = tokens[index][0] - if first_char in QUOTES: - tokens[index] = strip_quotes(tokens[index]) + if first_char in constants.QUOTES: + tokens[index] = utils.strip_quotes(tokens[index]) tokens[index] = os.path.expanduser(tokens[index]) # Restore the quotes - if first_char in QUOTES: + if first_char in constants.QUOTES: tokens[index] = first_char + tokens[index] + first_char expanded_command = ' '.join(tokens) @@ -3338,7 +3325,7 @@ class ParserManager: ignore=do_not_parse)('pipeTo')) + \ pyparsing.Optional(output_destination_parser + pyparsing.SkipTo(string_end, ignore=do_not_parse). - setParseAction(lambda x: strip_quotes(x[0].strip()))('outputTo')) + setParseAction(lambda x: utils.strip_quotes(x[0].strip()))('outputTo')) multilineCommand.setParseAction(lambda x: x[0]) oneline_command.setParseAction(lambda x: x[0]) @@ -3697,13 +3684,13 @@ class Cmd2TestCase(unittest.TestCase): def _test_transcript(self, fname, transcript): line_num = 0 finished = False - line = strip_ansi(next(transcript)) + line = utils.strip_ansi(next(transcript)) line_num += 1 while not finished: # Scroll forward to where actual commands begin while not line.startswith(self.cmdapp.visible_prompt): try: - line = strip_ansi(next(transcript)) + line = utils.strip_ansi(next(transcript)) except StopIteration: finished = True break @@ -3727,13 +3714,13 @@ class Cmd2TestCase(unittest.TestCase): self.cmdapp.onecmd_plus_hooks(command) result = self.cmdapp.stdout.read() # Read the expected result from transcript - if strip_ansi(line).startswith(self.cmdapp.visible_prompt): + if utils.strip_ansi(line).startswith(self.cmdapp.visible_prompt): message = '\nFile {}, line {}\nCommand was:\n{}\nExpected: (nothing)\nGot:\n{}\n'.format( fname, line_num, command, result) self.assert_(not (result.strip()), message) continue expected = [] - while not strip_ansi(line).startswith(self.cmdapp.visible_prompt): + while not utils.strip_ansi(line).startswith(self.cmdapp.visible_prompt): expected.append(line) try: line = next(transcript) diff --git a/cmd2/constants.py b/cmd2/constants.py new file mode 100644 index 00000000..6784264f --- /dev/null +++ b/cmd2/constants.py @@ -0,0 +1,12 @@ +# +# coding=utf-8 +"""Constants and definitions""" + +import re + +# Used for command parsing, tab completion and word breaks. Do not change. +QUOTES = ['"', "'"] +REDIRECTION_CHARS = ['|', '<', '>'] + +# Regular expression to match ANSI escape codes +ANSI_ESCAPE_RE = re.compile(r'\x1b[^m]*m') diff --git a/cmd2/utils.py b/cmd2/utils.py new file mode 100644 index 00000000..9705e4f7 --- /dev/null +++ b/cmd2/utils.py @@ -0,0 +1,27 @@ +# +# coding=utf-8 +"""Shared utility functions""" + +from . import constants + + +def strip_ansi(text: str) -> str: + """Strip ANSI escape codes from a string. + + :param text: string which may contain ANSI escape codes + :return: the same string with any ANSI escape codes removed + """ + return constants.ANSI_ESCAPE_RE.sub('', text) + + +def strip_quotes(arg: str) -> str: + """ Strip outer quotes from a string. + + Applies to both single and double quotes. + + :param arg: string to strip outer quotes from + :return: same string with potentially outer quotes stripped + """ + if len(arg) > 1 and arg[0] == arg[-1] and arg[0] in constants.QUOTES: + arg = arg[1:-1] + return arg diff --git a/tests/test_completion.py b/tests/test_completion.py index 2c600018..7026db48 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -322,10 +322,11 @@ def test_path_completion_doesnt_match_wildcards(cmd2_app, request): # Currently path completion doesn't accept wildcards, so will always return empty results assert cmd2_app.path_complete(text, line, begidx, endidx) == [] -def test_path_completion_expand_user_dir(cmd2_app): - # Get the current user. We can't use getpass.getuser() since - # that doesn't work when running these tests on Windows in AppVeyor. - user = os.path.basename(os.path.expanduser('~')) +@pytest.mark.skipif(sys.platform == 'win32', reason="getpass.getuser() does not work on Windows in AppVeyor because " + "no user name environment variables are set") +def test_path_completion_complete_user(cmd2_app): + import getpass + user = getpass.getuser() text = '~{}'.format(user) line = 'shell fake {}'.format(text) @@ -336,7 +337,7 @@ def test_path_completion_expand_user_dir(cmd2_app): expected = text + os.path.sep assert expected in completions -def test_path_completion_user_expansion(cmd2_app): +def test_path_completion_user_path_expansion(cmd2_app): # Run path with a tilde and a slash if sys.platform.startswith('win'): cmd = 'dir' |