diff options
-rw-r--r-- | .pytest_cache/v/cache/lastfailed | 1 | ||||
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rwxr-xr-x | README.md | 6 | ||||
-rwxr-xr-x | cmd2.py | 37 | ||||
-rw-r--r-- | docs/settingchanges.rst | 10 | ||||
-rwxr-xr-x | examples/case_sensitive.py | 25 | ||||
-rwxr-xr-x | setup.py | 2 | ||||
-rw-r--r-- | tests/test_cmd2.py | 3 | ||||
-rw-r--r-- | tests/test_completion.py | 13 | ||||
-rw-r--r-- | tests/test_parsing.py | 36 |
10 files changed, 29 insertions, 108 deletions
diff --git a/.pytest_cache/v/cache/lastfailed b/.pytest_cache/v/cache/lastfailed new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/.pytest_cache/v/cache/lastfailed @@ -0,0 +1 @@ +{}
\ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c522b7a8..aec8e5a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,9 @@ * See [tab_completion.py](https://github.com/python-cmd2/cmd2/blob/master/examples/tab_completion.py) * Attributes Removed (**can cause breaking changes**) * ``abbrev`` - Removed support for abbreviated commands - * Good tab completion makes this unnecessary + * Good tab completion makes this unnecessary and its presence could cause harmful unintended actions + * ``case_insensitive`` - Removed support for case-insensitive command parsing + * Its presence wasn't very helpful and could cause harmful unintended actions ## 0.8.0 (February 1, 2018) * Bug Fixes @@ -26,7 +26,7 @@ Main Features - Redirect command output to file with `>`, `>>`; input from file with `<` - Bare `>`, `>>` with no filename send output to paste buffer (clipboard) - `py` enters interactive Python console (opt-in `ipy` for IPython console) -- Multi-line and case-insensitive commands +- Multi-line commands - Special-character command shortcuts (beyond cmd's `@` and `!`) - Settable environment parameters - Parsing commands with arguments using `argparse`, including support for sub-commands @@ -94,10 +94,6 @@ Instructions for implementing each feature follow. The program will keep expecting input until a line ends with any of the characters in `Cmd.terminators` . The default terminators are `;` and `/n` (empty newline). -- Case-insensitive commands - - All commands are case-insensitive, unless ``Cmd.caseInsensitive`` is set to ``False``. - - Special-character shortcut commands (beyond cmd's "@" and "!") To create a single-character shortcut for a command, update `Cmd.shortcuts`. @@ -5,14 +5,12 @@ To use, simply import cmd2.Cmd instead of cmd.Cmd; use precisely as though you were using the standard library's cmd, while enjoying the extra features. -Searchable command history (commands: "history", "list", "run") +Searchable command history (commands: "history") Load commands from file, save to file, edit commands in file Multi-line commands -Case-insensitive commands Special-character shortcut commands (beyond cmd's "@" and "!") Settable environment parameters -Optional _onchange_{paramname} called when environment parameter changes -Parsing commands with `optparse` options (flags) +Parsing commands with `argparse` argument parsers (flags) Redirection to file with >, >>; input from file with < Easy transcript-based testing of applications (see examples/example.py) Bash-style ``select`` available @@ -991,7 +989,6 @@ class Cmd(cmd.Cmd): """ # Attributes used to configure the ParserManager (all are not dynamically settable at runtime) blankLinesAllowed = False - case_insensitive = True # Commands recognized regardless of case commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment]) commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd ^ '*/') legalChars = u'!#$%.:?@_-' + pyparsing.alphanums + pyparsing.alphas8bit @@ -1086,7 +1083,6 @@ class Cmd(cmd.Cmd): multilineCommands=self.multilineCommands, legalChars=self.legalChars, commentGrammars=self.commentGrammars, commentInProgress=self.commentInProgress, - case_insensitive=self.case_insensitive, blankLinesAllowed=self.blankLinesAllowed, prefixParser=self.prefixParser, preparse=self.preparse, postparse=self.postparse, shortcuts=self.shortcuts) self._transcript_files = transcript_files @@ -1216,12 +1212,8 @@ class Cmd(cmd.Cmd): # noinspection PyMethodOverriding def completenames(self, text, line, begidx, endidx): """Override of cmd method which completes command names both for command completion and help.""" - command = text - if self.case_insensitive: - command = text.lower() - # Call super class method. Need to do it this way for Python 2 and 3 compatibility - cmd_completion = cmd.Cmd.completenames(self, command) + cmd_completion = cmd.Cmd.completenames(self, text) # If we are completing the initial command name and get exactly 1 result and are at end of line, add a space if begidx == 0 and len(cmd_completion) == 1 and endidx == len(line): @@ -1954,7 +1946,6 @@ class Cmd(cmd.Cmd): :return: str - summary report of read-only settings which the user cannot modify at runtime """ read_only_settings = """ - Commands are case-sensitive: {} Commands may be terminated with: {} Arguments at invocation allowed: {} Output redirection and pipes allowed: {} @@ -1962,7 +1953,7 @@ class Cmd(cmd.Cmd): Shell lexer mode for command argument splitting: {} Strip Quotes after splitting arguments: {} Argument type: {} - """.format(not self.case_insensitive, str(self.terminators), self.allow_cli_args, self.allow_redirection, + """.format(str(self.terminators), self.allow_cli_args, self.allow_redirection, "POSIX" if POSIX_SHLEX else "non-POSIX", "True" if STRIP_QUOTES_FOR_NON_POSIX and not POSIX_SHLEX else "False", "List of argument strings" if USE_ARG_LIST else "string of space-separated arguments") @@ -2575,7 +2566,7 @@ class ParserManager: Class which encapsulates all of the pyparsing parser functionality for cmd2 in a single location. """ def __init__(self, redirector, terminators, multilineCommands, legalChars, commentGrammars, commentInProgress, - case_insensitive, blankLinesAllowed, prefixParser, preparse, postparse, shortcuts): + blankLinesAllowed, prefixParser, preparse, postparse, shortcuts): """Creates and uses parsers for user input according to app's parameters.""" self.commentGrammars = commentGrammars @@ -2586,13 +2577,12 @@ class ParserManager: self.main_parser = self._build_main_parser(redirector=redirector, terminators=terminators, multilineCommands=multilineCommands, legalChars=legalChars, commentInProgress=commentInProgress, - case_insensitive=case_insensitive, blankLinesAllowed=blankLinesAllowed, prefixParser=prefixParser) self.input_source_parser = self._build_input_source_parser(legalChars=legalChars, commentInProgress=commentInProgress) - def _build_main_parser(self, redirector, terminators, multilineCommands, legalChars, - commentInProgress, case_insensitive, blankLinesAllowed, prefixParser): + def _build_main_parser(self, redirector, terminators, multilineCommands, legalChars, commentInProgress, + blankLinesAllowed, prefixParser): """Builds a PyParsing parser for interpreting user commands.""" # Build several parsing components that are eventually compiled into overall parser @@ -2604,7 +2594,7 @@ class ParserManager: [(hasattr(t, 'parseString') and t) or pyparsing.Literal(t) for t in terminators])('terminator') string_end = pyparsing.stringEnd ^ '\nEOF' multilineCommand = pyparsing.Or( - [pyparsing.Keyword(c, caseless=case_insensitive) for c in multilineCommands])('multilineCommand') + [pyparsing.Keyword(c, caseless=False) for c in multilineCommands])('multilineCommand') oneline_command = (~multilineCommand + pyparsing.Word(legalChars))('command') pipe = pyparsing.Keyword('|', identChars='|') do_not_parse = self.commentGrammars | commentInProgress | pyparsing.quotedString @@ -2614,12 +2604,9 @@ class ParserManager: pyparsing.Optional(output_destination_parser + pyparsing.SkipTo(string_end, ignore=do_not_parse).setParseAction(lambda x: x[0].strip())('outputTo')) - if case_insensitive: - multilineCommand.setParseAction(lambda x: x[0].lower()) - oneline_command.setParseAction(lambda x: x[0].lower()) - else: - multilineCommand.setParseAction(lambda x: x[0]) - oneline_command.setParseAction(lambda x: x[0]) + + multilineCommand.setParseAction(lambda x: x[0]) + oneline_command.setParseAction(lambda x: x[0]) if blankLinesAllowed: blankLineTerminationParser = pyparsing.NoMatch @@ -2687,7 +2674,7 @@ class ParserManager: s = self.input_source_parser.transformString(s.lstrip()) s = self.commentGrammars.transformString(s) for (shortcut, expansion) in self.shortcuts: - if s.lower().startswith(shortcut): + if s.startswith(shortcut): s = s.replace(shortcut, expansion + ' ', 1) break try: diff --git a/docs/settingchanges.rst b/docs/settingchanges.rst index 3e48fe24..ab7254a3 100644 --- a/docs/settingchanges.rst +++ b/docs/settingchanges.rst @@ -8,16 +8,6 @@ A parameter can also be changed at runtime by the user *if* its name is included in the dictionary ``app.settable``. (To define your own user-settable parameters, see :ref:`parameters`) -Case-insensitivity -================== - -By default, all ``cmd2`` command names are case-insensitive; -``sing the blues`` and ``SiNg the blues`` are equivalent. To change this, -set ``App.case_insensitive`` to False. - -Whether or not you set ``case_insensitive``, *please do not* define -command method names with any uppercase letters. ``cmd2`` expects all command methods -to be lowercase. Shortcuts (command aliases) =========================== diff --git a/examples/case_sensitive.py b/examples/case_sensitive.py deleted file mode 100755 index 828ebc06..00000000 --- a/examples/case_sensitive.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -"""A sample application demonstrating when commands are set to be case sensitive. - -By default cmd2 parses commands in a case-insensitive manner. But this behavior can be changed. -""" - -import cmd2 - - -class CaseSensitiveApp(cmd2.Cmd): - """ Example cmd2 application where commands are case-sensitive.""" - - def __init__(self): - # Set this before calling the super class __init__() - self.case_insensitive = False - - cmd2.Cmd.__init__(self) - - self.debug = True - - -if __name__ == '__main__': - app = CaseSensitiveApp() - app.cmdloop() @@ -26,7 +26,7 @@ Main features: - Redirect command output to file with `>`, `>>`; input from file with `<` - Bare `>`, `>>` with no filename send output to paste buffer (clipboard) - `py` enters interactive Python console (opt-in `ipy` for IPython console) - - Multi-line and case-insensitive commands + - Multi-line commands - Special-character command shortcuts (beyond cmd's `@` and `!`) - Settable environment parameters - Parsing commands with arguments using `argparse`, including support for sub-commands diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 6de86b2d..92f21757 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -88,7 +88,6 @@ def test_base_show_readonly(base_app): base_app.editor = 'vim' out = run_cmd(base_app, 'set -a') expected = normalize(SHOW_TXT + '\nRead only settings:' + """ - Commands are case-sensitive: {} Commands may be terminated with: {} Arguments at invocation allowed: {} Output redirection and pipes allowed: {} @@ -97,7 +96,7 @@ def test_base_show_readonly(base_app): Strip Quotes after splitting arguments: {} Argument type: {} -""".format(not base_app.case_insensitive, base_app.terminators, base_app.allow_cli_args, base_app.allow_redirection, +""".format(base_app.terminators, base_app.allow_cli_args, base_app.allow_redirection, "POSIX" if cmd2.POSIX_SHLEX else "non-POSIX", "True" if cmd2.STRIP_QUOTES_FOR_NON_POSIX and not cmd2.POSIX_SHLEX else "False", "List of argument strings" if cmd2.USE_ARG_LIST else "string of space-separated arguments")) diff --git a/tests/test_completion.py b/tests/test_completion.py index 5bdbf457..6f075c67 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -26,7 +26,6 @@ def cmd2_app(): @pytest.fixture def cs_app(): - cmd2.Cmd.case_insensitive = False c = cmd2.Cmd() return c @@ -136,21 +135,13 @@ def test_complete_bogus_command(cmd2_app): assert first_match is None -def test_cmd2_command_completion_is_case_insensitive_by_default(cmd2_app): +def test_cmd2_command_completion_is_case_sensitive(cmd2_app): text = 'HE' line = 'HE' endidx = len(line) begidx = endidx - len(text) # It is at end of line, so extra space is present - assert cmd2_app.completenames(text, line, begidx, endidx) == ['help '] - -def test_cmd2_case_sensitive_command_completion(cs_app): - text = 'HE' - line = 'HE' - endidx = len(line) - begidx = endidx - len(text) - # It is at end of line, so extra space is present - assert cs_app.completenames(text, line, begidx, endidx) == [] + assert cmd2_app.completenames(text, line, begidx, endidx) == [] def test_cmd2_command_completion_single_mid(cmd2_app): text = 'he' diff --git a/tests/test_parsing.py b/tests/test_parsing.py index c7abf07c..8ef9c5c0 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -20,41 +20,26 @@ def hist(): h = cmd2.History([HistoryItem('first'), HistoryItem('second'), HistoryItem('third'), HistoryItem('fourth')]) return h -# Case-insensitive parser +# Case-sensitive parser @pytest.fixture def parser(): c = cmd2.Cmd() c.multilineCommands = ['multiline'] - c.case_insensitive = True - c.parser_manager = cmd2.ParserManager(redirector=c.redirector, terminators=c.terminators, multilineCommands=c.multilineCommands, - legalChars=c.legalChars, commentGrammars=c.commentGrammars, - commentInProgress=c.commentInProgress, case_insensitive=c.case_insensitive, + 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, shortcuts=c.shortcuts) return c.parser_manager.main_parser -# Case-insensitive ParserManager -@pytest.fixture -def ci_pm(): - c = cmd2.Cmd() - c.multilineCommands = ['multiline'] - c.case_insensitive = True - c.parser_manager = cmd2.ParserManager(redirector=c.redirector, terminators=c.terminators, multilineCommands=c.multilineCommands, - legalChars=c.legalChars, commentGrammars=c.commentGrammars, - commentInProgress=c.commentInProgress, case_insensitive=c.case_insensitive, - blankLinesAllowed=c.blankLinesAllowed, prefixParser=c.prefixParser, - preparse=c.preparse, postparse=c.postparse, shortcuts=c.shortcuts) - return c.parser_manager - # Case-sensitive ParserManager @pytest.fixture def cs_pm(): c = cmd2.Cmd() c.multilineCommands = ['multiline'] - c.case_insensitive = False - c.parser_manager = cmd2.ParserManager(redirector=c.redirector, terminators=c.terminators, multilineCommands=c.multilineCommands, - legalChars=c.legalChars, commentGrammars=c.commentGrammars, - commentInProgress=c.commentInProgress, case_insensitive=c.case_insensitive, + 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, shortcuts=c.shortcuts) return c.parser_manager @@ -167,7 +152,7 @@ def test_parse_suffix_after_terminator(parser): assert results.suffix == 'suffx' def test_parse_command_with_args(parser): - line = 'COMmand with args' + line = 'command with args' results = parser.parseString(line) assert results.command == 'command' assert results.args == 'with args' @@ -219,11 +204,6 @@ def test_parse_output_redirect_with_dash_in_path(parser): assert results.outputTo == 'python-cmd2/afile.txt' -def test_case_insensitive_parsed_single_word(ci_pm): - line = 'HeLp' - statement = ci_pm.parsed(line) - assert statement.parsed.command == line.lower() - def test_case_sensitive_parsed_single_word(cs_pm): line = 'HeLp' statement = cs_pm.parsed(line) |