diff options
-rw-r--r-- | cmd2/parsing.py | 44 | ||||
-rw-r--r-- | tests/test_shlexparsing.py | 20 |
2 files changed, 58 insertions, 6 deletions
diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 5bb8d654..dece2b5e 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -31,7 +31,10 @@ class Statement(str): self.outputTo = None class CommandParser(): - """Parse raw text into command components.""" + """Parse raw text into command components. + + Shortcuts is a list of tuples with each tuple containing the shortcut and the expansion. + """ def __init__( self, quotes=['"', "'"], @@ -39,14 +42,18 @@ class CommandParser(): redirection_chars=['|', '<', '>'], terminators=[';'], multilineCommands = [], + aliases = {}, + shortcuts = [], ): self.quotes = quotes self.allow_redirection = allow_redirection self.redirection_chars = redirection_chars self.terminators = terminators self.multilineCommands = multilineCommands + self.aliases = aliases + self.shortcuts = shortcuts - def parseString(self, rawinput): + def parseString(self, line): # strip C-style comments # shlex will handle the python/shell style comments for us def replacer(match): @@ -61,7 +68,22 @@ class CommandParser(): r'/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE ) - rawinput = re.sub(pattern, replacer, rawinput) + line = re.sub(pattern, replacer, line) + rawinput = line + + # expand shortcuts, have to do this first because + # a shortcut can expand into multiple tokens, ie '!ls' becomes + # 'shell ls' + for (shortcut, expansion) in self.shortcuts: + if rawinput.startswith(shortcut): + # If the next character after the shortcut isn't a space, then insert one + shortcut_len = len(shortcut) + if len(rawinput) == shortcut_len or rawinput[shortcut_len] != ' ': + expansion += ' ' + + # Expand the shortcut + rawinput = rawinput.replace(shortcut, expansion, 1) + break s = shlex.shlex(rawinput, posix=False) s.whitespace_split = True @@ -140,11 +162,27 @@ class CommandParser(): suffix = None (command, args) = self._command_and_args(tokens) + # expand aliases + # make a copy of aliases so we can edit it + tmp_aliases = list(self.aliases.keys()) + keep_expanding = len(tmp_aliases) > 0 + + while keep_expanding: + for cur_alias in tmp_aliases: + keep_expanding = False + if command == cur_alias: + command = self.aliases[cur_alias] + tmp_aliases.remove(cur_alias) + keep_expanding = len(tmp_aliases) > 0 + break + + # set multiline if command in self.multilineCommands: multilineCommand = command else: multilineCommand = None + # build Statement object result = Statement(args) result.raw = rawinput result.command = command diff --git a/tests/test_shlexparsing.py b/tests/test_shlexparsing.py index 0029ca07..b8d4b208 100644 --- a/tests/test_shlexparsing.py +++ b/tests/test_shlexparsing.py @@ -12,11 +12,10 @@ Todo List Notes: -- Shortcuts may have to be discarded, or handled in a different way than they - are with pyparsing. - valid comment styles: - C-style -> /* comment */ - Python/Shell style -> # comment +- we now ignore self.identchars, which breaks backwards compatibility with the cmd in the standard library Functions in cmd2.py to be modified: - _complete_statement() @@ -42,7 +41,9 @@ def parser(): allow_redirection=True, redirection_chars=['|', '<', '>'], terminators = [';'], - multilineCommands = ['multiline'] + multilineCommands = ['multiline'], + aliases = {'helpalias': 'help', '42': 'theanswer'}, + shortcuts = [('?', 'help'), ('!', 'shell')] ) return parser @@ -274,3 +275,16 @@ def test_empty_statement_raises_exception(): with pytest.raises(cmd2.cmd2.EmptyStatement): app._complete_statement(' ') + +@pytest.mark.parametrize('line,command,args', [ + ('helpalias', 'help', ''), + ('helpalias mycommand', 'help', 'mycommand'), + ('42', 'theanswer', ''), + ('42 arg1 arg2', 'theanswer', 'arg1 arg2'), + ('!ls', 'shell', 'ls'), + ('!ls -al /tmp', 'shell', 'ls -al /tmp'), +]) +def test_alias_and_shortcut_expansion(parser, line, command, args): + statement = parser.parseString(line) + assert statement.command == command + assert statement.args == args |