Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 3.1.3 (TBD)

- Bug Fixes
- Fixed issue where `CommandSet` registration did not respect disabled categories

## 3.1.2 (January 26, 2026)

- Bug Fixes
Expand Down
31 changes: 26 additions & 5 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,11 +593,14 @@ def __init__(
# being printed by a command.
self.terminal_lock = threading.RLock()

# Commands that have been disabled from use. This is to support commands that are only available
# during specific states of the application. This dictionary's keys are the command names and its
# values are DisabledCommand objects.
# Commands disabled during specific application states
# Key: Command name | Value: DisabledCommand object
self.disabled_commands: dict[str, DisabledCommand] = {}

# Categories of commands to be disabled
# Key: Category name | Value: Message to display
self.disabled_categories: dict[str, str] = {}

# 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:
Expand Down Expand Up @@ -788,6 +791,12 @@ def register_command_set(self, cmdset: CommandSet) -> None:
if default_category and not hasattr(command_method, constants.CMD_ATTR_HELP_CATEGORY):
utils.categorize(command_method, default_category)

# If this command is in a disabled category, then disable it
command_category = getattr(command_method, constants.CMD_ATTR_HELP_CATEGORY, None)
if command_category in self.disabled_categories:
message_to_print = self.disabled_categories[command_category]
self.disable_command(command, message_to_print)

self._installed_command_sets.add(cmdset)

self._register_subcommands(cmdset)
Expand Down Expand Up @@ -5805,7 +5814,7 @@ def enable_command(self, command: str) -> None:

:param command: the command being enabled
"""
# If the commands is already enabled, then return
# If the command is already enabled, then return
if command not in self.disabled_commands:
return

Expand Down Expand Up @@ -5837,11 +5846,17 @@ def enable_category(self, category: str) -> None:

:param category: the category to enable
"""
# If the category is already enabled, then return
if category not in self.disabled_categories:
return

for cmd_name in list(self.disabled_commands):
func = self.disabled_commands[cmd_name].command_function
if getattr(func, constants.CMD_ATTR_HELP_CATEGORY, None) == category:
self.enable_command(cmd_name)

del self.disabled_categories[category]

def disable_command(self, command: str, message_to_print: str) -> None:
"""Disable a command and overwrite its functions.

Expand All @@ -5852,7 +5867,7 @@ def disable_command(self, command: str, message_to_print: str) -> None:
command being disabled.
ex: message_to_print = f"{cmd2.COMMAND_NAME} is currently disabled"
"""
# If the commands is already disabled, then return
# If the command is already disabled, then return
if command in self.disabled_commands:
return

Expand Down Expand Up @@ -5891,13 +5906,19 @@ def disable_category(self, category: str, message_to_print: str) -> None:
of the command being disabled.
ex: message_to_print = f"{cmd2.COMMAND_NAME} is currently disabled"
"""
# If the category is already disabled, then return
if category in self.disabled_categories:
return

all_commands = self.get_all_commands()

for cmd_name in all_commands:
func = self.cmd_func(cmd_name)
if getattr(func, constants.CMD_ATTR_HELP_CATEGORY, None) == category:
self.disable_command(cmd_name, message_to_print)

self.disabled_categories[category] = message_to_print

def _report_disabled_command_usage(self, *_args: Any, message_to_print: str, **_kwargs: Any) -> None:
"""Report when a disabled command has been run or had help called on it.

Expand Down
59 changes: 57 additions & 2 deletions tests/test_cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
COMMAND_NAME,
Cmd2Style,
Color,
CommandSet,
RichPrintKwargs,
clipboard,
constants,
Expand Down Expand Up @@ -3106,6 +3107,16 @@ def do_has_no_helper_funcs(self, arg) -> None:
self.poutput("The real has_no_helper_funcs")


class DisableCommandSet(CommandSet):
"""Test registering a command which is in a disabled category"""

category_name = "CommandSet Test Category"

@cmd2.with_category(category_name)
def do_new_command(self, arg) -> None:
self._cmd.poutput("CommandSet function is enabled")


@pytest.fixture
def disable_commands_app():
return DisableCommandsApp()
Expand Down Expand Up @@ -3209,7 +3220,7 @@ def test_enable_enabled_command(disable_commands_app) -> None:
saved_len = len(disable_commands_app.disabled_commands)
disable_commands_app.enable_command('has_helper_funcs')

# The number of disabled_commands should not have changed
# The number of disabled commands should not have changed
assert saved_len == len(disable_commands_app.disabled_commands)


Expand All @@ -3223,7 +3234,7 @@ def test_disable_command_twice(disable_commands_app) -> None:
message_to_print = 'These commands are currently disabled'
disable_commands_app.disable_command('has_helper_funcs', message_to_print)

# The length of disabled_commands should have increased one
# The number of disabled commands should have increased one
new_len = len(disable_commands_app.disabled_commands)
assert saved_len == new_len - 1
saved_len = new_len
Expand Down Expand Up @@ -3251,6 +3262,50 @@ def test_disabled_message_command_name(disable_commands_app) -> None:
assert err[0].startswith('has_helper_funcs is currently disabled')


def test_register_command_in_enabled_category(disable_commands_app) -> None:
disable_commands_app.enable_category(DisableCommandSet.category_name)
cs = DisableCommandSet()
disable_commands_app.register_command_set(cs)

out, _err = run_cmd(disable_commands_app, 'new_command')
assert out[0] == "CommandSet function is enabled"


def test_register_command_in_disabled_category(disable_commands_app) -> None:
message_to_print = "CommandSet function is disabled"
disable_commands_app.disable_category(DisableCommandSet.category_name, message_to_print)
cs = DisableCommandSet()
disable_commands_app.register_command_set(cs)

_out, err = run_cmd(disable_commands_app, 'new_command')
assert err[0] == message_to_print


def test_enable_enabled_category(disable_commands_app) -> None:
# Test enabling a category that is not disabled
saved_len = len(disable_commands_app.disabled_categories)
disable_commands_app.enable_category('Test Category')

# The number of disabled categories should not have changed
assert saved_len == len(disable_commands_app.disabled_categories)


def test_disable_category_twice(disable_commands_app) -> None:
saved_len = len(disable_commands_app.disabled_categories)
message_to_print = 'These commands are currently disabled'
disable_commands_app.disable_category('Test Category', message_to_print)

# The number of disabled categories should have increased one
new_len = len(disable_commands_app.disabled_categories)
assert saved_len == new_len - 1
saved_len = new_len

# Disable again and the length should not change
disable_commands_app.disable_category('Test Category', message_to_print)
new_len = len(disable_commands_app.disabled_categories)
assert saved_len == new_len


@pytest.mark.parametrize('silence_startup_script', [True, False])
def test_startup_script(request, capsys, silence_startup_script) -> None:
test_dir = os.path.dirname(request.module.__file__)
Expand Down
Loading