From 18b42fcfbb9b071964bd9f8ba4c48b1767bc9327 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 16 May 2018 00:22:43 -0400 Subject: [PATCH 1/3] Added check to support a continuous run of a terminator to end a line --- cmd2/parsing.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/cmd2/parsing.py b/cmd2/parsing.py index ce15bd38d..9fe8f6eb9 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -250,23 +250,18 @@ def parse(self, rawinput: str) -> Statement: tokens = self.tokenize(rawinput) # of the valid terminators, find the first one to occur in the input - terminator_pos = len(tokens)+1 - for test_terminator in self.terminators: - try: - pos = tokens.index(test_terminator) - if pos < terminator_pos: + terminator_pos = len(tokens) + 1 + for pos, cur_token in enumerate(tokens): + for test_terminator in self.terminators: + if cur_token.startswith(test_terminator): terminator_pos = pos terminator = test_terminator break - except ValueError: - # the terminator is not in the tokens - pass if terminator: if terminator == LINE_FEED: terminator_pos = len(tokens)+1 - else: - terminator_pos = tokens.index(terminator) + # everything before the first terminator is the command and the args argv = tokens[:terminator_pos] (command, args) = self._command_and_args(argv) From 5c1c427bbe724d1ad002c86c097a6d25ca73909c Mon Sep 17 00:00:00 2001 From: kotfu Date: Wed, 16 May 2018 10:19:55 -0600 Subject: [PATCH 2/3] Add unit tests to check for multiple terminators --- tests/test_parsing.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 41966c71c..d147c6a1a 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -250,6 +250,23 @@ def test_parse_redirect_inside_terminator(parser): assert statement.argv == ['has', '>', 'inside'] assert statement.terminator == ';' +@pytest.mark.parametrize('line,terminator',[ + ('multiline with | inside;', ';'), + ('multiline with | inside ;', ';'), + ('multiline with | inside;;;', ';'), + ('multiline with | inside;; ;;', ';'), + ('multiline with | inside&', '&'), + ('multiline with | inside &;', '&'), + ('multiline with | inside&&;', '&'), + ('multiline with | inside &; &;', '&'), +]) +def test_parse_multiple_terminators(parser, line, terminator): + statement = parser.parse(line) + assert statement.multiline_command == 'multiline' + assert statement.args == 'has | inside' + assert statement.argv == ['multiline', 'has', '|', 'inside'] + assert statement.terminator == terminator + def test_parse_unfinished_multiliine_command(parser): line = 'multiline has > inside an unfinished command' statement = parser.parse(line) @@ -261,7 +278,10 @@ def test_parse_unfinished_multiliine_command(parser): @pytest.mark.parametrize('line,terminator',[ ('multiline has > inside;', ';'), + ('multiline has > inside;;;', ';'), + ('multiline has > inside;; ;;', ';'), ('multiline has > inside &', '&'), + ('multiline has > inside & &', '&'), ]) def test_parse_multiline_command_ignores_redirectors_within_it(parser, line, terminator): statement = parser.parse(line) @@ -388,8 +408,15 @@ def test_parse_alias_pipe(parser, line): assert not statement.args assert statement.pipe_to == ['less'] -def test_parse_alias_terminator_no_whitespace(parser): - line = 'helpalias;' +@pytest.mark.parametrize('line', [ + 'helpalias;', + 'helpalias;;', + 'helpalias;; ;', + 'helpalias ;', + 'helpalias ; ;', + 'helpalias ;; ;', +]) +def test_parse_alias_terminator_no_whitespace(parser, line): statement = parser.parse(line) assert statement.command == 'help' assert not statement.args @@ -448,6 +475,8 @@ def test_parse_command_only_quoted_args(parser): 'helpalias>>out.txt', 'help|less', 'helpalias;', + 'help ;;', + 'help; ;;', ]) def test_parse_command_only_specialchars(parser, line): statement = parser.parse_command_only(line) @@ -455,6 +484,11 @@ def test_parse_command_only_specialchars(parser, line): @pytest.mark.parametrize('line', [ ';', + ';;', + ';; ;', + '&', + '& &', + ' && &', '>', "'", '"', From 490c8424c9be872538a5130734f120d0a34fdcaf Mon Sep 17 00:00:00 2001 From: kotfu Date: Thu, 17 May 2018 10:04:47 -0600 Subject: [PATCH 3/3] Fix bug in sequential terminator logic --- cmd2/parsing.py | 9 +++++++++ tests/test_parsing.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 9fe8f6eb9..655e0c587 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -256,7 +256,16 @@ def parse(self, rawinput: str) -> Statement: if cur_token.startswith(test_terminator): terminator_pos = pos terminator = test_terminator + # break the inner loop, and we want to break the + # outer loop too break + else: + # this else clause is only run if the inner loop + # didn't execute a break. If it didn't, then + # continue to the next iteration of the outer loop + continue + # inner loop was broken, break the outer + break if terminator: if terminator == LINE_FEED: diff --git a/tests/test_parsing.py b/tests/test_parsing.py index d147c6a1a..7b361b7e9 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -263,8 +263,8 @@ def test_parse_redirect_inside_terminator(parser): def test_parse_multiple_terminators(parser, line, terminator): statement = parser.parse(line) assert statement.multiline_command == 'multiline' - assert statement.args == 'has | inside' - assert statement.argv == ['multiline', 'has', '|', 'inside'] + assert statement.args == 'with | inside' + assert statement.argv == ['multiline', 'with', '|', 'inside'] assert statement.terminator == terminator def test_parse_unfinished_multiliine_command(parser):