Skip to content

Commit 81ad085

Browse files
committed
Merged master into transcript_fixes branch and resolved conflicts
2 parents 85a21ef + 61d5703 commit 81ad085

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3244
-1245
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ htmlcov
2525
# mypy plugin for PyCharm
2626
dmypy.json
2727
dmypy.sock
28+
29+
# cmd2 history file used in main.py
30+
cmd2_history.txt

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ matrix:
1414
python: 3.6
1515
env: TOXENV=py36
1616
- os: linux
17-
python: 3.7-dev
17+
python: 3.7
18+
dist: xenial
19+
sudo: true # Travis CI doesn't yet support official (non-development) Python 3.7 on container-based builds
1820
env: TOXENV=py37
1921
- os: linux
2022
python: 3.5

CHANGELOG.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,37 @@
22
* Bug Fixes
33
* Fixed bug where ``get_all_commands`` could return non-callable attributes
44
* Fixed bug where **alias** command was dropping quotes around arguments
5+
* Fixed bug where running help on argparse commands didn't work if they didn't support -h
56
* Enhancements
67
* Added ``exit_code`` attribute of ``cmd2.Cmd`` class
78
* Enables applications to return a non-zero exit code when exiting from ``cmdloop``
89
* ``ACHelpFormatter`` now inherits from ``argparse.RawTextHelpFormatter`` to make it easier
910
for formatting help/description text
11+
* Aliases are now sorted alphabetically
12+
* The **set** command now tab-completes settable parameter names
13+
* Added ``async_alert``, ``async_update_prompt``, and ``set_window_title`` functions
14+
* These allow you to provide feedback to the user in an asychronous fashion, meaning alerts can
15+
display when the user is still entering text at the prompt. See [async_printing.py](https://github.com/python-cmd2/cmd2/blob/master/examples/async_printing.py)
16+
for an example.
17+
* Cross-platform colored output support
18+
* ``colorama`` gets initialized properly in ``Cmd.__init()``
19+
* The ``Cmd.colors`` setting is no longer platform dependent and now has three values:
20+
* Terminal (default) - output methods do not strip any ANSI escape sequences when output is a terminal, but
21+
if the output is a pipe or a file the escape sequences are stripped
22+
* Always - output methods **never** strip ANSI escape sequences, regardless of the output destination
23+
* Never - output methods strip all ANSI escape sequences
24+
* Added ``macro`` command to create macros, which are similar to aliases, but can take arguments when called
25+
* All cmd2 command functions have been converted to use argparse.
26+
* Deprecations
27+
* Deprecated the built-in ``cmd2`` support for colors including ``Cmd.colorize()`` and ``Cmd._colorcodes``
28+
* Deletions (potentially breaking changes)
29+
* The ``preparse``, ``postparsing_precmd``, and ``postparsing_postcmd`` methods *deprecated* in the previous release
30+
have been deleted
31+
* The new application lifecycle hook system allows for registration of callbacks to be called at various points
32+
in the lifecycle and is more powerful and flexible than the previous system
33+
* ``alias`` is now a command with subcommands to create, list, and delete aliases. Therefore its syntax
34+
has changed. All current alias commands in startup scripts or transcripts will break with this release.
35+
* `unalias` was deleted since ``alias delete`` replaced it
1036

1137
## 0.9.4 (August 21, 2018)
1238
* Bug Fixes
@@ -118,7 +144,7 @@
118144
* Fixed ``AttributeError`` on Windows when running a ``select`` command cause by **pyreadline** not implementing ``remove_history_item``
119145
* Enhancements
120146
* Added warning about **libedit** variant of **readline** not being supported on macOS
121-
* Added tab-completion of alias names in value filed of **alias** command
147+
* Added tab-completion of alias names in value field of **alias** command
122148
* Enhanced the ``py`` console in the following ways
123149
* Added tab completion of Python identifiers instead of **cmd2** commands
124150
* Separated the ``py`` console history from the **cmd2** history

CONTRIBUTING.md

Lines changed: 120 additions & 121 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ applications. It provides a simple API which is an extension of Python's built-
1414
of cmd to make your life easier and eliminates much of the boilerplate code which would be necessary
1515
when using cmd.
1616

17-
[![Screenshot](cmd2.png)](https://github.com/python-cmd2/cmd2/blob/master/cmd2.png)
18-
17+
Click on image below to watch a short video demonstrating the capabilities of cmd2:
18+
[![Screenshot](cmd2.png)](https://youtu.be/DDU_JH6cFsA)
1919

2020
Main Features
2121
-------------
@@ -31,6 +31,7 @@ Main Features
3131
- Multi-line commands
3232
- Special-character command shortcuts (beyond cmd's `?` and `!`)
3333
- Command aliasing similar to bash `alias` command
34+
- Macros, which are similar to aliases, but can take arguments when called
3435
- Ability to load commands at startup from an initialization script
3536
- Settable environment parameters
3637
- Parsing commands with arguments using `argparse`, including support for sub-commands
@@ -40,6 +41,7 @@ Main Features
4041
- Trivial to provide built-in help for all commands
4142
- Built-in regression testing framework for your applications (transcript-based testing)
4243
- Transcripts for use with built-in regression can be automatically generated from `history -t`
44+
- Alerts that seamlessly print while user enters text at prompt
4345

4446
Python 2.7 support is EOL
4547
-------------------------
@@ -259,3 +261,15 @@ timing: False
259261
Note how a regular expression `/(True|False)/` is used for output of the **show color** command since
260262
colored text is currently not available for cmd2 on Windows. Regular expressions can be used anywhere within a
261263
transcript file simply by enclosing them within forward slashes, `/`.
264+
265+
266+
Found a bug?
267+
------------
268+
269+
If you think you've found a bug, please first read through the open [Issues](https://github.com/python-cmd2/cmd2/issues). If you're confident it's a new bug, go ahead and create a new GitHub issue. Be sure to include as much information as possible so we can reproduce the bug. At a minimum, please state the following:
270+
271+
* ``cmd2`` version
272+
* Python version
273+
* OS name and version
274+
* What you did to cause the bug to occur
275+
* Include any traceback or error message associated with the bug

cmd2.png

17.5 KB
Loading

cmd2/argcomplete_bridge.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,18 @@
2323
import os
2424
import shlex
2525
import sys
26+
from typing import List, Tuple, Union
2627

2728
from . import constants
2829
from . import utils
2930

3031

31-
def tokens_for_completion(line, endidx):
32+
def tokens_for_completion(line: str, endidx: int) -> Union[Tuple[List[str], List[str], int, int],
33+
Tuple[None, None, None, None]]:
3234
"""
3335
Used by tab completion functions to get all tokens through the one being completed
34-
:param line: str - the current input line with leading whitespace removed
35-
:param endidx: int - the ending index of the prefix text
36+
:param line: the current input line with leading whitespace removed
37+
:param endidx: the ending index of the prefix text
3638
:return: A 4 item tuple where the items are
3739
On Success
3840
tokens: list of unquoted tokens
@@ -46,7 +48,7 @@ def tokens_for_completion(line, endidx):
4648
The last item in both lists is the token being tab completed
4749
4850
On Failure
49-
Both items are None
51+
All 4 items are None
5052
"""
5153
unclosed_quote = ''
5254
quotes_to_try = copy.copy(constants.QUOTES)

cmd2/argparse_completer.py

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,18 @@ def complete_command_help(self, tokens: List[str], text: str, line: str, begidx:
510510
return self.basic_complete(text, line, begidx, endidx, completers.keys())
511511
return []
512512

513+
def format_help(self, tokens: List[str]) -> str:
514+
"""Supports the completion of sub-commands for commands through the cmd2 help command."""
515+
for idx, token in enumerate(tokens):
516+
if idx >= self._token_start_index:
517+
if self._positional_completers:
518+
# For now argparse only allows 1 sub-command group per level
519+
# so this will only loop once.
520+
for completers in self._positional_completers.values():
521+
if token in completers:
522+
return completers[token].format_help(tokens)
523+
return self._parser.format_help()
524+
513525
@staticmethod
514526
def _process_action_nargs(action: argparse.Action, arg_state: _ArgumentState) -> None:
515527
if isinstance(action, _RangeAction):
@@ -654,12 +666,19 @@ def _print_action_help(self, action: argparse.Action) -> None:
654666
else:
655667
prefix = ''
656668

669+
if action.help is None:
670+
help_text = ''
671+
else:
672+
help_text = action.help
673+
674+
# is there anything to print for this parameter?
675+
if not prefix and not help_text:
676+
return
677+
657678
prefix = ' {0: <{width}} '.format(prefix, width=20)
658679
pref_len = len(prefix)
659-
if action.help is not None:
660-
help_lines = action.help.splitlines()
661-
else:
662-
help_lines = ['']
680+
help_lines = help_text.splitlines()
681+
663682
if len(help_lines) == 1:
664683
print('\nHint:\n{}{}\n'.format(prefix, help_lines[0]))
665684
else:
@@ -676,12 +695,12 @@ def basic_complete(text: str, line: str, begidx: int, endidx: int, match_against
676695
"""
677696
Performs tab completion against a list
678697
679-
:param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
680-
:param line: str - the current input line with leading whitespace removed
681-
:param begidx: int - the beginning index of the prefix text
682-
:param endidx: int - the ending index of the prefix text
683-
:param match_against: Collection - the list being matched against
684-
:return: List[str] - a list of possible tab completions
698+
:param text: the string prefix we are attempting to match (all returned matches must begin with it)
699+
:param line: the current input line with leading whitespace removed
700+
:param begidx: the beginning index of the prefix text
701+
:param endidx: the ending index of the prefix text
702+
:param match_against: the list being matched against
703+
:return: a list of possible tab completions
685704
"""
686705
return [cur_match for cur_match in match_against if cur_match.startswith(text)]
687706

@@ -731,7 +750,7 @@ def _format_usage(self, usage, actions, groups, prefix) -> str:
731750

732751
# build full usage string
733752
format = self._format_actions_usage
734-
action_usage = format(positionals + required_options + optionals, groups)
753+
action_usage = format(required_options + optionals + positionals, groups)
735754
usage = ' '.join([s for s in [prog, action_usage] if s])
736755

737756
# wrap the usage parts if it's too long
@@ -742,15 +761,15 @@ def _format_usage(self, usage, actions, groups, prefix) -> str:
742761

743762
# break usage into wrappable parts
744763
part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
764+
req_usage = format(required_options, groups)
745765
opt_usage = format(optionals, groups)
746766
pos_usage = format(positionals, groups)
747-
req_usage = format(required_options, groups)
767+
req_parts = _re.findall(part_regexp, req_usage)
748768
opt_parts = _re.findall(part_regexp, opt_usage)
749769
pos_parts = _re.findall(part_regexp, pos_usage)
750-
req_parts = _re.findall(part_regexp, req_usage)
770+
assert ' '.join(req_parts) == req_usage
751771
assert ' '.join(opt_parts) == opt_usage
752772
assert ' '.join(pos_parts) == pos_usage
753-
assert ' '.join(req_parts) == req_usage
754773

755774
# End cmd2 customization
756775

@@ -780,13 +799,15 @@ def get_lines(parts, indent, prefix=None):
780799
if len(prefix) + len(prog) <= 0.75 * text_width:
781800
indent = ' ' * (len(prefix) + len(prog) + 1)
782801
# Begin cmd2 customization
783-
if opt_parts:
784-
lines = get_lines([prog] + pos_parts, indent, prefix)
785-
lines.extend(get_lines(req_parts, indent))
802+
if req_parts:
803+
lines = get_lines([prog] + req_parts, indent, prefix)
786804
lines.extend(get_lines(opt_parts, indent))
805+
lines.extend(get_lines(pos_parts, indent))
806+
elif opt_parts:
807+
lines = get_lines([prog] + opt_parts, indent, prefix)
808+
lines.extend(get_lines(pos_parts, indent))
787809
elif pos_parts:
788810
lines = get_lines([prog] + pos_parts, indent, prefix)
789-
lines.extend(get_lines(req_parts, indent))
790811
else:
791812
lines = [prog]
792813
# End cmd2 customization
@@ -795,13 +816,13 @@ def get_lines(parts, indent, prefix=None):
795816
else:
796817
indent = ' ' * len(prefix)
797818
# Begin cmd2 customization
798-
parts = pos_parts + req_parts + opt_parts
819+
parts = req_parts + opt_parts + pos_parts
799820
lines = get_lines(parts, indent)
800821
if len(lines) > 1:
801822
lines = []
802-
lines.extend(get_lines(pos_parts, indent))
803823
lines.extend(get_lines(req_parts, indent))
804824
lines.extend(get_lines(opt_parts, indent))
825+
lines.extend(get_lines(pos_parts, indent))
805826
# End cmd2 customization
806827
lines = [prog] + lines
807828

@@ -870,6 +891,9 @@ def _format_args(self, action, default_metavar) -> str:
870891
result = super()._format_args(action, default_metavar)
871892
return result
872893

894+
def format_help(self):
895+
return super().format_help() + '\n'
896+
873897

874898
# noinspection PyCompatibility
875899
class ACArgumentParser(argparse.ArgumentParser):

0 commit comments

Comments
 (0)