summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2018-03-02 23:47:36 -0500
committerGitHub <noreply@github.com>2018-03-02 23:47:36 -0500
commit59a59233f7005491ea4187b4e8e120baa0e3905d (patch)
tree0e60c9821ff135fd3783e02773fe2380591c9f1e
parent40b52252a3108c8989afd6ca7e64ce9d4116cb4f (diff)
parent254cc0892a613e6681dad3802570d8e952a37075 (diff)
downloadcmd2-git-59a59233f7005491ea4187b4e8e120baa0e3905d.tar.gz
Merge pull request #295 from python-cmd2/case_sensitivity
Removed support for case-insensitive command parsing
-rw-r--r--.pytest_cache/v/cache/lastfailed1
-rw-r--r--CHANGELOG.md4
-rwxr-xr-xREADME.md6
-rwxr-xr-xcmd2.py37
-rw-r--r--docs/settingchanges.rst10
-rwxr-xr-xexamples/case_sensitive.py25
-rwxr-xr-xsetup.py2
-rw-r--r--tests/test_cmd2.py3
-rw-r--r--tests/test_completion.py13
-rw-r--r--tests/test_parsing.py36
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
diff --git a/README.md b/README.md
index 28757da6..863a4c1d 100755
--- a/README.md
+++ b/README.md
@@ -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`.
diff --git a/cmd2.py b/cmd2.py
index b71a8796..af3ebd8d 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -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()
diff --git a/setup.py b/setup.py
index 47ee5c97..ef7a2da7 100755
--- a/setup.py
+++ b/setup.py
@@ -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)