Skip to content

Commit bfca4e9

Browse files
committed
Merge branch 'master' into 3.0.0
2 parents 9f1d162 + abd8bdf commit bfca4e9

File tree

9 files changed

+298
-155
lines changed

9 files changed

+298
-155
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
# Set fetch-depth: 0 to fetch all history for all branches and tags.
2020
fetch-depth: 0 # Needed for setuptools_scm to work correctly
2121
- name: Install uv
22-
uses: astral-sh/setup-uv@v3
22+
uses: astral-sh/setup-uv@v4
2323

2424
- name: Set up Python ${{ matrix.python-version }}
2525
uses: actions/setup-python@v5

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
* Added `RawDescriptionCmd2HelpFormatter`, `RawTextCmd2HelpFormatter`, `ArgumentDefaultsCmd2HelpFormatter`,
1111
and `MetavarTypeCmd2HelpFormatter` and they all use `rich-argparse`.
1212

13+
## 2.5.7 (November 22, 2024)
14+
* Bug Fixes
15+
* Fixed issue where argument parsers for overridden commands were not being created.
16+
* Fixed issue where `Cmd.ppaged()` was not writing to the passed in destination.
17+
1318
## 2.5.6 (November 14, 2024)
1419
* Bug Fixes
1520
* Fixed type hint for `with_default_category` decorator which caused type checkers to mistype

cmd2/cmd2.py

Lines changed: 194 additions & 132 deletions
Large diffs are not rendered by default.

