summaryrefslogtreecommitdiff
path: root/tests/test_parsing.py
diff options
context:
space:
mode:
authorkotfu <jared@kotfu.net>2018-05-02 20:27:14 -0600
committerGitHub <noreply@github.com>2018-05-02 20:27:14 -0600
commit6b5c23c127e78ca4f9e30a1cbea76c61f10e418c (patch)
tree07ebf6b0b51227800b287a6d26d1951d449cbde6 /tests/test_parsing.py
parentd37004d05d0be67f48f35d6986ea899a35bcf89f (diff)
parentfa94eed90cf81b24e5b83c2b4c7e16025d849996 (diff)
downloadcmd2-git-6b5c23c127e78ca4f9e30a1cbea76c61f10e418c.tar.gz
Merge pull request #370 from python-cmd2/ply
Switch parsing logic from pyparsing to shlex
Diffstat (limited to 'tests/test_parsing.py')
-rw-r--r--tests/test_parsing.py561
1 files changed, 300 insertions, 261 deletions
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index b61e2d06..7940bbd8 100644
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -1,327 +1,294 @@
# coding=utf-8
"""
-Unit/functional testing for helper functions/classes in the cmd2.py module.
-
-These are primarily tests related to parsing. Moreover, they are mostly a port of the old doctest tests which were
-problematic because they worked properly for some versions of pyparsing but not for others.
+Test the parsing logic in parsing.py
Copyright 2017 Todd Leonhardt <todd.leonhardt@gmail.com>
Released under MIT license, see LICENSE file
"""
-from cmd2 import cmd2
import pytest
+from cmd2 import cmd2
+from cmd2.parsing import StatementParser
-@pytest.fixture
-def hist():
- from cmd2.cmd2 import HistoryItem
- h = cmd2.History([HistoryItem('first'), HistoryItem('second'), HistoryItem('third'), HistoryItem('fourth')])
- return h
+from cmd2 import utils
-# Case-sensitive parser
@pytest.fixture
def parser():
- c = cmd2.Cmd()
- c.multilineCommands = ['multiline']
- c.parser_manager = cmd2.ParserManager(redirector=c.redirector, terminators=c.terminators,
- multilineCommands=c.multilineCommands, legalChars=c.legalChars,
- commentGrammars=c.commentGrammars, commentInProgress=c.commentInProgress,
- blankLinesAllowed=c.blankLinesAllowed, prefixParser=c.prefixParser,
- preparse=c.preparse, postparse=c.postparse, aliases=c.aliases,
- shortcuts=c.shortcuts)
- return c.parser_manager.main_parser
-
-# Case-sensitive ParserManager
-@pytest.fixture
-def cs_pm():
- c = cmd2.Cmd()
- c.multilineCommands = ['multiline']
- c.parser_manager = cmd2.ParserManager(redirector=c.redirector, terminators=c.terminators,
- multilineCommands=c.multilineCommands, legalChars=c.legalChars,
- commentGrammars=c.commentGrammars, commentInProgress=c.commentInProgress,
- blankLinesAllowed=c.blankLinesAllowed, prefixParser=c.prefixParser,
- preparse=c.preparse, postparse=c.postparse, aliases=c.aliases,
- shortcuts=c.shortcuts)
- return c.parser_manager
-
-
-@pytest.fixture
-def input_parser():
- c = cmd2.Cmd()
- return c.parser_manager.input_source_parser
-
-
-def test_history_span(hist):
- h = hist
- assert h == ['first', 'second', 'third', 'fourth']
- assert h.span('-2..') == ['third', 'fourth']
- assert h.span('2..3') == ['second', 'third'] # Inclusive of end
- assert h.span('3') == ['third']
- assert h.span(':') == h
- assert h.span('2..') == ['second', 'third', 'fourth']
- assert h.span('-1') == ['fourth']
- assert h.span('-2..-3') == ['third', 'second']
- assert h.span('*') == h
-
-def test_history_get(hist):
- h = hist
- assert h == ['first', 'second', 'third', 'fourth']
- assert h.get('') == h
- assert h.get('-2') == h[:-2]
- assert h.get('5') == []
- assert h.get('2-3') == ['second'] # Exclusive of end
- assert h.get('ir') == ['first', 'third'] # Normal string search for all elements containing "ir"
- assert h.get('/i.*d/') == ['third'] # Regex string search "i", then anything, then "d"
-
-
-def test_cast():
- cast = cmd2.cast
-
- # Boolean
- assert cast(True, True) == True
- assert cast(True, False) == False
- assert cast(True, 0) == False
- assert cast(True, 1) == True
- assert cast(True, 'on') == True
- assert cast(True, 'off') == False
- assert cast(True, 'ON') == True
- assert cast(True, 'OFF') == False
- assert cast(True, 'y') == True
- assert cast(True, 'n') == False
- assert cast(True, 't') == True
- assert cast(True, 'f') == False
-
- # Non-boolean same type
- assert cast(1, 5) == 5
- assert cast(3.4, 2.7) == 2.7
- assert cast('foo', 'bar') == 'bar'
- assert cast([1,2], [3,4]) == [3,4]
-
-
-def test_cast_problems(capsys):
- cast = cmd2.cast
-
- expected = 'Problem setting parameter (now {}) to {}; incorrect type?\n'
-
- # Boolean current, with new value not convertible to bool
- current = True
- new = [True, True]
- assert cast(current, new) == current
- out, err = capsys.readouterr()
- assert out == expected.format(current, new)
-
- # Non-boolean current, with new value not convertible to current type
- current = 1
- new = 'octopus'
- assert cast(current, new) == current
- out, err = capsys.readouterr()
- assert out == expected.format(current, new)
-
+ parser = StatementParser(
+ allow_redirection=True,
+ terminators=[';'],
+ multiline_commands=['multiline'],
+ aliases={'helpalias': 'help',
+ '42': 'theanswer',
+ 'l': '!ls -al',
+ 'anothermultiline': 'multiline',
+ 'fake': 'pyscript'},
+ shortcuts=[('?', 'help'), ('!', 'shell')]
+ )
+ return parser
def test_parse_empty_string(parser):
- assert parser.parseString('').dump() == '[]'
-
-def test_parse_only_comment(parser):
- assert parser.parseString('/* empty command */').dump() == '[]'
-
-def test_parse_single_word(parser):
- line = 'plainword'
- results = parser.parseString(line)
- assert results.command == line
-
-def test_parse_word_plus_terminator(parser):
+ statement = parser.parse('')
+ assert not statement.command
+ assert not statement.args
+ assert statement.raw == ''
+
+@pytest.mark.parametrize('line,tokens', [
+ ('command', ['command']),
+ ('command /* with some comment */ arg', ['command', 'arg']),
+ ('command arg1 arg2 # comment at the end', ['command', 'arg1', 'arg2']),
+ ('42 arg1 arg2', ['theanswer', 'arg1', 'arg2']),
+ ('l', ['shell', 'ls', '-al'])
+])
+def test_tokenize(parser, line, tokens):
+ tokens_to_test = parser.tokenize(line)
+ assert tokens_to_test == tokens
+
+@pytest.mark.parametrize('tokens,command,args', [
+ ([], None, None),
+ (['command'], 'command', None),
+ (['command', 'arg1', 'arg2'], 'command', 'arg1 arg2')
+])
+def test_command_and_args(parser, tokens, command, args):
+ (parsed_command, parsed_args) = parser._command_and_args(tokens)
+ assert command == parsed_command
+ assert args == parsed_args
+
+@pytest.mark.parametrize('line', [
+ 'plainword',
+ '"one word"',
+ "'one word'",
+])
+def test_single_word(parser, line):
+ statement = parser.parse(line)
+ assert statement.command == line
+ assert not statement.args
+ assert statement.argv == [utils.strip_quotes(line)]
+
+def test_word_plus_terminator(parser):
line = 'termbare;'
- results = parser.parseString(line)
- assert results.command == 'termbare'
- assert results.terminator == ';'
+ statement = parser.parse(line)
+ assert statement.command == 'termbare'
+ assert statement.terminator == ';'
+ assert statement.argv == ['termbare']
-def test_parse_suffix_after_terminator(parser):
+def test_suffix_after_terminator(parser):
line = 'termbare; suffx'
- results = parser.parseString(line)
- assert results.command == 'termbare'
- assert results.terminator == ';'
- assert results.suffix == 'suffx'
+ statement = parser.parse(line)
+ assert statement.command == 'termbare'
+ assert statement.terminator == ';'
+ assert statement.suffix == 'suffx'
+ assert statement.argv == ['termbare']
-def test_parse_command_with_args(parser):
+def test_command_with_args(parser):
line = 'command with args'
- results = parser.parseString(line)
- assert results.command == 'command'
- assert results.args == 'with args'
+ statement = parser.parse(line)
+ assert statement.command == 'command'
+ assert statement.args == 'with args'
+ assert statement.argv == ['command', 'with', 'args']
+
+def test_command_with_quoted_args(parser):
+ line = 'command with "quoted args" and "some not"'
+ statement = parser.parse(line)
+ assert statement.command == 'command'
+ assert statement.args == 'with "quoted args" and "some not"'
+ assert statement.argv == ['command', 'with', 'quoted args', 'and', 'some not']
def test_parse_command_with_args_terminator_and_suffix(parser):
line = 'command with args and terminator; and suffix'
- results = parser.parseString(line)
- assert results.command == 'command'
- assert results.args == "with args and terminator"
- assert results.terminator == ';'
- assert results.suffix == 'and suffix'
-
-def test_parse_simple_piped(parser):
- line = 'simple | piped'
- results = parser.parseString(line)
- assert results.command == 'simple'
- assert results.pipeTo == " piped"
-
-def test_parse_double_pipe_is_not_a_pipe(parser):
+ statement = parser.parse(line)
+ assert statement.command == 'command'
+ assert statement.args == "with args and terminator"
+ assert statement.terminator == ';'
+ assert statement.suffix == 'and suffix'
+ assert statement.argv == ['command', 'with', 'args', 'and', 'terminator']
+
+def test_hashcomment(parser):
+ statement = parser.parse('hi # this is all a comment')
+ assert statement.command == 'hi'
+ assert not statement.args
+ assert statement.argv == ['hi']
+
+def test_c_comment(parser):
+ statement = parser.parse('hi /* this is | all a comment */')
+ assert statement.command == 'hi'
+ assert not statement.args
+ assert not statement.pipe_to
+ assert statement.argv == ['hi']
+
+def test_c_comment_empty(parser):
+ statement = parser.parse('/* this is | all a comment */')
+ assert not statement.command
+ assert not statement.args
+ assert not statement.pipe_to
+ assert not statement.argv
+
+def test_parse_what_if_quoted_strings_seem_to_start_comments(parser):
+ statement = parser.parse('what if "quoted strings /* seem to " start comments?')
+ assert statement.command == 'what'
+ assert statement.args == 'if "quoted strings /* seem to " start comments?'
+ assert not statement.pipe_to
+ assert statement.argv == ['what', 'if', 'quoted strings /* seem to ', 'start', 'comments?']
+
+def test_simple_piped(parser):
+ statement = parser.parse('simple | piped')
+ assert statement.command == 'simple'
+ assert not statement.args
+ assert statement.argv == ['simple']
+ assert statement.pipe_to == 'piped'
+
+def test_double_pipe_is_not_a_pipe(parser):
line = 'double-pipe || is not a pipe'
- results = parser.parseString(line)
- assert results.command == 'double-pipe'
- assert results.args == '|| is not a pipe'
- assert not 'pipeTo' in results
+ statement = parser.parse(line)
+ assert statement.command == 'double-pipe'
+ assert statement.args == '|| is not a pipe'
+ assert statement.argv == ['double-pipe', '||', 'is', 'not', 'a', 'pipe']
+ assert not statement.pipe_to
-def test_parse_complex_pipe(parser):
+def test_complex_pipe(parser):
line = 'command with args, terminator;sufx | piped'
- results = parser.parseString(line)
- assert results.command == 'command'
- assert results.args == "with args, terminator"
- assert results.terminator == ';'
- assert results.suffix == 'sufx'
- assert results.pipeTo == ' piped'
-
-def test_parse_output_redirect(parser):
+ statement = parser.parse(line)
+ assert statement.command == 'command'
+ assert statement.args == "with args, terminator"
+ assert statement.argv == ['command', 'with', 'args,', 'terminator']
+ assert statement.terminator == ';'
+ assert statement.suffix == 'sufx'
+ assert statement.pipe_to == 'piped'
+
+def test_output_redirect(parser):
line = 'output into > afile.txt'
- results = parser.parseString(line)
- assert results.command == 'output'
- assert results.args == 'into'
- assert results.output == '>'
- assert results.outputTo == 'afile.txt'
-
-def test_parse_output_redirect_with_dash_in_path(parser):
+ statement = parser.parse(line)
+ assert statement.command == 'output'
+ assert statement.args == 'into'
+ assert statement.argv == ['output', 'into']
+ assert statement.output == '>'
+ assert statement.output_to == 'afile.txt'
+
+def test_output_redirect_with_dash_in_path(parser):
line = 'output into > python-cmd2/afile.txt'
- results = parser.parseString(line)
- assert results.command == 'output'
- assert results.args == 'into'
- assert results.output == '>'
- assert results.outputTo == 'python-cmd2/afile.txt'
-
-
-def test_case_sensitive_parsed_single_word(cs_pm):
- line = 'HeLp'
- statement = cs_pm.parsed(line)
- assert statement.parsed.command == line
-
-
-def test_parse_input_redirect(input_parser):
- line = '< afile.txt'
- results = input_parser.parseString(line)
- assert results.inputFrom == line
-
-def test_parse_input_redirect_with_dash_in_path(input_parser):
- line = "< python-cmd2/afile.txt"
- results = input_parser.parseString(line)
- assert results.inputFrom == line
-
-def test_parse_pipe_and_redirect(parser):
+ statement = parser.parse(line)
+ assert statement.command == 'output'
+ assert statement.args == 'into'
+ assert statement.argv == ['output', 'into']
+ assert statement.output == '>'
+ assert statement.output_to == 'python-cmd2/afile.txt'
+
+def test_output_redirect_append(parser):
+ line = 'output appended to >> /tmp/afile.txt'
+ statement = parser.parse(line)
+ assert statement.command == 'output'
+ assert statement.args == 'appended to'
+ assert statement.argv == ['output', 'appended', 'to']
+ assert statement.output == '>>'
+ assert statement.output_to == '/tmp/afile.txt'
+
+def test_pipe_and_redirect(parser):
line = 'output into;sufx | pipethrume plz > afile.txt'
- results = parser.parseString(line)
- assert results.command == 'output'
- assert results.args == 'into'
- assert results.terminator == ';'
- assert results.suffix == 'sufx'
- assert results.pipeTo == ' pipethrume plz'
- assert results.output == '>'
- assert results.outputTo == 'afile.txt'
+ statement = parser.parse(line)
+ assert statement.command == 'output'
+ assert statement.args == 'into'
+ assert statement.argv == ['output', 'into']
+ assert statement.terminator == ';'
+ assert statement.suffix == 'sufx'
+ assert statement.pipe_to == 'pipethrume plz'
+ assert statement.output == '>'
+ assert statement.output_to == 'afile.txt'
def test_parse_output_to_paste_buffer(parser):
line = 'output to paste buffer >> '
- results = parser.parseString(line)
- assert results.command == 'output'
- assert results.args == 'to paste buffer'
- assert results.output == '>>'
-
-def test_parse_ignore_commented_redirectors(parser):
- line = 'ignore the /* commented | > */ stuff;'
- results = parser.parseString(line)
- assert results.command == 'ignore'
- assert results.args == 'the /* commented | > */ stuff'
- assert results.terminator == ';'
-
-def test_parse_has_redirect_inside_terminator(parser):
+ statement = parser.parse(line)
+ assert statement.command == 'output'
+ assert statement.args == 'to paste buffer'
+ assert statement.argv == ['output', 'to', 'paste', 'buffer']
+ assert statement.output == '>>'
+
+def test_has_redirect_inside_terminator(parser):
"""The terminator designates the end of the commmand/arguments portion. If a redirector
occurs before a terminator, then it will be treated as part of the arguments and not as a redirector."""
line = 'has > inside;'
- results = parser.parseString(line)
- assert results.command == 'has'
- assert results.args == '> inside'
- assert results.terminator == ';'
-
-def test_parse_what_if_quoted_strings_seem_to_start_comments(parser):
- line = 'what if "quoted strings /* seem to " start comments?'
- results = parser.parseString(line)
- assert results.command == 'what'
- assert results.args == 'if "quoted strings /* seem to " start comments?'
+ statement = parser.parse(line)
+ assert statement.command == 'has'
+ assert statement.args == '> inside'
+ assert statement.argv == ['has', '>', 'inside']
+ assert statement.terminator == ';'
def test_parse_unfinished_multiliine_command(parser):
line = 'multiline has > inside an unfinished command'
- results = parser.parseString(line)
- assert results.multilineCommand == 'multiline'
- assert not 'args' in results
+ statement = parser.parse(line)
+ assert statement.multiline_command == 'multiline'
+ assert statement.command == 'multiline'
+ assert statement.args == 'has > inside an unfinished command'
+ assert statement.argv == ['multiline', 'has', '>', 'inside', 'an', 'unfinished', 'command']
+ assert not statement.terminator
def test_parse_multiline_command_ignores_redirectors_within_it(parser):
line = 'multiline has > inside;'
- results = parser.parseString(line)
- assert results.multilineCommand == 'multiline'
- assert results.args == 'has > inside'
- assert results.terminator == ';'
+ statement = parser.parse(line)
+ assert statement.multiline_command == 'multiline'
+ assert statement.args == 'has > inside'
+ assert statement.argv == ['multiline', 'has', '>', 'inside']
+ assert statement.terminator == ';'
def test_parse_multiline_with_incomplete_comment(parser):
"""A terminator within a comment will be ignored and won't terminate a multiline command.
Un-closed comments effectively comment out everything after the start."""
line = 'multiline command /* with comment in progress;'
- results = parser.parseString(line)
- assert results.multilineCommand == 'multiline'
- assert not 'args' in results
+ statement = parser.parse(line)
+ assert statement.multiline_command == 'multiline'
+ assert statement.command == 'multiline'
+ assert statement.args == 'command'
+ assert statement.argv == ['multiline', 'command']
+ assert not statement.terminator
def test_parse_multiline_with_complete_comment(parser):
line = 'multiline command /* with comment complete */ is done;'
- results = parser.parseString(line)
- assert results.multilineCommand == 'multiline'
- assert results.args == 'command /* with comment complete */ is done'
- assert results.terminator == ';'
+ statement = parser.parse(line)
+ assert statement.multiline_command == 'multiline'
+ assert statement.command == 'multiline'
+ assert statement.args == 'command is done'
+ assert statement.argv == ['multiline', 'command', 'is', 'done']
+ assert statement.terminator == ';'
def test_parse_multiline_termninated_by_empty_line(parser):
line = 'multiline command ends\n\n'
- results = parser.parseString(line)
- assert results.multilineCommand == 'multiline'
- assert results.args == 'command ends'
- assert len(results.terminator) == 2
- assert results.terminator[0] == '\n'
- assert results.terminator[1] == '\n'
+ statement = parser.parse(line)
+ assert statement.multiline_command == 'multiline'
+ assert statement.command == 'multiline'
+ assert statement.args == 'command ends'
+ assert statement.argv == ['multiline', 'command', 'ends']
+ assert statement.terminator == '\n'
def test_parse_multiline_ignores_terminators_in_comments(parser):
line = 'multiline command "with term; ends" now\n\n'
- results = parser.parseString(line)
- assert results.multilineCommand == 'multiline'
- assert results.args == 'command "with term; ends" now'
- assert len(results.terminator) == 2
- assert results.terminator[0] == '\n'
- assert results.terminator[1] == '\n'
+ statement = parser.parse(line)
+ assert statement.multiline_command == 'multiline'
+ assert statement.command == 'multiline'
+ assert statement.args == 'command "with term; ends" now'
+ assert statement.argv == ['multiline', 'command', 'with term; ends', 'now']
+ assert statement.terminator == '\n'
def test_parse_command_with_unicode_args(parser):
line = 'drink café'
- results = parser.parseString(line)
- assert results.command == 'drink'
- assert results.args == 'café'
+ statement = parser.parse(line)
+ assert statement.command == 'drink'
+ assert statement.args == 'café'
+ assert statement.argv == ['drink', 'café']
def test_parse_unicode_command(parser):
line = 'café au lait'
- results = parser.parseString(line)
- assert results.command == 'café'
- assert results.args == 'au lait'
+ statement = parser.parse(line)
+ assert statement.command == 'café'
+ assert statement.args == 'au lait'
+ assert statement.argv == ['café', 'au', 'lait']
def test_parse_redirect_to_unicode_filename(parser):
line = 'dir home > café'
- results = parser.parseString(line)
- assert results.command == 'dir'
- assert results.args == 'home'
- assert results.output == '>'
- assert results.outputTo == 'café'
-
-def test_parse_input_redirect_from_unicode_filename(input_parser):
- line = '< café'
- results = input_parser.parseString(line)
- assert results.inputFrom == line
-
+ statement = parser.parse(line)
+ assert statement.command == 'dir'
+ assert statement.args == 'home'
+ assert statement.argv == ['dir', 'home']
+ assert statement.output == '>'
+ assert statement.output_to == 'café'
def test_empty_statement_raises_exception():
app = cmd2.Cmd()
@@ -330,3 +297,75 @@ def test_empty_statement_raises_exception():
with pytest.raises(cmd2.EmptyStatement):
app._complete_statement(' ')
+
+@pytest.mark.parametrize('line,command,args', [
+ ('helpalias', 'help', None),
+ ('helpalias mycommand', 'help', 'mycommand'),
+ ('42', 'theanswer', None),
+ ('42 arg1 arg2', 'theanswer', 'arg1 arg2'),
+ ('!ls', 'shell', 'ls'),
+ ('!ls -al /tmp', 'shell', 'ls -al /tmp'),
+ ('l', 'shell', 'ls -al')
+])
+def test_alias_and_shortcut_expansion(parser, line, command, args):
+ statement = parser.parse(line)
+ assert statement.command == command
+ assert statement.args == args
+
+def test_alias_on_multiline_command(parser):
+ line = 'anothermultiline has > inside an unfinished command'
+ statement = parser.parse(line)
+ assert statement.multiline_command == 'multiline'
+ assert statement.command == 'multiline'
+ assert statement.args == 'has > inside an unfinished command'
+ assert not statement.terminator
+
+def test_parse_command_only_command_and_args(parser):
+ line = 'help history'
+ statement = parser.parse_command_only(line)
+ assert statement.command == 'help'
+ assert statement.args == 'history'
+ assert statement.argv == ['help', 'history']
+ assert statement.command_and_args == line
+
+def test_parse_command_only_emptyline(parser):
+ line = ''
+ statement = parser.parse_command_only(line)
+ # statement is a subclass of str(), the value of the str
+ # should be '', to retain backwards compatibility with
+ # the cmd in the standard library
+ assert statement == ''
+ assert statement.command is None
+ assert statement.args is None
+ assert not statement.argv
+ assert statement.command_and_args == None
+
+def test_parse_command_only_strips_line(parser):
+ line = ' help history '
+ statement = parser.parse_command_only(line)
+ assert statement.command == 'help'
+ assert statement.args == 'history'
+ assert statement.argv == ['help', 'history']
+ assert statement.command_and_args == line.strip()
+
+def test_parse_command_only_expands_alias(parser):
+ line = 'fake foobar.py'
+ statement = parser.parse_command_only(line)
+ assert statement.command == 'pyscript'
+ assert statement.args == 'foobar.py'
+ assert statement.argv == ['pyscript', 'foobar.py']
+
+def test_parse_command_only_expands_shortcuts(parser):
+ line = '!cat foobar.txt'
+ statement = parser.parse_command_only(line)
+ assert statement.command == 'shell'
+ assert statement.args == 'cat foobar.txt'
+ assert statement.argv == ['shell', 'cat', 'foobar.txt']
+ assert statement.command_and_args == 'shell cat foobar.txt'
+
+def test_parse_command_only_quoted_args(parser):
+ line = 'l "/tmp/directory with spaces/doit.sh"'
+ statement = parser.parse_command_only(line)
+ assert statement.command == 'shell'
+ assert statement.args == 'ls -al "/tmp/directory with spaces/doit.sh"'
+ assert statement.command_and_args == line.replace('l', 'shell ls -al')