Skip to content

Commit c628722

Browse files
committed
Merged master into history and resolved conflicts
2 parents 7349624 + eb86c73 commit c628722

File tree

9 files changed

+83
-153
lines changed

9 files changed

+83
-153
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
``AutoCompleter`` which has since developed a dependency on ``cmd2`` methods.
1212
* Removed ability to call commands in ``pyscript`` as if they were functions (e.g ``app.help()``) in favor
1313
of only supporting one ``pyscript`` interface. This simplifies future maintenance.
14+
* No longer supporting C-style comments. Hash (#) is the only valid comment marker.
15+
* No longer supporting comments embedded in a command. Only command line input where the first
16+
non-whitespace character is a # will be treated as a comment. This means any # character appearing
17+
later in the command will be treated as a literal. The same applies to a # in the middle of a multiline
18+
command, even if it is the first character on a line.
19+
* \# this is a comment
20+
* this # is not a comment
1421

1522
## 0.9.10 (February 22, 2019)
1623
* Bug Fixes

CODEOWNERS

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@
1414
1515

1616
# cmd2 code
17-
cmd2/__init__.py @tleonhardt @kotfu
18-
cmd2/arg*.py @anselor
19-
cmd2/cmd2.py @tleonhardt @kmvanbrunt @kotfu
20-
cmd2/constants.py @kotfu
21-
cmd2/parsing.py @kotfu @kmvanbrunt
22-
cmd2/pyscript*.py @anselor
23-
cmd2/rl_utils.py @kmvanbrunt
24-
cmd2/transcript.py @kotfu
25-
cmd2/utils.py @tleonhardt @kotfu @kmvanbrunt
17+
cmd2/__init__.py @tleonhardt @kotfu
18+
cmd2/argparse_completer.py @anselor @kmvanbrunt
19+
cmd2/clipboard.py @tleonhardt
20+
cmd2/cmd2.py @tleonhardt @kmvanbrunt @kotfu
21+
cmd2/constants.py @kotfu
22+
cmd2/parsing.py @kotfu @kmvanbrunt
23+
cmd2/pyscript_bridge.py @anselor @kmvanbrunt
24+
cmd2/rl_utils.py @kmvanbrunt
25+
cmd2/transcript.py @kotfu
26+
cmd2/utils.py @tleonhardt @kotfu @kmvanbrunt
2627

2728
# Sphinx documentation
2829
docs/* @tleonhardt @kotfu

cmd2/cmd2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def parse_quoted_string(string: str, preserve_quotes: bool) -> List[str]:
161161
lexed_arglist = string
162162
else:
163163
# Use shlex to split the command line into a list of arguments based on shell rules
164-
lexed_arglist = shlex.split(string, posix=False)
164+
lexed_arglist = shlex.split(string, comments=False, posix=False)
165165

166166
if not preserve_quotes:
167167
lexed_arglist = [utils.strip_quotes(arg) for arg in lexed_arglist]
@@ -738,7 +738,7 @@ def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> Tuple[Li
738738
while True:
739739
try:
740740
# Use non-POSIX parsing to keep the quotes around the tokens
741-
initial_tokens = shlex.split(tmp_line[:tmp_endidx], posix=False)
741+
initial_tokens = shlex.split(tmp_line[:tmp_endidx], comments=False, posix=False)
742742

743743
# If the cursor is at an empty token outside of a quoted string,
744744
# then that is the token being completed. Add it to the list.

cmd2/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
REDIRECTION_APPEND = '>>'
1313
REDIRECTION_CHARS = [REDIRECTION_PIPE, REDIRECTION_OUTPUT]
1414
REDIRECTION_TOKENS = [REDIRECTION_PIPE, REDIRECTION_OUTPUT, REDIRECTION_APPEND]
15+
COMMENT_CHAR = '#'
1516
MULTILINE_TERMINATOR = ';'
1617

1718
# Regular expression to match ANSI escape codes

cmd2/parsing.py

Lines changed: 10 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -258,33 +258,6 @@ def __init__(
258258
else:
259259
self.shortcuts = shortcuts
260260

261-
# this regular expression matches C-style comments and quoted
262-
# strings, i.e. stuff between single or double quote marks
263-
# it's used with _comment_replacer() to strip out the C-style
264-
# comments, while leaving C-style comments that are inside either
265-
# double or single quotes.
266-
#
267-
# this big regular expression can be broken down into 3 regular
268-
# expressions that are OR'ed together with a pipe character
269-
#
270-
# /\*.*\*/ Matches C-style comments (i.e. /* comment */)
271-
# does not match unclosed comments.
272-
# \'(?:\\.|[^\\\'])*\' Matches a single quoted string, allowing
273-
# for embedded backslash escaped single quote
274-
# marks.
275-
# "(?:\\.|[^\\"])*" Matches a double quoted string, allowing
276-
# for embedded backslash escaped double quote
277-
# marks.
278-
#
279-
# by way of reminder the (?:...) regular expression syntax is just
280-
# a non-capturing version of regular parenthesis. We need the non-
281-
# capturing syntax because _comment_replacer() looks at match
282-
# groups
283-
self.comment_pattern = re.compile(
284-
r'/\*.*\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
285-
re.DOTALL | re.MULTILINE
286-
)
287-
288261
# commands have to be a word, so make a regular expression
289262
# that matches the first word in the line. This regex has three
290263
# parts:
@@ -337,6 +310,9 @@ def is_valid_command(self, word: str) -> Tuple[bool, str]:
337310
if not word:
338311
return False, 'cannot be an empty string'
339312

313+
if word.startswith(constants.COMMENT_CHAR):
314+
return False, 'cannot start with the comment character'
315+
340316
for (shortcut, _) in self.shortcuts:
341317
if word.startswith(shortcut):
342318
# Build an error string with all shortcuts listed
@@ -360,24 +336,23 @@ def is_valid_command(self, word: str) -> Tuple[bool, str]:
360336
def tokenize(self, line: str) -> List[str]:
361337
"""Lex a string into a list of tokens.
362338
363-
Comments are removed, and shortcuts and aliases are expanded.
339+
shortcuts and aliases are expanded and comments are removed
364340
365341
Raises ValueError if there are unclosed quotation marks.
366342
"""
367343

368-
# strip C-style comments
369-
# shlex will handle the python/shell style comments for us
370-
line = re.sub(self.comment_pattern, self._comment_replacer, line)
371-
372344
# expand shortcuts and aliases
373345
line = self._expand(line)
374346

347+
# check if this line is a comment
348+
if line.strip().startswith(constants.COMMENT_CHAR):
349+
return []
350+
375351
# split on whitespace
376-
lexer = shlex.shlex(line, posix=False)
377-
lexer.whitespace_split = True
352+
tokens = shlex.split(line, comments=False, posix=False)
378353

379354
# custom lexing
380-
tokens = self._split_on_punctuation(list(lexer))
355+
tokens = self._split_on_punctuation(tokens)
381356
return tokens
382357

383358
def parse(self, line: str) -> Statement:
@@ -632,15 +607,6 @@ def _command_and_args(tokens: List[str]) -> Tuple[str, str]:
632607

633608
return command, args
634609

635-
@staticmethod
636-
def _comment_replacer(match):
637-
matched_string = match.group(0)
638-
if matched_string.startswith('/'):
639-
# the matched string was a comment, so remove it
640-
return ''
641-
# the matched string was a quoted string, return the match
642-
return matched_string
643-
644610
def _split_on_punctuation(self, tokens: List[str]) -> List[str]:
645611
"""Further splits tokens from a command line using punctuation characters
646612

docs/freefeatures.rst

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,16 @@ Simply include one command per line, typed exactly as you would inside a ``cmd2`
2929
Comments
3030
========
3131

32-
Comments are omitted from the argument list
33-
before it is passed to a ``do_`` method. By
34-
default, both Python-style and C-style comments
35-
are recognized. Comments can be useful in :ref:`scripts`, but would
36-
be pointless within an interactive session.
32+
Any command line input where the first non-whitespace character is a # will be treated as a comment.
33+
This means any # character appearing later in the command will be treated as a literal. The same
34+
applies to a # in the middle of a multiline command, even if it is the first character on a line.
3735

38-
::
39-
40-
def do_speak(self, arg):
41-
self.stdout.write(arg + '\n')
36+
Comments can be useful in :ref:`scripts`, but would be pointless within an interactive session.
4237

4338
::
4439

45-
(Cmd) speak it was /* not */ delicious! # Yuck!
46-
it was delicious!
47-
48-
.. _arg_print: https://github.com/python-cmd2/cmd2/blob/master/examples/arg_print.py
40+
(Cmd) # this is a comment
41+
(Cmd) this # is not a comment
4942

5043
Startup Initialization Script
5144
=============================
@@ -209,9 +202,9 @@ is superior for doing this in two primary ways:
209202
- it has the ability to pass command-line arguments to the scripts invoked
210203

211204
There are no disadvantages to using ``pyscript`` as opposed to ``py run()``. A simple example
212-
of using ``pyscript`` is shown below along with the **examples/arg_printer.py** script::
205+
of using ``pyscript`` is shown below along with the arg_printer_ script::
213206

214-
(Cmd) pyscript examples/arg_printer.py foo bar baz
207+
(Cmd) pyscript examples/scripts/arg_printer.py foo bar baz
215208
Running Python script 'arg_printer.py' which was called with 3 arguments
216209
arg 1: 'foo'
217210
arg 2: 'bar'
@@ -224,11 +217,12 @@ of using ``pyscript`` is shown below along with the **examples/arg_printer.py**
224217

225218
When using this decorator, you can then put arguments in quotes like so (NOTE: the ``do_pyscript`` method uses this decorator::
226219

227-
(Cmd) pyscript examples/arg_printer.py hello '23 fnord'
220+
(Cmd) pyscript examples/scripts/arg_printer.py hello '23 fnord'
228221
Running Python script 'arg_printer.py' which was called with 2 arguments
229222
arg 1: 'hello'
230223
arg 2: '23 fnord'
231224

225+
.. _arg_printer: https://github.com/python-cmd2/cmd2/blob/master/examples/scripts/arg_printer.py
232226

233227
IPython (optional)
234228
==================

tests/test_argparse.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,6 @@ def test_argparse_with_list_and_empty_doc(argparse_app):
141141
out = run_cmd(argparse_app, 'speak -s hello world!')
142142
assert out == ['HELLO WORLD!']
143143

144-
def test_argparse_comment_stripping(argparse_app):
145-
out = run_cmd(argparse_app, 'speak it was /* not */ delicious! # Yuck!')
146-
assert out == ['it was delicious!']
147-
148144
def test_argparser_correct_args_with_quotes_and_midline_options(argparse_app):
149145
out = run_cmd(argparse_app, "speak 'This is a' -s test of the emergency broadcast system!")
150146
assert out == ['THIS IS A TEST OF THE EMERGENCY BROADCAST SYSTEM!']

tests/test_cmd2.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@
2424
from unittest import mock
2525

2626
import cmd2
27-
from cmd2 import clipboard
28-
from cmd2 import utils
27+
from cmd2 import clipboard, constants, utils
2928
from .conftest import run_cmd, normalize, BASE_HELP, BASE_HELP_VERBOSE, \
3029
HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG
3130

@@ -1596,6 +1595,7 @@ def test_poutput_color_never(base_app):
15961595
# These are invalid names for aliases and macros
15971596
invalid_command_name = [
15981597
'""', # Blank name
1598+
constants.COMMENT_CHAR,
15991599
'!no_shortcut',
16001600
'">"',
16011601
'"no>pe"',
@@ -1668,6 +1668,17 @@ def test_alias_create_with_macro_name(base_app, capsys):
16681668
out, err = capsys.readouterr()
16691669
assert "Alias cannot have the same name as a macro" in err
16701670

1671+
def test_alias_that_resolves_into_comment(base_app, capsys):
1672+
# Create the alias
1673+
out = run_cmd(base_app, 'alias create fake ' + constants.COMMENT_CHAR + ' blah blah')
1674+
assert out == normalize("Alias 'fake' created")
1675+
1676+
# Use the alias
1677+
run_cmd(base_app, 'fake')
1678+
out, err = capsys.readouterr()
1679+
assert not out
1680+
assert not err
1681+
16711682
def test_alias_list_invalid_alias(base_app, capsys):
16721683
# Look up invalid alias
16731684
out = run_cmd(base_app, 'alias list invalid')
@@ -1824,6 +1835,17 @@ def test_macro_create_with_missing_unicode_arg_nums(base_app, capsys):
18241835
out, err = capsys.readouterr()
18251836
assert "Not all numbers between 1 and 3" in err
18261837

1838+
def test_macro_that_resolves_into_comment(base_app, capsys):
1839+
# Create the macro
1840+
out = run_cmd(base_app, 'macro create fake {1} blah blah')
1841+
assert out == normalize("Macro 'fake' created")
1842+
1843+
# Use the macro
1844+
run_cmd(base_app, 'fake ' + constants.COMMENT_CHAR)
1845+
out, err = capsys.readouterr()
1846+
assert not out
1847+
assert not err
1848+
18271849
def test_macro_list_invalid_macro(base_app, capsys):
18281850
# Look up invalid macro
18291851
run_cmd(base_app, 'macro list invalid')

0 commit comments

Comments
 (0)