cmd2/decorators.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,9 @@ def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> Optional[bool]:
343343
statement, parsed_arglist = cmd2_app.statement_parser.get_command_arg_list(
344344
command_name, statement_arg, preserve_quotes
345345
)
346-
arg_parser = cmd2_app._command_parsers.get(command_name, None)
346+
347+
# Pass cmd_wrapper instead of func, since it contains the parser info.
348+
arg_parser = cmd2_app._command_parsers.get(cmd_wrapper)
347349
if arg_parser is None:
348350
# This shouldn't be possible to reach
349351
raise ValueError(f'No argument parser found for {command_name}') # pragma: no cover

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ dev-dependencies = [
319319
"pytest",
320320
"pytest-cov",
321321
"pytest-mock",
322+
"ruff",
322323
"sphinx",
323324
"sphinx-autobuild",
324325
"sphinx-rtd-theme",

tests/test_argparse.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import cmd2
1515

1616
from .conftest import (
17-
find_subcommand,
1817
run_cmd,
1918
)
2019

@@ -386,8 +385,7 @@ def test_add_another_subcommand(subcommand_app):
386385
This tests makes sure _set_parser_prog() sets _prog_prefix on every _SubParsersAction so that all future calls
387386
to add_parser() write the correct prog value to the parser being added.
388387
"""
389-
base_parser = subcommand_app._command_parsers.get('base')
390-
find_subcommand(subcommand_app._command_parsers.get('base'), [])
388+
base_parser = subcommand_app._command_parsers.get(subcommand_app.do_base)
391389
for sub_action in base_parser._actions:
392390
if isinstance(sub_action, argparse._SubParsersAction):
393391
new_parser = sub_action.add_parser('new_sub', help='stuff')

tests/test_cmd2.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,13 @@ def do_multiline_docstr(self, arg):
12101210
"""
12111211
pass
12121212

1213+
parser_cmd_parser = cmd2.Cmd2ArgumentParser(description="This is the description.")
1214+
1215+
@cmd2.with_argparser(parser_cmd_parser)
1216+
def do_parser_cmd(self, args):
1217+
"""This is the docstring."""
1218+
pass
1219+
12131220

12141221
@pytest.fixture
12151222
def help_app():
@@ -1249,6 +1256,11 @@ def test_help_multiline_docstring(help_app):
12491256
assert help_app.last_result is True
12501257

12511258

1259+
def test_help_verbose_uses_parser_description(help_app: HelpApp):
1260+
out, err = run_cmd(help_app, 'help --verbose')
1261+
verify_help_text(help_app, out, verbose_strings=[help_app.parser_cmd_parser.description])
1262+
1263+
12521264
class HelpCategoriesApp(cmd2.Cmd):
12531265
"""Class for testing custom help_* methods which override docstring help."""
12541266

@@ -2224,20 +2236,6 @@ def test_ppaged(outsim_app):
22242236
assert out == msg + end
22252237

22262238

2227-
def test_ppaged_blank(outsim_app):
2228-
msg = ''
2229-
outsim_app.ppaged(msg)
2230-
out = outsim_app.stdout.getvalue()
2231-
assert not out
2232-
2233-
2234-
def test_ppaged_none(outsim_app):
2235-
msg = None
2236-
outsim_app.ppaged(msg)
2237-
out = outsim_app.stdout.getvalue()
2238-
assert not out
2239-
2240-
22412239
@with_ansi_style(ansi.AllowStyle.TERMINAL)
22422240
def test_ppaged_strips_ansi_when_redirecting(outsim_app):
22432241
msg = 'testing...'
@@ -2771,3 +2769,26 @@ def test_columnize_too_wide(outsim_app):
27712769

27722770
expected = "\n".join(str_list) + "\n"
27732771
assert outsim_app.stdout.getvalue() == expected
2772+
2773+
2774+
def test_command_parser_retrieval(outsim_app: cmd2.Cmd):
2775+
# Pass something that isn't a method
2776+
not_a_method = "just a string"
2777+
assert outsim_app._command_parsers.get(not_a_method) is None
2778+
2779+
# Pass a non-command method
2780+
assert outsim_app._command_parsers.get(outsim_app.__init__) is None
2781+
2782+
2783+
def test_command_synonym_parser():
2784+
# Make sure a command synonym returns the same parser as what it aliases
2785+
class SynonymApp(cmd2.cmd2.Cmd):
2786+
do_synonym = cmd2.cmd2.Cmd.do_help
2787+
2788+
app = SynonymApp()
2789+
2790+
synonym_parser = app._command_parsers.get(app.do_synonym)
2791+
help_parser = app._command_parsers.get(app.do_help)
2792+
2793+
assert synonym_parser is not None
2794+
assert synonym_parser is help_parser

tests/transcripts/from_cmdloop.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# so you can see where they are.
33

44
(Cmd) help say
5-
Usage: say [-h] [-p] [-s] [-r REPEAT]/ */
5+
Usage: speak [-h] [-p] [-s] [-r REPEAT]/ */
66

77
Repeats what you tell me to./ */
88

tests_isolated/test_commandset/test_commandset.py

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,52 @@ def test_autoload_commands(command_sets_app):
146146
assert 'Command Set B' not in cmds_cats
147147

148148

149+
def test_command_synonyms():
150+
"""Test the use of command synonyms in CommandSets"""
151+
152+
class SynonymCommandSet(cmd2.CommandSet):
153+
def __init__(self, arg1):
154+
super().__init__()
155+
self._arg1 = arg1
156+
157+
@cmd2.with_argparser(cmd2.Cmd2ArgumentParser(description="Native Command"))
158+
def do_builtin(self, _):
159+
pass
160+
161+
# Create a synonym to a command inside of this CommandSet
162+
do_builtin_synonym = do_builtin
163+
164+
# Create a synonym to a command outside of this CommandSet with subcommands.
165+
# This will best test the synonym check in cmd2.Cmd._check_uninstallable() when
166+
# we unresgister this CommandSet.
167+
do_alias_synonym = cmd2.Cmd.do_alias
168+
169+
cs = SynonymCommandSet("foo")
170+
app = WithCommandSets(command_sets=[cs])
171+
172+
# Make sure the synonyms have the same parser as what they alias
173+
builtin_parser = app._command_parsers.get(app.do_builtin)
174+
builtin_synonym_parser = app._command_parsers.get(app.do_builtin_synonym)
175+
assert builtin_parser is not None
176+
assert builtin_parser is builtin_synonym_parser
177+
178+
alias_parser = app._command_parsers.get(cmd2.Cmd.do_alias)
179+
alias_synonym_parser = app._command_parsers.get(app.do_alias_synonym)
180+
assert alias_parser is not None
181+
assert alias_parser is alias_synonym_parser
182+
183+
# Unregister the CommandSet and make sure built-in command and synonyms are gone
184+
app.unregister_command_set(cs)
185+
assert not hasattr(app, "do_builtin")
186+
assert not hasattr(app, "do_builtin_synonym")
187+
assert not hasattr(app, "do_alias_synonym")
188+
189+
# Make sure the alias command still exists, has the same parser, and works.
190+
assert alias_parser is app._command_parsers.get(cmd2.Cmd.do_alias)
191+
out, err = run_cmd(app, 'alias --help')
192+
assert normalize(alias_parser.format_help())[0] in out
193+
194+
149195
def test_custom_construct_commandsets():
150196
command_set_b = CommandSetB('foo')
151197

@@ -288,7 +334,7 @@ def test_load_commandset_errors(command_sets_manual, capsys):
288334
cmd_set = CommandSetA()
289335

290336
# create a conflicting command before installing CommandSet to verify rollback behavior
291-
command_sets_manual._install_command_function('durian', cmd_set.do_durian)
337+
command_sets_manual._install_command_function('do_durian', cmd_set.do_durian)
292338
with pytest.raises(CommandSetRegistrationError):
293339
command_sets_manual.register_command_set(cmd_set)
294340

@@ -313,13 +359,21 @@ def test_load_commandset_errors(command_sets_manual, capsys):
313359
assert "Deleting alias 'apple'" in err
314360
assert "Deleting alias 'banana'" in err
315361

362+
# verify command functions which don't start with "do_" raise an exception
363+
with pytest.raises(CommandSetRegistrationError):
364+
command_sets_manual._install_command_function('new_cmd', cmd_set.do_banana)
365+
366+
# verify methods which don't start with "do_" raise an exception
367+
with pytest.raises(CommandSetRegistrationError):
368+
command_sets_manual._install_command_function('do_new_cmd', cmd_set.on_register)
369+
316370
# verify duplicate commands are detected
317371
with pytest.raises(CommandSetRegistrationError):
318-
command_sets_manual._install_command_function('banana', cmd_set.do_banana)
372+
command_sets_manual._install_command_function('do_banana', cmd_set.do_banana)
319373

320374
# verify bad command names are detected
321375
with pytest.raises(CommandSetRegistrationError):
322-
command_sets_manual._install_command_function('bad command', cmd_set.do_banana)
376+
command_sets_manual._install_command_function('do_bad command', cmd_set.do_banana)
323377

324378
# verify error conflict with existing completer function
325379
with pytest.raises(CommandSetRegistrationError):

0 commit comments

Comments
 (0)