summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkotfu <kotfu@kotfu.net>2019-07-16 17:30:23 -0600
committerkotfu <kotfu@kotfu.net>2019-07-16 17:30:23 -0600
commitf3ae3e129c9205229922463fc5ef57cc413c0ab9 (patch)
treebfe8b7eec20592de74725e5298dec68799f7ebec
parenta8c4a65106ad6325adec5e662ee8a5397527dba5 (diff)
parentf77abb09888c6ea3f6d8dadb28de46c36f035459 (diff)
downloadcmd2-git-f3ae3e129c9205229922463fc5ef57cc413c0ab9.tar.gz
Merge branch 'master' into integrate_legacy_documentation
# Conflicts: # docs/features/history.rst
-rw-r--r--CHANGELOG.md7
-rw-r--r--cmd2/argparse_completer.py2
-rw-r--r--cmd2/argparse_custom.py2
-rw-r--r--cmd2/cmd2.py54
-rw-r--r--cmd2/parsing.py13
-rw-r--r--docs/features/history.rst2
-rw-r--r--tests/conftest.py8
-rw-r--r--tests/test_argparse_completer.py14
-rw-r--r--tests/test_completion.py6
-rw-r--r--tests/test_history.py2
-rw-r--r--tests/test_parsing.py2
-rw-r--r--tests/test_utils.py3
12 files changed, 63 insertions, 52 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14aff012..615e282d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,13 @@
* Moved `basic_complete` to utils.py
* Made optional arguments on the following completer methods keyword-only:
`delimiter_complete`, `flag_based_complete`, `index_based_complete`. `path_complete`, `shell_cmd_complete`
+ * Renamed history option from `--output-file` to `--output_file`
+ * Renamed `matches_sort_key` to `default_sort_key`. This value determines the default sort ordering of string
+ results like alias, command, category, macro, settable, and shortcut names. Unsorted tab-completion results
+ also are sorted with this key. Its default value (ALPHABETICAL_SORT_KEY) performs a case-insensitive alphabetical
+ sort, but it can be changed to a natural sort by setting the value to NATURAL_SORT_KEY.
+ * `StatementParser` now expects shortcuts to be passed in as dictionary. This eliminates the step of converting the
+ shortcuts dictionary into a tuple before creating `StatementParser`.
## 0.9.14 (June 29, 2019)
* Enhancements
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index 737286c1..95ccf7b4 100644
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -391,7 +391,7 @@ class AutoCompleter(object):
# If the user has not already sorted the CompletionItems, then sort them before appending the descriptions
if not self._cmd2_app.matches_sorted:
- completions.sort(key=self._cmd2_app.matches_sort_key)
+ completions.sort(key=self._cmd2_app.default_sort_key)
self._cmd2_app.matches_sorted = True
token_width = ansi_safe_wcswidth(action.dest)
diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py
index 1cdb7840..5d8e76ef 100644
--- a/cmd2/argparse_custom.py
+++ b/cmd2/argparse_custom.py
@@ -74,7 +74,7 @@ Tab Completion:
completer_method
This is exactly like completer_function, but the function needs to be an instance method of a cmd2-based class.
When AutoCompleter calls the method, it will pass the app instance as the self argument. cmd2 provides
- a few completer methods for convenience (e.g. path_complete, delimiter_complete)
+ a few completer methods for convenience (e.g., path_complete, delimiter_complete)
Example:
This adds file-path completion to an argument
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index a4036a8e..0255d1ce 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -397,9 +397,6 @@ class Cmd(cmd.Cmd):
self._py_history = []
self.pyscript_name = 'app'
- if shortcuts is None:
- shortcuts = constants.DEFAULT_SHORTCUTS
- shortcuts = sorted(shortcuts.items(), reverse=True)
self.statement_parser = StatementParser(allow_redirection=allow_redirection,
terminators=terminators,
multiline_commands=multiline_commands,
@@ -468,11 +465,13 @@ class Cmd(cmd.Cmd):
elif transcript_files:
self._transcript_files = transcript_files
- # The default key for sorting tab completion matches. This only applies when the matches are not
- # already marked as sorted by setting self.matches_sorted to True. Its default value performs a
- # case-insensitive alphabetical sort. If natural sorting preferred, then set this to NATURAL_SORT_KEY.
- # Otherwise it can be set to any custom key to meet your needs.
- self.matches_sort_key = ALPHABETICAL_SORT_KEY
+ # The default key for sorting string results. Its default value performs a case-insensitive alphabetical sort.
+ # If natural sorting is preferred, then set this to NATURAL_SORT_KEY.
+ # cmd2 uses this key for sorting:
+ # command and category names
+ # alias, macro, settable, and shortcut names
+ # tab completion results when self.matches_sorted is False
+ self.default_sort_key = ALPHABETICAL_SORT_KEY
############################################################################################################
# The following variables are used by tab-completion functions. They are reset each time complete() is run
@@ -501,8 +500,8 @@ class Cmd(cmd.Cmd):
# quote matches that are completed in a delimited fashion
self.matches_delimited = False
- # Set to True before returning matches to complete() in cases where matches are sorted with custom ordering.
- # If False, then complete() will sort the matches using self.matches_sort_key before they are displayed.
+ # Set to True before returning matches to complete() in cases where matches have already been sorted.
+ # If False, then complete() will sort the matches using self.default_sort_key before they are displayed.
self.matches_sorted = False
# Set the pager(s) for use with the ppaged() method for displaying output using a pager
@@ -1107,7 +1106,7 @@ class Cmd(cmd.Cmd):
self.allow_closing_quote = False
# Sort the matches before any trailing slashes are added
- matches.sort(key=self.matches_sort_key)
+ matches.sort(key=self.default_sort_key)
self.matches_sorted = True
# Build display_matches and add a slash to directories
@@ -1553,8 +1552,8 @@ class Cmd(cmd.Cmd):
# Sort matches if they haven't already been sorted
if not self.matches_sorted:
- self.completion_matches.sort(key=self.matches_sort_key)
- self.display_matches.sort(key=self.matches_sort_key)
+ self.completion_matches.sort(key=self.default_sort_key)
+ self.display_matches.sort(key=self.default_sort_key)
self.matches_sorted = True
try:
@@ -2326,8 +2325,7 @@ class Cmd(cmd.Cmd):
else:
self.perror("Alias '{}' not found".format(cur_name))
else:
- sorted_aliases = utils.alphabetical_sort(self.aliases)
- for cur_alias in sorted_aliases:
+ for cur_alias in sorted(self.aliases, key=self.default_sort_key):
self.poutput("alias create {} {}".format(cur_alias, self.aliases[cur_alias]))
# Top-level parser for alias
@@ -2507,8 +2505,7 @@ class Cmd(cmd.Cmd):
else:
self.perror("Macro '{}' not found".format(cur_name))
else:
- sorted_macros = utils.alphabetical_sort(self.macros)
- for cur_macro in sorted_macros:
+ for cur_macro in sorted(self.macros, key=self.default_sort_key):
self.poutput("macro create {} {}".format(cur_macro, self.macros[cur_macro].value))
# Top-level parser for macro
@@ -2692,10 +2689,10 @@ class Cmd(cmd.Cmd):
"""Show a list of commands which help can be displayed for.
"""
# Get a sorted list of help topics
- help_topics = utils.alphabetical_sort(self.get_help_topics())
+ help_topics = sorted(self.get_help_topics(), key=self.default_sort_key)
# Get a sorted list of visible command names
- visible_commands = utils.alphabetical_sort(self.get_visible_commands())
+ visible_commands = sorted(self.get_visible_commands(), key=self.default_sort_key)
cmds_doc = []
cmds_undoc = []
@@ -2730,7 +2727,7 @@ class Cmd(cmd.Cmd):
# Categories found, Organize all commands by category
self.poutput('{}'.format(str(self.doc_leader)))
self.poutput('{}'.format(str(self.doc_header)), end="\n\n")
- for category in sorted(cmds_cats.keys()):
+ for category in sorted(cmds_cats.keys(), key=self.default_sort_key):
self._print_topics(category, cmds_cats[category], verbose)
self._print_topics('Other', cmds_doc, verbose)
@@ -2816,7 +2813,9 @@ class Cmd(cmd.Cmd):
@with_argparser(ArgParser())
def do_shortcuts(self, _: argparse.Namespace) -> None:
"""List available shortcuts"""
- result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in sorted(self.statement_parser.shortcuts))
+ # Sort the shortcut tuples by name
+ sorted_shortcuts = sorted(self.statement_parser.shortcuts, key=lambda x: self.default_sort_key(x[0]))
+ result = "\n".join('{}: {}'.format(sc[0], sc[1]) for sc in sorted_shortcuts)
self.poutput("Shortcuts for other commands:\n{}".format(result))
@with_argparser(ArgParser(epilog=INTERNAL_COMMAND_EPILOG))
@@ -2903,7 +2902,7 @@ class Cmd(cmd.Cmd):
maxlen = max(maxlen, len(result[p]))
if result:
- for p in sorted(result):
+ for p in sorted(result, key=self.default_sort_key):
if args.long:
self.poutput('{} # {}'.format(result[p].ljust(maxlen), self.settable[p]))
else:
@@ -3275,11 +3274,11 @@ class Cmd(cmd.Cmd):
history_action_group.add_argument('-r', '--run', action='store_true', help='run selected history items')
history_action_group.add_argument('-e', '--edit', action='store_true',
help='edit and then run selected history items')
- history_action_group.add_argument('-o', '--output-file', metavar='FILE',
+ history_action_group.add_argument('-o', '--output_file', metavar='FILE',
help='output commands to a script file, implies -s',
completer_method=path_complete)
- history_action_group.add_argument('-t', '--transcript', help='output commands and results to a transcript file,\n'
- 'implies -s',
+ history_action_group.add_argument('-t', '--transcript', metavar='TRANSCRIPT_FILE',
+ help='output commands and results to a transcript file,\nimplies -s',
completer_method=path_complete)
history_action_group.add_argument('-c', '--clear', action='store_true', help='clear all history')
@@ -3598,11 +3597,12 @@ class Cmd(cmd.Cmd):
"Script should contain one command per line, just like the command would be\n"
"typed in the console.\n"
"\n"
- "If the -r/--record_transcript flag is used, this command instead records\n"
+ "If the -t/--transcript flag is used, this command instead records\n"
"the output of the script commands to a transcript for testing purposes.\n")
run_script_parser = ArgParser(description=run_script_description)
- run_script_parser.add_argument('-t', '--transcript', help='record the output of the script as a transcript file',
+ run_script_parser.add_argument('-t', '--transcript', metavar='TRANSCRIPT_FILE',
+ help='record the output of the script as a transcript file',
completer_method=path_complete)
run_script_parser.add_argument('script_path', help="path to the script file", completer_method=path_complete)
diff --git a/cmd2/parsing.py b/cmd2/parsing.py
index 2e94516a..dbfabc80 100644
--- a/cmd2/parsing.py
+++ b/cmd2/parsing.py
@@ -249,7 +249,7 @@ class StatementParser:
terminators: Optional[Iterable[str]] = None,
multiline_commands: Optional[Iterable[str]] = None,
aliases: Optional[Dict[str, str]] = None,
- shortcuts: Optional[Iterable[Tuple[str, str]]] = None) -> None:
+ shortcuts: Optional[Dict[str, str]] = None) -> None:
"""Initialize an instance of StatementParser.
The following will get converted to an immutable tuple before storing internally:
@@ -261,7 +261,7 @@ class StatementParser:
:param terminators: iterable containing strings which should terminate multiline commands
:param multiline_commands: iterable containing the names of commands that accept multiline input
:param aliases: dictionary containing aliases
- :param shortcuts: an iterable of tuples with each tuple containing the shortcut and the expansion
+ :param shortcuts: dictionary containing shortcuts
"""
self.allow_redirection = allow_redirection
if terminators is None:
@@ -276,10 +276,13 @@ class StatementParser:
self.aliases = dict()
else:
self.aliases = aliases
+
if shortcuts is None:
- self.shortcuts = tuple()
- else:
- self.shortcuts = tuple(shortcuts)
+ shortcuts = constants.DEFAULT_SHORTCUTS
+
+ # Sort the shortcuts in descending order by name length because the longest match
+ # should take precedence. (e.g., @@file should match '@@' and not '@'.
+ self.shortcuts = tuple(sorted(shortcuts.items(), key=lambda x: len(x[0]), reverse=True))
# commands have to be a word, so make a regular expression
# that matches the first word in the line. This regex has three
diff --git a/docs/features/history.rst b/docs/features/history.rst
index 8aa305c0..c1806065 100644
--- a/docs/features/history.rst
+++ b/docs/features/history.rst
@@ -191,7 +191,7 @@ reference to identify previously entered commands. However, when creating a
script or a transcript, the command numbers would prevent the script from
loading properly. The ``-s`` or ``--script`` option instructs the ``history``
command to suppress the line numbers. This option is automatically set by the
-``--output-file``, ``--transcript``, and ``--edit`` options. If you want to
+``--output_file``, ``--transcript``, and ``--edit`` options. If you want to
output the history commands with line numbers to a file, you can do it with
output redirection::
diff --git a/tests/conftest.py b/tests/conftest.py
index c0aea4a6..e09e07b1 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -44,8 +44,8 @@ def verify_help_text(cmd2_app: cmd2.Cmd, help_output: Union[str, List[str]]) ->
# Help text for the history command
-HELP_HISTORY = """Usage: history [-h] [-r | -e | -o FILE | -t TRANSCRIPT | -c] [-s] [-x] [-v]
- [-a]
+HELP_HISTORY = """Usage: history [-h] [-r | -e | -o FILE | -t TRANSCRIPT_FILE | -c] [-s] [-x]
+ [-v] [-a]
[arg]
View, run, edit, save, or clear previously entered commands
@@ -61,9 +61,9 @@ optional arguments:
-h, --help show this help message and exit
-r, --run run selected history items
-e, --edit edit and then run selected history items
- -o, --output-file FILE
+ -o, --output_file FILE
output commands to a script file, implies -s
- -t, --transcript TRANSCRIPT
+ -t, --transcript TRANSCRIPT_FILE
output commands and results to a transcript file,
implies -s
-c, --clear clear all history
diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py
index 4ad4c560..19ec551b 100644
--- a/tests/test_argparse_completer.py
+++ b/tests/test_argparse_completer.py
@@ -263,7 +263,7 @@ def test_complete_help(ac_app, command, text, completions):
else:
assert first_match is None
- assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.default_sort_key)
@pytest.mark.parametrize('command_and_args, text, completions', [
@@ -320,7 +320,7 @@ def test_autcomp_flag_completion(ac_app, command_and_args, text, completions):
else:
assert first_match is None
- assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.default_sort_key)
@pytest.mark.parametrize('flag, text, completions', [
@@ -346,7 +346,7 @@ def test_autocomp_flag_choices_completion(ac_app, flag, text, completions):
else:
assert first_match is None
- assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.default_sort_key)
@pytest.mark.parametrize('pos, text, completions', [
@@ -369,7 +369,7 @@ def test_autocomp_positional_choices_completion(ac_app, pos, text, completions):
else:
assert first_match is None
- assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.default_sort_key)
@pytest.mark.parametrize('flag, text, completions', [
@@ -389,7 +389,7 @@ def test_autocomp_flag_completers(ac_app, flag, text, completions):
else:
assert first_match is None
- assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.default_sort_key)
@pytest.mark.parametrize('pos, text, completions', [
@@ -410,7 +410,7 @@ def test_autocomp_positional_completers(ac_app, pos, text, completions):
else:
assert first_match is None
- assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.default_sort_key)
def test_autocomp_blank_token(ac_app):
@@ -548,7 +548,7 @@ def test_autcomp_nargs(ac_app, args, completions):
else:
assert first_match is None
- assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.default_sort_key)
@pytest.mark.parametrize('command_and_args, text, is_error', [
diff --git a/tests/test_completion.py b/tests/test_completion.py
index 1411cc49..3cee1955 100644
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -174,20 +174,20 @@ def test_complete_macro(base_app, request):
assert first_match is not None and base_app.completion_matches == expected
-def test_matches_sort_key(cmd2_app):
+def test_default_sort_key(cmd2_app):
text = ''
line = 'test_sort_key {}'.format(text)
endidx = len(line)
begidx = endidx - len(text)
# First do alphabetical sorting
- cmd2_app.matches_sort_key = cmd2.cmd2.ALPHABETICAL_SORT_KEY
+ cmd2_app.default_sort_key = cmd2.cmd2.ALPHABETICAL_SORT_KEY
expected = ['1', '11', '2']
first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
assert first_match is not None and cmd2_app.completion_matches == expected
# Now switch to natural sorting
- cmd2_app.matches_sort_key = cmd2.cmd2.NATURAL_SORT_KEY
+ cmd2_app.default_sort_key = cmd2.cmd2.NATURAL_SORT_KEY
expected = ['1', '2', '11']
first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
assert first_match is not None and cmd2_app.completion_matches == expected
diff --git a/tests/test_history.py b/tests/test_history.py
index add93ea6..88f38172 100644
--- a/tests/test_history.py
+++ b/tests/test_history.py
@@ -276,7 +276,7 @@ def parser():
'l': '!ls -al',
'anothermultiline': 'multiline',
'fake': 'run_pyscript'},
- shortcuts=[('?', 'help'), ('!', 'shell')]
+ shortcuts={'?': 'help', '!': 'shell'}
)
return parser
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index 13a535c0..a629d9fa 100644
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -21,7 +21,7 @@ def parser():
'l': '!ls -al',
'anothermultiline': 'multiline',
'fake': 'run_pyscript'},
- shortcuts=[('?', 'help'), ('!', 'shell')]
+ shortcuts={'?': 'help', '!': 'shell'}
)
return parser
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 262e6c54..edb6ca68 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -234,7 +234,8 @@ def test_proc_reader_send_sigint(pr_none):
else:
assert ret_code == -signal.SIGINT
-@pytest.mark.skipif(sys.platform == 'linux', reason="Test doesn't work correctly on TravisCI")
+@pytest.mark.skipif(not sys.platform.startswith('win'),
+ reason="Test doesn't work correctly on TravisCI and is unreliable on Azure DevOps macOS")
def test_proc_reader_terminate(pr_none):
assert pr_none._proc.poll() is None
pr_none.terminate()