Skip to content

Commit 907fa3a

Browse files
committed
Did some refactoring and updated comments.
1 parent 9416464 commit 907fa3a

2 files changed

Lines changed: 91 additions & 76 deletions

File tree

cmd2/cmd2.py

Lines changed: 83 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,7 @@
4141
import threading
4242
import time
4343
from code import InteractiveConsole
44-
from collections import (
45-
deque,
46-
namedtuple,
47-
)
44+
from collections import deque
4845
from collections.abc import (
4946
Callable,
5047
Iterable,
@@ -61,6 +58,7 @@
6158
TYPE_CHECKING,
6259
Any,
6360
ClassVar,
61+
NamedTuple,
6462
TextIO,
6563
TypeVar,
6664
cast,
@@ -219,8 +217,15 @@ def __init__(self) -> None:
219217
self.completer: Callable[[str, int], str | None] | None = None
220218

221219

222-
# Contains data about a disabled command which is used to restore its original functions when the command is enabled
223-
DisabledCommand = namedtuple("DisabledCommand", ["command_function", "help_function", "completer_function"]) # noqa: PYI024
220+
class DisabledCommand(NamedTuple):
221+
"""Stores data about a disabled command.
222+
223+
This data is used to restore its functions when the command is enabled.
224+
"""
225+
226+
command_func: BoundCommandFunc
227+
help_func: Callable[[], Any] | None
228+
completer_func: BoundCompleter | None
224229

225230

226231
class CommandParsers:
@@ -2448,12 +2453,12 @@ def _perform_completion(
24482453
completer_func = func_attr
24492454
else:
24502455
# There's no completer function, next see if the command uses argparse
2451-
func = self.get_command_func(command)
2452-
argparser = None if func is None else self.command_parsers.get(func)
2456+
command_func = self.get_command_func(command)
2457+
argparser = None if command_func is None else self.command_parsers.get(command_func)
24532458

2454-
if func is not None and argparser is not None:
2459+
if command_func is not None and argparser is not None:
24552460
# Get arguments for complete()
2456-
preserve_quotes = getattr(func, constants.CMD_ATTR_PRESERVE_QUOTES)
2461+
preserve_quotes = getattr(command_func, constants.CMD_ATTR_PRESERVE_QUOTES)
24572462
cmd_set = self.find_commandset_for_command(command)
24582463

24592464
# Create the argparse completer
@@ -2718,9 +2723,8 @@ def _get_commands_aliases_and_macros_choices(self) -> Choices:
27182723

27192724
# Add commands
27202725
for command in self.get_visible_commands():
2721-
# Get the command method
2722-
func = getattr(self, constants.COMMAND_FUNC_PREFIX + command)
2723-
description = strip_doc_annotations(func.__doc__).splitlines()[0] if func.__doc__ else ""
2726+
command_func = cast(BoundCommandFunc, self.get_command_func(command))
2727+
description = strip_doc_annotations(command_func.__doc__).splitlines()[0] if command_func.__doc__ else ""
27242728
items.append(CompletionItem(command, display_meta=description))
27252729

27262730
# Add aliases
@@ -3328,9 +3332,9 @@ def get_command_func(self, command: str) -> BoundCommandFunc | None:
33283332
:param command: the name of the command
33293333
:return: the bound function implementing the command, or None if not found
33303334
"""
3331-
func_name = constants.COMMAND_FUNC_PREFIX + command
3332-
func = getattr(self, func_name, None)
3333-
return cast(BoundCommandFunc, func) if callable(func) else None
3335+
command_func_name = constants.COMMAND_FUNC_PREFIX + command
3336+
command_func = getattr(self, command_func_name, None)
3337+
return cast(BoundCommandFunc, command_func) if callable(command_func) else None
33343338

33353339
def _get_command_category(self, func: BoundCommandFunc) -> str:
33363340
"""Determine the category for a command.
@@ -3363,8 +3367,8 @@ def onecmd(self, statement: Statement | str, *, add_to_history: bool = True) ->
33633367
if not isinstance(statement, Statement):
33643368
statement = self._input_line_to_statement(statement)
33653369

3366-
func = self.get_command_func(statement.command)
3367-
if func:
3370+
command_func = self.get_command_func(statement.command)
3371+
if command_func:
33683372
# Check to see if this command should be stored in history
33693373
if (
33703374
statement.command not in self.exclude_from_history
@@ -3375,7 +3379,7 @@ def onecmd(self, statement: Statement | str, *, add_to_history: bool = True) ->
33753379

33763380
try:
33773381
self.current_command = statement
3378-
stop = func(statement)
3382+
stop = command_func(statement)
33793383
finally:
33803384
self.current_command = None
33813385

@@ -4219,7 +4223,9 @@ def complete_help_subcommands(
42194223
return Completions()
42204224

42214225
# Check if this command uses argparse
4222-
if (func := self.get_command_func(command)) is None or (argparser := self.command_parsers.get(func)) is None:
4226+
if (command_func := self.get_command_func(command)) is None or (
4227+
argparser := self.command_parsers.get(command_func)
4228+
) is None:
42234229
return Completions()
42244230

42254231
completer = argparse_completer.DEFAULT_AP_COMPLETER(argparser, self)
@@ -4245,8 +4251,8 @@ def _build_command_info(self) -> tuple[dict[str, list[str]], list[str]]:
42454251
help_topics.remove(command)
42464252

42474253
# Store the command within its category
4248-
func = cast(BoundCommandFunc, self.get_command_func(command))
4249-
category = self._get_command_category(func)
4254+
command_func = cast(BoundCommandFunc, self.get_command_func(command))
4255+
category = self._get_command_category(command_func)
42504256
cmds_cats.setdefault(category, []).append(command)
42514257

42524258
return cmds_cats, help_topics
@@ -4320,16 +4326,16 @@ def do_help(self, args: argparse.Namespace) -> None:
43204326
if help_func is not None:
43214327
help_func()
43224328
else:
4323-
# This is a defensive fallback in case someone disabled a command
4324-
# without using disable_command() resulting in no help function.
4329+
# Handle potential case where command is disabled by manually editing
4330+
# self.disabled_commands instead of using disable_command().
43254331
self._report_disabled_command_usage(message_to_print=f"{args.command} is currently disabled.")
43264332
return
43274333

4328-
func = self.get_command_func(args.command)
4329-
argparser = None if func is None else self.command_parsers.get(func)
4334+
command_func = self.get_command_func(args.command)
4335+
argparser = None if command_func is None else self.command_parsers.get(command_func)
43304336

43314337
# If the command function uses argparse, then use argparse's help
4332-
if func is not None and argparser is not None:
4338+
if command_func is not None and argparser is not None:
43334339
completer = argparse_completer.DEFAULT_AP_COMPLETER(argparser, self)
43344340
completer.print_help(args.subcommands, self.stdout)
43354341

@@ -4338,8 +4344,8 @@ def do_help(self, args: argparse.Namespace) -> None:
43384344
help_func()
43394345

43404346
# If the command function has a docstring, then print it
4341-
elif func is not None and func.__doc__ is not None:
4342-
self.poutput(pydoc.getdoc(func))
4347+
elif command_func is not None and command_func.__doc__ is not None:
4348+
self.poutput(pydoc.getdoc(command_func))
43434349

43444350
# If there is no help information then print an error
43454351
else:
@@ -4380,15 +4386,15 @@ def print_topics(self, header: str, cmds: Sequence[str] | None, cmdlen: int, max
43804386
self.columnize(cmds, maxcol)
43814387
self.poutput()
43824388

4383-
def _print_documented_command_topics(self, header: str, cmds: Sequence[str], verbose: bool) -> None:
4389+
def _print_documented_command_topics(self, header: str, commands: Sequence[str], verbose: bool) -> None:
43844390
"""Print topics which are documented commands, switching between verbose or traditional output."""
43854391
import io
43864392

4387-
if not cmds:
4393+
if not commands:
43884394
return
43894395

43904396
if not verbose:
4391-
self.print_topics(header, cmds, 15, 80)
4397+
self.print_topics(header, commands, 15, 80)
43924398
return
43934399

43944400
topic_table = Cmd2SimpleTable(
@@ -4398,8 +4404,8 @@ def _print_documented_command_topics(self, header: str, cmds: Sequence[str], ver
43984404

43994405
# Try to get the documentation string for each command
44004406
topics = self.get_help_topics()
4401-
for command in cmds:
4402-
if (cmd_func := self.get_command_func(command)) is None:
4407+
for command in commands:
4408+
if (command_func := self.get_command_func(command)) is None:
44034409
continue
44044410

44054411
doc: str | None
@@ -4424,7 +4430,7 @@ def _print_documented_command_topics(self, header: str, cmds: Sequence[str], ver
44244430
doc = result.getvalue()
44254431

44264432
else:
4427-
doc = cmd_func.__doc__
4433+
doc = command_func.__doc__
44284434

44294435
# Attempt to locate the first documentation block
44304436
cmd_desc = strip_doc_annotations(doc) if doc else ""
@@ -5593,25 +5599,25 @@ def enable_command(self, command: str) -> None:
55935599
if command not in self.disabled_commands:
55945600
return
55955601

5596-
cmd_func_name = constants.COMMAND_FUNC_PREFIX + command
5602+
command_func_name = constants.COMMAND_FUNC_PREFIX + command
55975603
help_func_name = constants.HELP_FUNC_PREFIX + command
55985604
completer_func_name = constants.COMPLETER_FUNC_PREFIX + command
55995605

56005606
# Restore the command function to its original value
56015607
dc = self.disabled_commands[command]
5602-
setattr(self, cmd_func_name, dc.command_function)
5608+
setattr(self, command_func_name, dc.command_func)
56035609

56045610
# Restore the help function to its original value
5605-
if dc.help_function is None:
5611+
if dc.help_func is None:
56065612
delattr(self, help_func_name)
56075613
else:
5608-
setattr(self, help_func_name, dc.help_function)
5614+
setattr(self, help_func_name, dc.help_func)
56095615

56105616
# Restore the completer function to its original value
5611-
if dc.completer_function is None:
5617+
if dc.completer_func is None:
56125618
delattr(self, completer_func_name)
56135619
else:
5614-
setattr(self, completer_func_name, dc.completer_function)
5620+
setattr(self, completer_func_name, dc.completer_func)
56155621

56165622
# Remove the disabled command entry
56175623
del self.disabled_commands[command]
@@ -5625,15 +5631,15 @@ def enable_category(self, category: str) -> None:
56255631
if category not in self.disabled_categories:
56265632
return
56275633

5628-
for cmd_name in list(self.disabled_commands):
5629-
func = self.disabled_commands[cmd_name].command_function
5630-
if self._get_command_category(func) == category:
5631-
self.enable_command(cmd_name)
5634+
for command in list(self.disabled_commands):
5635+
command_func = self.disabled_commands[command].command_func
5636+
if self._get_command_category(command_func) == category:
5637+
self.enable_command(command)
56325638

56335639
del self.disabled_categories[category]
56345640

56355641
def disable_command(self, command: str, message_to_print: str) -> None:
5636-
"""Disable a command and overwrite its functions.
5642+
"""Disable a command and replace its functions with disabled versions.
56375643
56385644
:param command: the command being disabled
56395645
:param message_to_print: what to print when this command is run or help is called on it while disabled
@@ -5647,41 +5653,48 @@ def disable_command(self, command: str, message_to_print: str) -> None:
56475653
return
56485654

56495655
# Make sure this is an actual command
5650-
command_function = self.get_command_func(command)
5651-
if command_function is None:
5656+
command_func = self.get_command_func(command)
5657+
if command_func is None:
56525658
raise AttributeError(f"'{command}' does not refer to a command")
56535659

5654-
cmd_func_name = constants.COMMAND_FUNC_PREFIX + command
5660+
command_func_name = constants.COMMAND_FUNC_PREFIX + command
5661+
56555662
help_func_name = constants.HELP_FUNC_PREFIX + command
5663+
help_func = getattr(self, help_func_name, None)
5664+
56565665
completer_func_name = constants.COMPLETER_FUNC_PREFIX + command
5666+
completer_func = getattr(self, completer_func_name, None)
56575667

56585668
# Add the disabled command record
56595669
self.disabled_commands[command] = DisabledCommand(
5660-
command_function=command_function,
5661-
help_function=getattr(self, help_func_name, None),
5662-
completer_function=getattr(self, completer_func_name, None),
5670+
command_func=command_func,
5671+
help_func=help_func,
5672+
completer_func=completer_func,
56635673
)
56645674

5665-
# Overwrite command function to print the message
5675+
# Replace command and help functions to report the disabled message
56665676
message_to_print = message_to_print.replace(constants.COMMAND_NAME, command)
5667-
new_cmd_func = functools.partial(self._report_disabled_command_usage, message_to_print=message_to_print)
5677+
new_cmd_func = functools.partial(
5678+
self._report_disabled_command_usage,
5679+
message_to_print=message_to_print,
5680+
)
56685681

5669-
# Preserve the metadata of the original command function
5670-
functools.update_wrapper(new_cmd_func, command_function)
5671-
setattr(self, cmd_func_name, new_cmd_func)
5682+
# Ensure the replacement function identifies as the original for introspection
5683+
functools.update_wrapper(new_cmd_func, command_func)
5684+
setattr(self, command_func_name, new_cmd_func)
56725685

5673-
# Overwrite the help function to print the message
5674-
new_help_func = functools.partial(self._report_disabled_command_usage, message_to_print=message_to_print)
5675-
if (help_function := self.disabled_commands[command].help_function) is not None:
5676-
# Preserve the metadata of the original help function
5677-
functools.update_wrapper(new_help_func, help_function)
5686+
new_help_func = functools.partial(
5687+
self._report_disabled_command_usage,
5688+
message_to_print=message_to_print,
5689+
)
5690+
if help_func is not None:
5691+
functools.update_wrapper(new_help_func, help_func)
56785692
setattr(self, help_func_name, new_help_func)
56795693

5680-
# Set the completer to a function that returns a nothing
5694+
# Replace completer with a function that returns nothing
56815695
new_completer_func = functools.partial(self._disabled_completer)
5682-
if (completer_function := self.disabled_commands[command].completer_function) is not None:
5683-
# Preserve the metadata of the original completer function
5684-
functools.update_wrapper(new_completer_func, completer_function)
5696+
if completer_func is not None:
5697+
functools.update_wrapper(new_completer_func, completer_func)
56855698
setattr(self, completer_func_name, new_completer_func)
56865699

56875700
def disable_category(self, category: str, message_to_print: str) -> None:
@@ -5699,10 +5712,10 @@ def disable_category(self, category: str, message_to_print: str) -> None:
56995712

57005713
all_commands = self.get_all_commands()
57015714

5702-
for cmd_name in all_commands:
5703-
func = cast(BoundCommandFunc, self.get_command_func(cmd_name))
5704-
if self._get_command_category(func) == category:
5705-
self.disable_command(cmd_name, message_to_print)
5715+
for command in all_commands:
5716+
command_func = cast(BoundCommandFunc, self.get_command_func(command))
5717+
if self._get_command_category(command_func) == category:
5718+
self.disable_command(command, message_to_print)
57065719

57075720
self.disabled_categories[category] = message_to_print
57085721

tests/test_cmd2.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
import sys
77
import tempfile
88
from code import InteractiveConsole
9-
from typing import NoReturn
9+
from typing import (
10+
NoReturn,
11+
cast,
12+
)
1013
from unittest import mock
1114

1215
import pytest
@@ -34,6 +37,7 @@
3437
)
3538
from cmd2 import rich_utils as ru
3639
from cmd2 import string_utils as su
40+
from cmd2.types import BoundCommandFunc
3741

3842
from .conftest import (
3943
SHORTCUTS_TXT,
@@ -4080,12 +4084,10 @@ def test_help_argparse_command_while_disabled(disable_commands_app: DisableComma
40804084
def test_help_disabled_no_help_func(base_app: cmd2.Cmd) -> None:
40814085
from cmd2.cmd2 import DisabledCommand
40824086

4083-
# Manually disable a command without a help function to trigger the defensive fallback
4087+
# Intentionally bypass disable_command() to test the fallback in do_help()
40844088
command = "quit"
4085-
command_func = base_app.get_command_func(command)
4086-
base_app.disabled_commands[command] = DisabledCommand(
4087-
command_function=command_func, help_function=None, completer_function=None
4088-
)
4089+
command_func = cast(BoundCommandFunc, base_app.get_command_func(command))
4090+
base_app.disabled_commands[command] = DisabledCommand(command_func=command_func, help_func=None, completer_func=None)
40894091

40904092
_out, err = run_cmd(base_app, f"help {command}")
40914093
assert err[0].startswith(f"{command} is currently disabled.")

0 commit comments

Comments
 (0)