cmd2 3.4.0__tar.gz → 3.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cmd2-3.4.0 → cmd2-3.5.1}/CHANGELOG.md +11 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/PKG-INFO +3 -3
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/argparse_completer.py +2 -2
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/cmd2.py +5 -11
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/constants.py +3 -3
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/parsing.py +10 -10
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/rich_utils.py +10 -63
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/utils.py +1 -1
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2.egg-info/PKG-INFO +3 -3
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2.egg-info/SOURCES.txt +0 -1
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2.egg-info/requires.txt +2 -2
- {cmd2-3.4.0 → cmd2-3.5.1}/pyproject.toml +2 -2
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_cmd2.py +21 -30
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_rich_utils.py +97 -37
- cmd2-3.4.0/examples/cmd2_history.dat +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/.pre-commit-config.yaml +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/.prettierignore +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/.prettierrc +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/GEMINI.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/LICENSE +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/MANIFEST.in +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/Makefile +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/README.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/__init__.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/argparse_custom.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/clipboard.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/colors.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/command_definition.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/decorators.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/exceptions.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/history.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/plugin.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/py.typed +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/py_bridge.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/rl_utils.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/string_utils.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/styles.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/terminal_utils.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2/transcript.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2.egg-info/dependency_links.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2.egg-info/top_level.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/cmd2.png +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/codecov.yml +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/argparse_completer.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/argparse_custom.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/clipboard.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/cmd.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/colors.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/command_definition.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/constants.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/decorators.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/exceptions.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/history.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/index.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/parsing.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/plugin.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/py_bridge.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/rich_utils.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/rl_utils.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/string_utils.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/styles.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/terminal_utils.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/transcript.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/api/utils.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/doc_conventions.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/examples/alternate_event_loops.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/examples/examples.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/examples/getting_started.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/examples/index.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/argument_processing.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/builtin_commands.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/clipboard.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/commands.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/completion.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/disable_commands.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/embedded_python_shells.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/generating_output.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/help.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/history.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/hooks.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/index.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/initialization.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/misc.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/modular_commands.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/multiline_commands.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/os.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/packaging.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/plugins.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/prompt.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/redirection.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/scripting.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/settings.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/shortcuts_aliases_macros.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/startup_commands.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/table_creation.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/theme.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/features/transcripts.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/index.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/javascripts/readthedocs.js +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/migrating/incompatibilities.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/migrating/index.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/migrating/minimum.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/migrating/next_steps.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/migrating/why.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/mixins/index.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/mixins/mixin_template.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/overrides/main.html +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/overview/alternatives.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/overview/index.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/overview/installation.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/overview/integrating.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/overview/resources.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/stylesheets/cmd2.css +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/testing.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/docs/upgrades.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/.cmd2rc +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/README.md +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/argparse_completion.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/argparse_example.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/async_call.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/async_printing.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/basic_completion.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/cmd_as_argument.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/color.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/command_sets.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/custom_parser.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/custom_types.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/default_categories.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/dynamic_commands.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/environment.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/event_loops.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/exit_code.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/getting_started.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/hello_cmd2.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/help_categories.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/hooks.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/migrating.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/mixin.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/modular_commands/__init__.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/modular_commands/commandset_basic.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/modular_commands/commandset_complex.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/modular_commands/commandset_custominit.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/modular_commands.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/paged_output.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/persistent_history.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/pretty_print.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/python_scripting.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/read_input.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/remove_builtin_commands.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/remove_settable.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/rich_tables.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/rich_theme.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/scripts/arg_printer.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/scripts/conditional.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/scripts/nested.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/scripts/quit.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/scripts/save_help_text.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/scripts/script.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/scripts/script.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/tmux_launch.sh +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/tmux_split.sh +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/transcript_example.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/transcripts/exampleSession.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/transcripts/pirate.transcript +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/transcripts/quit.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/transcripts/transcript_regex.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/examples/unicode_commands.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/mkdocs.yml +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/package.json +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/ruff.toml +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/scripts/validate_tag.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/setup.cfg +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/.cmd2rc +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/__init__.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/conftest.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/pyscript/echo.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/pyscript/environment.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/pyscript/help.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/pyscript/py_locals.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/pyscript/pyscript_dir.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/pyscript/raises_exception.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/pyscript/recursive.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/pyscript/self_in_py.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/pyscript/stdout_capture.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/pyscript/stop.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/relative_multiple.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/script.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/script.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/scripts/binary.bin +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/scripts/empty.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/scripts/help.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/scripts/nested.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/scripts/one_down.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/scripts/postcmds.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/scripts/precmds.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/scripts/utf8.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_argparse.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_argparse_completer.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_argparse_custom.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_argparse_subcommands.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_async_alert.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_categories.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_commandset.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_completion.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_future_annotations.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_history.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_parsing.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_plugin.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_run_pyscript.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_string_utils.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_terminal_utils.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_transcript.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_utils.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/test_utils_defining_class.py +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/bol_eol.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/characterclass.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/dotstar.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/extension_notation.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/failure.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/from_cmdloop.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/multiline_no_regex.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/multiline_regex.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/no_output.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/no_output_last.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/singleslash.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/slashes_escaped.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/slashslash.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/spaces.txt +0 -0
- {cmd2-3.4.0 → cmd2-3.5.1}/tests/transcripts/word_boundaries.txt +0 -0
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 3.5.1 (April 24, 2026)
|
|
2
|
+
|
|
3
|
+
- Bug Fixes
|
|
4
|
+
- Fixed `ArgparseCompleter.print_help()` not passing file stream to recursive call.
|
|
5
|
+
- Fixed issue where `constants.REDIRECTION_TOKENS` was being mutated.
|
|
6
|
+
|
|
7
|
+
## 3.5.0 (April 13, 2026)
|
|
8
|
+
|
|
9
|
+
- Bug Fixes
|
|
10
|
+
- Fixed issue where Rich stripped colors from text in test environments where TERM=dumb.
|
|
11
|
+
|
|
1
12
|
## 3.4.0 (March 3, 2026)
|
|
2
13
|
|
|
3
14
|
- Enhancements
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cmd2
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.1
|
|
4
4
|
Summary: cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python
|
|
5
5
|
Author: cmd2 Contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,8 +26,8 @@ Requires-Dist: backports.strenum; python_version == "3.10"
|
|
|
26
26
|
Requires-Dist: gnureadline>=8; platform_system == "Darwin"
|
|
27
27
|
Requires-Dist: pyperclip>=1.8.2
|
|
28
28
|
Requires-Dist: pyreadline3>=3.4; platform_system == "Windows"
|
|
29
|
-
Requires-Dist: rich>=
|
|
30
|
-
Requires-Dist: rich-argparse>=1.7.
|
|
29
|
+
Requires-Dist: rich>=15.0.0
|
|
30
|
+
Requires-Dist: rich-argparse>=1.7.2
|
|
31
31
|
Requires-Dist: typing-extensions; python_version == "3.10"
|
|
32
32
|
Dynamic: license-file
|
|
33
33
|
|
|
@@ -368,7 +368,7 @@ class ArgparseCompleter:
|
|
|
368
368
|
action = remaining_positionals.popleft()
|
|
369
369
|
|
|
370
370
|
# Are we at a subcommand? If so, forward to the matching completer
|
|
371
|
-
if action == self._subcommand_action:
|
|
371
|
+
if self._subcommand_action is not None and action == self._subcommand_action:
|
|
372
372
|
if token in self._subcommand_action.choices:
|
|
373
373
|
# Merge self._parent_tokens and consumed_arg_values
|
|
374
374
|
parent_tokens = {**self._parent_tokens, **consumed_arg_values}
|
|
@@ -637,7 +637,7 @@ class ArgparseCompleter:
|
|
|
637
637
|
if parser:
|
|
638
638
|
completer_type = self._cmd2_app._determine_ap_completer_type(parser)
|
|
639
639
|
completer = completer_type(parser, self._cmd2_app)
|
|
640
|
-
completer.print_help(tokens[1:])
|
|
640
|
+
completer.print_help(tokens[1:], file=file)
|
|
641
641
|
return
|
|
642
642
|
|
|
643
643
|
self._parser.print_help(file=file)
|
|
@@ -1666,10 +1666,8 @@ class Cmd:
|
|
|
1666
1666
|
**On Failure**
|
|
1667
1667
|
- Two empty lists
|
|
1668
1668
|
"""
|
|
1669
|
-
import copy
|
|
1670
|
-
|
|
1671
1669
|
unclosed_quote = ''
|
|
1672
|
-
quotes_to_try =
|
|
1670
|
+
quotes_to_try = [*constants.QUOTES]
|
|
1673
1671
|
|
|
1674
1672
|
tmp_line = line[:endidx]
|
|
1675
1673
|
tmp_endidx = endidx
|
|
@@ -3721,8 +3719,7 @@ class Cmd:
|
|
|
3721
3719
|
return
|
|
3722
3720
|
|
|
3723
3721
|
# Unquote redirection and terminator tokens
|
|
3724
|
-
tokens_to_unquote = constants.REDIRECTION_TOKENS
|
|
3725
|
-
tokens_to_unquote.extend(self.statement_parser.terminators)
|
|
3722
|
+
tokens_to_unquote = [*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators]
|
|
3726
3723
|
utils.unquote_specific_tokens(args.command_args, tokens_to_unquote)
|
|
3727
3724
|
|
|
3728
3725
|
# Build the alias value string
|
|
@@ -3801,8 +3798,7 @@ class Cmd:
|
|
|
3801
3798
|
"""List some or all aliases as 'alias create' commands."""
|
|
3802
3799
|
self.last_result = {} # dict[alias_name, alias_value]
|
|
3803
3800
|
|
|
3804
|
-
tokens_to_quote = constants.REDIRECTION_TOKENS
|
|
3805
|
-
tokens_to_quote.extend(self.statement_parser.terminators)
|
|
3801
|
+
tokens_to_quote = [*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators]
|
|
3806
3802
|
|
|
3807
3803
|
to_list = utils.remove_duplicates(args.names) if args.names else sorted(self.aliases, key=self.default_sort_key)
|
|
3808
3804
|
|
|
@@ -3964,8 +3960,7 @@ class Cmd:
|
|
|
3964
3960
|
return
|
|
3965
3961
|
|
|
3966
3962
|
# Unquote redirection and terminator tokens
|
|
3967
|
-
tokens_to_unquote = constants.REDIRECTION_TOKENS
|
|
3968
|
-
tokens_to_unquote.extend(self.statement_parser.terminators)
|
|
3963
|
+
tokens_to_unquote = [*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators]
|
|
3969
3964
|
utils.unquote_specific_tokens(args.command_args, tokens_to_unquote)
|
|
3970
3965
|
|
|
3971
3966
|
# Build the macro value string
|
|
@@ -4087,8 +4082,7 @@ class Cmd:
|
|
|
4087
4082
|
"""List macros."""
|
|
4088
4083
|
self.last_result = {} # dict[macro_name, macro_value]
|
|
4089
4084
|
|
|
4090
|
-
tokens_to_quote = constants.REDIRECTION_TOKENS
|
|
4091
|
-
tokens_to_quote.extend(self.statement_parser.terminators)
|
|
4085
|
+
tokens_to_quote = [*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators]
|
|
4092
4086
|
|
|
4093
4087
|
to_list = utils.remove_duplicates(args.names) if args.names else sorted(self.macros, key=self.default_sort_key)
|
|
4094
4088
|
|
|
@@ -7,12 +7,12 @@ INFINITY = float('inf')
|
|
|
7
7
|
|
|
8
8
|
# Used for command parsing, output redirection, tab completion and word
|
|
9
9
|
# breaks. Do not change.
|
|
10
|
-
QUOTES =
|
|
10
|
+
QUOTES = ('"', "'")
|
|
11
11
|
REDIRECTION_PIPE = '|'
|
|
12
12
|
REDIRECTION_OUTPUT = '>'
|
|
13
13
|
REDIRECTION_APPEND = '>>'
|
|
14
|
-
REDIRECTION_CHARS =
|
|
15
|
-
REDIRECTION_TOKENS =
|
|
14
|
+
REDIRECTION_CHARS = (REDIRECTION_PIPE, REDIRECTION_OUTPUT)
|
|
15
|
+
REDIRECTION_TOKENS = (REDIRECTION_PIPE, REDIRECTION_OUTPUT, REDIRECTION_APPEND)
|
|
16
16
|
COMMENT_CHAR = '#'
|
|
17
17
|
MULTILINE_TERMINATOR = ';'
|
|
18
18
|
|
|
@@ -298,10 +298,12 @@ class StatementParser:
|
|
|
298
298
|
# the string (\Z matches the end of the string even if it
|
|
299
299
|
# contains multiple lines)
|
|
300
300
|
#
|
|
301
|
-
invalid_command_chars =
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
invalid_command_chars = (
|
|
302
|
+
*constants.QUOTES,
|
|
303
|
+
*constants.REDIRECTION_CHARS,
|
|
304
|
+
*self.terminators,
|
|
305
|
+
)
|
|
306
|
+
|
|
305
307
|
# escape each item so it will for sure get treated as a literal
|
|
306
308
|
second_group_items = [re.escape(x) for x in invalid_command_chars]
|
|
307
309
|
# add the whitespace and end of string, not escaped because they
|
|
@@ -352,9 +354,8 @@ class StatementParser:
|
|
|
352
354
|
return False, errmsg
|
|
353
355
|
|
|
354
356
|
errmsg = 'cannot contain: whitespace, quotes, '
|
|
355
|
-
|
|
356
|
-
errchars
|
|
357
|
-
errchars.extend(self.terminators)
|
|
357
|
+
|
|
358
|
+
errchars = (*constants.REDIRECTION_CHARS, *self.terminators)
|
|
358
359
|
errmsg += ', '.join([shlex.quote(x) for x in errchars])
|
|
359
360
|
|
|
360
361
|
match = self._command_pattern.search(word)
|
|
@@ -677,9 +678,8 @@ class StatementParser:
|
|
|
677
678
|
:param tokens: the tokens as parsed by shlex
|
|
678
679
|
:return: a new list of tokens, further split using punctuation
|
|
679
680
|
"""
|
|
680
|
-
|
|
681
|
-
punctuation
|
|
682
|
-
punctuation.extend(constants.REDIRECTION_CHARS)
|
|
681
|
+
# Using a set for faster lookups
|
|
682
|
+
punctuation = {*self.terminators, *constants.REDIRECTION_CHARS}
|
|
683
683
|
|
|
684
684
|
punctuated_tokens = []
|
|
685
685
|
|
|
@@ -136,8 +136,9 @@ class Cmd2BaseConsole(Console):
|
|
|
136
136
|
:param kwargs: keyword arguments passed to the parent Console class.
|
|
137
137
|
:raises TypeError: if disallowed keyword argument is passed in.
|
|
138
138
|
"""
|
|
139
|
-
#
|
|
140
|
-
|
|
139
|
+
# These settings are controlled by the ALLOW_STYLE setting and cannot be overridden.
|
|
140
|
+
if "color_system" in kwargs:
|
|
141
|
+
raise TypeError("Passing 'color_system' is not allowed. Its behavior is controlled by the 'ALLOW_STYLE' setting.")
|
|
141
142
|
if "force_terminal" in kwargs:
|
|
142
143
|
raise TypeError(
|
|
143
144
|
"Passing 'force_terminal' is not allowed. Its behavior is controlled by the 'ALLOW_STYLE' setting."
|
|
@@ -157,18 +158,24 @@ class Cmd2BaseConsole(Console):
|
|
|
157
158
|
|
|
158
159
|
force_terminal: bool | None = None
|
|
159
160
|
force_interactive: bool | None = None
|
|
161
|
+
allow_style = False
|
|
160
162
|
|
|
161
163
|
if ALLOW_STYLE == AllowStyle.ALWAYS:
|
|
162
164
|
force_terminal = True
|
|
165
|
+
allow_style = True
|
|
163
166
|
|
|
164
167
|
# Turn off interactive mode if dest is not actually a terminal which supports it
|
|
165
168
|
tmp_console = Console(file=file)
|
|
166
169
|
force_interactive = tmp_console.is_interactive
|
|
170
|
+
elif ALLOW_STYLE == AllowStyle.TERMINAL:
|
|
171
|
+
tmp_console = Console(file=file)
|
|
172
|
+
allow_style = tmp_console.is_terminal
|
|
167
173
|
elif ALLOW_STYLE == AllowStyle.NEVER:
|
|
168
174
|
force_terminal = False
|
|
169
175
|
|
|
170
176
|
super().__init__(
|
|
171
177
|
file=file,
|
|
178
|
+
color_system="truecolor" if allow_style else None,
|
|
172
179
|
force_terminal=force_terminal,
|
|
173
180
|
force_interactive=force_interactive,
|
|
174
181
|
theme=APP_THEME,
|
|
@@ -412,6 +419,7 @@ def rich_text_to_string(text: Text) -> str:
|
|
|
412
419
|
"""
|
|
413
420
|
console = Console(
|
|
414
421
|
force_terminal=True,
|
|
422
|
+
color_system="truecolor",
|
|
415
423
|
soft_wrap=True,
|
|
416
424
|
no_color=False,
|
|
417
425
|
theme=APP_THEME,
|
|
@@ -477,64 +485,3 @@ def prepare_objects_for_rendering(*objects: Any) -> tuple[Any, ...]:
|
|
|
477
485
|
object_list[i] = Text.from_ansi(renderable_as_str)
|
|
478
486
|
|
|
479
487
|
return tuple(object_list)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
###################################################################################
|
|
483
|
-
# Rich Library Monkey Patches
|
|
484
|
-
#
|
|
485
|
-
# These patches fix specific bugs in the Rich library. They are conditional and
|
|
486
|
-
# will only be applied if the bug is detected. When the bugs are fixed in a
|
|
487
|
-
# future Rich release, these patches and their corresponding tests should be
|
|
488
|
-
# removed.
|
|
489
|
-
###################################################################################
|
|
490
|
-
|
|
491
|
-
###################################################################################
|
|
492
|
-
# Text.from_ansi() monkey patch
|
|
493
|
-
###################################################################################
|
|
494
|
-
|
|
495
|
-
# Save original Text.from_ansi() so we can call it in our wrapper
|
|
496
|
-
_orig_text_from_ansi = Text.from_ansi
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
@classmethod # type: ignore[misc]
|
|
500
|
-
def _from_ansi_wrapper(cls: type[Text], text: str, *args: Any, **kwargs: Any) -> Text: # noqa: ARG001
|
|
501
|
-
r"""Wrap Text.from_ansi() to fix its trailing newline bug.
|
|
502
|
-
|
|
503
|
-
This wrapper handles an issue where Text.from_ansi() removes the
|
|
504
|
-
trailing line break from a string (e.g. "Hello\n" becomes "Hello").
|
|
505
|
-
|
|
506
|
-
There is currently a pull request on Rich to fix this.
|
|
507
|
-
https://github.com/Textualize/rich/pull/3793
|
|
508
|
-
"""
|
|
509
|
-
result = _orig_text_from_ansi(text, *args, **kwargs)
|
|
510
|
-
|
|
511
|
-
# If the original string ends with a recognized line break character,
|
|
512
|
-
# then restore the missing newline. We use "\n" because Text.from_ansi()
|
|
513
|
-
# converts all line breaks into newlines.
|
|
514
|
-
# Source: https://docs.python.org/3/library/stdtypes.html#str.splitlines
|
|
515
|
-
line_break_chars = {
|
|
516
|
-
"\n", # Line Feed
|
|
517
|
-
"\r", # Carriage Return
|
|
518
|
-
"\v", # Vertical Tab
|
|
519
|
-
"\f", # Form Feed
|
|
520
|
-
"\x1c", # File Separator
|
|
521
|
-
"\x1d", # Group Separator
|
|
522
|
-
"\x1e", # Record Separator
|
|
523
|
-
"\x85", # Next Line (NEL)
|
|
524
|
-
"\u2028", # Line Separator
|
|
525
|
-
"\u2029", # Paragraph Separator
|
|
526
|
-
}
|
|
527
|
-
if text and text[-1] in line_break_chars:
|
|
528
|
-
result.append("\n")
|
|
529
|
-
|
|
530
|
-
return result
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
def _from_ansi_has_newline_bug() -> bool:
|
|
534
|
-
"""Check if Test.from_ansi() strips the trailing line break from a string."""
|
|
535
|
-
return Text.from_ansi("\n") == Text.from_ansi("")
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
# Only apply the monkey patch if the bug is present
|
|
539
|
-
if _from_ansi_has_newline_bug():
|
|
540
|
-
Text.from_ansi = _from_ansi_wrapper # type: ignore[assignment]
|
|
@@ -75,7 +75,7 @@ class Settable:
|
|
|
75
75
|
settable_object: object,
|
|
76
76
|
*,
|
|
77
77
|
settable_attrib_name: str | None = None,
|
|
78
|
-
onchange_cb: Callable[[str,
|
|
78
|
+
onchange_cb: Callable[[str, Any, Any], Any] | None = None,
|
|
79
79
|
choices: Iterable[Any] | None = None,
|
|
80
80
|
choices_provider: ChoicesProviderFunc | None = None,
|
|
81
81
|
completer: CompleterFunc | None = None,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cmd2
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.1
|
|
4
4
|
Summary: cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python
|
|
5
5
|
Author: cmd2 Contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,8 +26,8 @@ Requires-Dist: backports.strenum; python_version == "3.10"
|
|
|
26
26
|
Requires-Dist: gnureadline>=8; platform_system == "Darwin"
|
|
27
27
|
Requires-Dist: pyperclip>=1.8.2
|
|
28
28
|
Requires-Dist: pyreadline3>=3.4; platform_system == "Windows"
|
|
29
|
-
Requires-Dist: rich>=
|
|
30
|
-
Requires-Dist: rich-argparse>=1.7.
|
|
29
|
+
Requires-Dist: rich>=15.0.0
|
|
30
|
+
Requires-Dist: rich-argparse>=1.7.2
|
|
31
31
|
Requires-Dist: typing-extensions; python_version == "3.10"
|
|
32
32
|
Dynamic: license-file
|
|
33
33
|
|
|
@@ -33,8 +33,8 @@ dependencies = [
|
|
|
33
33
|
"gnureadline>=8; platform_system == 'Darwin'",
|
|
34
34
|
"pyperclip>=1.8.2",
|
|
35
35
|
"pyreadline3>=3.4; platform_system == 'Windows'",
|
|
36
|
-
"rich>=
|
|
37
|
-
"rich-argparse>=1.7.
|
|
36
|
+
"rich>=15.0.0",
|
|
37
|
+
"rich-argparse>=1.7.2",
|
|
38
38
|
"typing-extensions; python_version == '3.10'",
|
|
39
39
|
]
|
|
40
40
|
|
|
@@ -2964,7 +2964,6 @@ class AnsiApp(cmd2.Cmd):
|
|
|
2964
2964
|
|
|
2965
2965
|
def do_echo_error(self, args) -> None:
|
|
2966
2966
|
self.poutput(args, style=Cmd2Style.ERROR)
|
|
2967
|
-
# perror uses colors by default
|
|
2968
2967
|
self.perror(args)
|
|
2969
2968
|
|
|
2970
2969
|
|
|
@@ -2974,21 +2973,18 @@ def test_ansi_pouterr_always_tty(mocker, capsys) -> None:
|
|
|
2974
2973
|
mocker.patch.object(app.stdout, 'isatty', return_value=True)
|
|
2975
2974
|
mocker.patch.object(sys.stderr, 'isatty', return_value=True)
|
|
2976
2975
|
|
|
2976
|
+
expected_plain = 'oopsie\n'
|
|
2977
|
+
expected_styled = su.stylize('oopsie\n', Cmd2Style.ERROR)
|
|
2978
|
+
|
|
2977
2979
|
app.onecmd_plus_hooks('echo_error oopsie')
|
|
2978
2980
|
out, err = capsys.readouterr()
|
|
2979
|
-
|
|
2980
|
-
assert
|
|
2981
|
-
assert 'oopsie' in out
|
|
2982
|
-
assert len(err) > len('oopsie\n')
|
|
2983
|
-
assert 'oopsie' in err
|
|
2981
|
+
assert out == expected_styled
|
|
2982
|
+
assert err == expected_styled
|
|
2984
2983
|
|
|
2985
|
-
# but this one shouldn't
|
|
2986
2984
|
app.onecmd_plus_hooks('echo oopsie')
|
|
2987
2985
|
out, err = capsys.readouterr()
|
|
2988
|
-
assert out ==
|
|
2989
|
-
|
|
2990
|
-
assert len(err) > len('oopsie\n')
|
|
2991
|
-
assert 'oopsie' in err
|
|
2986
|
+
assert out == expected_plain
|
|
2987
|
+
assert err == expected_styled
|
|
2992
2988
|
|
|
2993
2989
|
|
|
2994
2990
|
@with_ansi_style(ru.AllowStyle.ALWAYS)
|
|
@@ -2997,21 +2993,18 @@ def test_ansi_pouterr_always_notty(mocker, capsys) -> None:
|
|
|
2997
2993
|
mocker.patch.object(app.stdout, 'isatty', return_value=False)
|
|
2998
2994
|
mocker.patch.object(sys.stderr, 'isatty', return_value=False)
|
|
2999
2995
|
|
|
2996
|
+
expected_plain = 'oopsie\n'
|
|
2997
|
+
expected_styled = su.stylize('oopsie\n', Cmd2Style.ERROR)
|
|
2998
|
+
|
|
3000
2999
|
app.onecmd_plus_hooks('echo_error oopsie')
|
|
3001
3000
|
out, err = capsys.readouterr()
|
|
3002
|
-
|
|
3003
|
-
assert
|
|
3004
|
-
assert 'oopsie' in out
|
|
3005
|
-
assert len(err) > len('oopsie\n')
|
|
3006
|
-
assert 'oopsie' in err
|
|
3001
|
+
assert out == expected_styled
|
|
3002
|
+
assert err == expected_styled
|
|
3007
3003
|
|
|
3008
|
-
# but this one shouldn't
|
|
3009
3004
|
app.onecmd_plus_hooks('echo oopsie')
|
|
3010
3005
|
out, err = capsys.readouterr()
|
|
3011
|
-
assert out ==
|
|
3012
|
-
|
|
3013
|
-
assert len(err) > len('oopsie\n')
|
|
3014
|
-
assert 'oopsie' in err
|
|
3006
|
+
assert out == expected_plain
|
|
3007
|
+
assert err == expected_styled
|
|
3015
3008
|
|
|
3016
3009
|
|
|
3017
3010
|
@with_ansi_style(ru.AllowStyle.TERMINAL)
|
|
@@ -3020,20 +3013,18 @@ def test_ansi_terminal_tty(mocker, capsys) -> None:
|
|
|
3020
3013
|
mocker.patch.object(app.stdout, 'isatty', return_value=True)
|
|
3021
3014
|
mocker.patch.object(sys.stderr, 'isatty', return_value=True)
|
|
3022
3015
|
|
|
3016
|
+
expected_plain = 'oopsie\n'
|
|
3017
|
+
expected_styled = su.stylize('oopsie\n', Cmd2Style.ERROR)
|
|
3018
|
+
|
|
3023
3019
|
app.onecmd_plus_hooks('echo_error oopsie')
|
|
3024
|
-
# if colors are on, the output should have some ANSI style sequences in it
|
|
3025
3020
|
out, err = capsys.readouterr()
|
|
3026
|
-
assert
|
|
3027
|
-
assert
|
|
3028
|
-
assert len(err) > len('oopsie\n')
|
|
3029
|
-
assert 'oopsie' in err
|
|
3021
|
+
assert out == expected_styled
|
|
3022
|
+
assert err == expected_styled
|
|
3030
3023
|
|
|
3031
|
-
# but this one shouldn't
|
|
3032
3024
|
app.onecmd_plus_hooks('echo oopsie')
|
|
3033
3025
|
out, err = capsys.readouterr()
|
|
3034
|
-
assert out ==
|
|
3035
|
-
assert
|
|
3036
|
-
assert 'oopsie' in err
|
|
3026
|
+
assert out == expected_plain
|
|
3027
|
+
assert err == expected_styled
|
|
3037
3028
|
|
|
3038
3029
|
|
|
3039
3030
|
@with_ansi_style(ru.AllowStyle.TERMINAL)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Unit testing for cmd2/rich_utils.py module"""
|
|
2
2
|
|
|
3
|
+
from unittest import mock
|
|
4
|
+
|
|
3
5
|
import pytest
|
|
4
6
|
import rich.box
|
|
5
7
|
from rich.console import Console
|
|
@@ -18,6 +20,10 @@ from .conftest import with_ansi_style
|
|
|
18
20
|
|
|
19
21
|
def test_cmd2_base_console() -> None:
|
|
20
22
|
# Test the keyword arguments which are not allowed.
|
|
23
|
+
with pytest.raises(TypeError) as excinfo:
|
|
24
|
+
ru.Cmd2BaseConsole(color_system="auto")
|
|
25
|
+
assert 'color_system' in str(excinfo.value)
|
|
26
|
+
|
|
21
27
|
with pytest.raises(TypeError) as excinfo:
|
|
22
28
|
ru.Cmd2BaseConsole(force_terminal=True)
|
|
23
29
|
assert 'force_terminal' in str(excinfo.value)
|
|
@@ -74,7 +80,12 @@ def test_indented_table() -> None:
|
|
|
74
80
|
[
|
|
75
81
|
(Text("Hello"), "Hello"),
|
|
76
82
|
(Text("Hello\n"), "Hello\n"),
|
|
77
|
-
|
|
83
|
+
# Test standard color support
|
|
84
|
+
(Text("Standard", style="blue"), "\x1b[34mStandard\x1b[0m"),
|
|
85
|
+
# Test 256-color support
|
|
86
|
+
(Text("256-color", style=Color.NAVY_BLUE), "\x1b[38;5;17m256-color\x1b[0m"),
|
|
87
|
+
# Test 24-bit color (TrueColor) support
|
|
88
|
+
(Text("TrueColor", style="#123456"), "\x1b[38;2;18;52;86mTrueColor\x1b[0m"),
|
|
78
89
|
],
|
|
79
90
|
)
|
|
80
91
|
def test_rich_text_to_string(rich_text: Text, string: str) -> None:
|
|
@@ -110,42 +121,6 @@ def test_set_theme() -> None:
|
|
|
110
121
|
assert ru.APP_THEME.styles[rich_style_key] == theme[rich_style_key]
|
|
111
122
|
|
|
112
123
|
|
|
113
|
-
def test_from_ansi_wrapper() -> None:
|
|
114
|
-
# Check if we are still patching Text.from_ansi(). If this check fails, then Rich
|
|
115
|
-
# has fixed the bug. Therefore, we can remove this test function and ru._from_ansi_wrapper.
|
|
116
|
-
assert Text.from_ansi.__func__ is ru._from_ansi_wrapper.__func__ # type: ignore[attr-defined]
|
|
117
|
-
|
|
118
|
-
# Line breaks recognized by str.splitlines().
|
|
119
|
-
# Source: https://docs.python.org/3/library/stdtypes.html#str.splitlines
|
|
120
|
-
line_breaks = {
|
|
121
|
-
"\n", # Line Feed
|
|
122
|
-
"\r", # Carriage Return
|
|
123
|
-
"\r\n", # Carriage Return + Line Feed
|
|
124
|
-
"\v", # Vertical Tab
|
|
125
|
-
"\f", # Form Feed
|
|
126
|
-
"\x1c", # File Separator
|
|
127
|
-
"\x1d", # Group Separator
|
|
128
|
-
"\x1e", # Record Separator
|
|
129
|
-
"\x85", # Next Line (NEL)
|
|
130
|
-
"\u2028", # Line Separator
|
|
131
|
-
"\u2029", # Paragraph Separator
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
# Test all line breaks
|
|
135
|
-
for lb in line_breaks:
|
|
136
|
-
input_string = f"Text{lb}"
|
|
137
|
-
expected_output = input_string.replace(lb, "\n")
|
|
138
|
-
assert Text.from_ansi(input_string).plain == expected_output
|
|
139
|
-
|
|
140
|
-
# Test string without trailing line break
|
|
141
|
-
input_string = "No trailing\nline break"
|
|
142
|
-
assert Text.from_ansi(input_string).plain == input_string
|
|
143
|
-
|
|
144
|
-
# Test empty string
|
|
145
|
-
input_string = ""
|
|
146
|
-
assert Text.from_ansi(input_string).plain == input_string
|
|
147
|
-
|
|
148
|
-
|
|
149
124
|
@with_ansi_style(ru.AllowStyle.ALWAYS)
|
|
150
125
|
def test_cmd2_base_console_print() -> None:
|
|
151
126
|
"""Test that Cmd2BaseConsole.print() correctly propagates formatting overrides to structured renderables."""
|
|
@@ -192,3 +167,88 @@ def test_cmd2_base_console_log() -> None:
|
|
|
192
167
|
# Verify stack offset: the log line should point to this file, not rich_utils.py
|
|
193
168
|
# Rich logs include the filename and line number on the right.
|
|
194
169
|
assert "test_rich_utils.py" in result
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@with_ansi_style(ru.AllowStyle.ALWAYS)
|
|
173
|
+
def test_cmd2_base_console_init_always_interactive_true() -> None:
|
|
174
|
+
"""Test Cmd2BaseConsole initialization when ALLOW_STYLE is ALWAYS and is_interactive is True."""
|
|
175
|
+
with (
|
|
176
|
+
mock.patch('rich.console.Console.__init__', return_value=None) as mock_base_init,
|
|
177
|
+
mock.patch('cmd2.rich_utils.Console', autospec=True) as mock_detect_console_class,
|
|
178
|
+
):
|
|
179
|
+
mock_detect_console = mock_detect_console_class.return_value
|
|
180
|
+
mock_detect_console.is_interactive = True
|
|
181
|
+
|
|
182
|
+
ru.Cmd2BaseConsole()
|
|
183
|
+
|
|
184
|
+
# Verify arguments passed to super().__init__
|
|
185
|
+
_, kwargs = mock_base_init.call_args
|
|
186
|
+
assert kwargs['color_system'] == "truecolor"
|
|
187
|
+
assert kwargs['force_terminal'] is True
|
|
188
|
+
assert kwargs['force_interactive'] is True
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@with_ansi_style(ru.AllowStyle.ALWAYS)
|
|
192
|
+
def test_cmd2_base_console_init_always_interactive_false() -> None:
|
|
193
|
+
"""Test Cmd2BaseConsole initialization when ALLOW_STYLE is ALWAYS and is_interactive is False."""
|
|
194
|
+
with (
|
|
195
|
+
mock.patch('rich.console.Console.__init__', return_value=None) as mock_base_init,
|
|
196
|
+
mock.patch('cmd2.rich_utils.Console', autospec=True) as mock_detect_console_class,
|
|
197
|
+
):
|
|
198
|
+
mock_detect_console = mock_detect_console_class.return_value
|
|
199
|
+
mock_detect_console.is_interactive = False
|
|
200
|
+
|
|
201
|
+
ru.Cmd2BaseConsole()
|
|
202
|
+
|
|
203
|
+
_, kwargs = mock_base_init.call_args
|
|
204
|
+
assert kwargs['color_system'] == "truecolor"
|
|
205
|
+
assert kwargs['force_terminal'] is True
|
|
206
|
+
assert kwargs['force_interactive'] is False
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@with_ansi_style(ru.AllowStyle.TERMINAL)
|
|
210
|
+
def test_cmd2_base_console_init_terminal_true() -> None:
|
|
211
|
+
"""Test Cmd2BaseConsole initialization when ALLOW_STYLE is TERMINAL and it is a terminal."""
|
|
212
|
+
with (
|
|
213
|
+
mock.patch('rich.console.Console.__init__', return_value=None) as mock_base_init,
|
|
214
|
+
mock.patch('cmd2.rich_utils.Console', autospec=True) as mock_detect_console_class,
|
|
215
|
+
):
|
|
216
|
+
mock_detect_console = mock_detect_console_class.return_value
|
|
217
|
+
mock_detect_console.is_terminal = True
|
|
218
|
+
|
|
219
|
+
ru.Cmd2BaseConsole()
|
|
220
|
+
|
|
221
|
+
_, kwargs = mock_base_init.call_args
|
|
222
|
+
assert kwargs['color_system'] == "truecolor"
|
|
223
|
+
assert kwargs['force_terminal'] is None
|
|
224
|
+
assert kwargs['force_interactive'] is None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@with_ansi_style(ru.AllowStyle.TERMINAL)
|
|
228
|
+
def test_cmd2_base_console_init_terminal_false() -> None:
|
|
229
|
+
"""Test Cmd2BaseConsole initialization when ALLOW_STYLE is TERMINAL and it is not a terminal."""
|
|
230
|
+
with (
|
|
231
|
+
mock.patch('rich.console.Console.__init__', return_value=None) as mock_base_init,
|
|
232
|
+
mock.patch('cmd2.rich_utils.Console', autospec=True) as mock_detect_console_class,
|
|
233
|
+
):
|
|
234
|
+
mock_detect_console = mock_detect_console_class.return_value
|
|
235
|
+
mock_detect_console.is_terminal = False
|
|
236
|
+
|
|
237
|
+
ru.Cmd2BaseConsole()
|
|
238
|
+
|
|
239
|
+
_, kwargs = mock_base_init.call_args
|
|
240
|
+
assert kwargs['color_system'] is None
|
|
241
|
+
assert kwargs['force_terminal'] is None
|
|
242
|
+
assert kwargs['force_interactive'] is None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@with_ansi_style(ru.AllowStyle.NEVER)
|
|
246
|
+
def test_cmd2_base_console_init_never() -> None:
|
|
247
|
+
"""Test Cmd2BaseConsole initialization when ALLOW_STYLE is NEVER."""
|
|
248
|
+
with mock.patch('rich.console.Console.__init__', return_value=None) as mock_base_init:
|
|
249
|
+
ru.Cmd2BaseConsole()
|
|
250
|
+
|
|
251
|
+
_, kwargs = mock_base_init.call_args
|
|
252
|
+
assert kwargs['color_system'] is None
|
|
253
|
+
assert kwargs['force_terminal'] is False
|
|
254
|
+
assert kwargs['force_interactive'] is None
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|