cmd2 4.0.1__tar.gz → 4.1.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-4.0.1 → cmd2-4.1.1}/.pre-commit-config.yaml +2 -2
- {cmd2-4.0.1 → cmd2-4.1.1}/CHANGELOG.md +7 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/PKG-INFO +1 -1
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/argparse_utils.py +3 -3
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/cmd2.py +35 -8
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2.egg-info/PKG-INFO +1 -1
- cmd2-4.1.1/cmd2.egg-info/scm_version.json +8 -0
- cmd2-4.1.1/examples/async_printing.py +181 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_cmd2.py +71 -7
- cmd2-4.0.1/cmd2.egg-info/scm_version.json +0 -8
- cmd2-4.0.1/examples/async_printing.py +0 -182
- {cmd2-4.0.1 → cmd2-4.1.1}/.prettierignore +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/.prettierrc +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/.typos.toml +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/GEMINI.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/LICENSE +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/MANIFEST.in +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/Makefile +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/README.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/__init__.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/annotated.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/argparse_completer.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/clipboard.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/colors.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/command_set.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/completion.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/constants.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/decorators.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/exceptions.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/history.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/parsing.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/plugin.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/pt_utils.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/py.typed +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/py_bridge.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/rich_utils.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/string_utils.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/styles.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/theme.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/types.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2/utils.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2.egg-info/SOURCES.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2.egg-info/dependency_links.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2.egg-info/requires.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2.egg-info/scm_file_list.json +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2.egg-info/top_level.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/cmd2.png +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/codecov.yml +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/annotated.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/argparse_completer.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/argparse_utils.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/clipboard.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/cmd.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/colors.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/command_set.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/completion.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/constants.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/decorators.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/exceptions.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/history.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/index.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/parsing.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/plugin.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/pt_utils.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/py_bridge.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/rich_utils.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/string_utils.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/styles.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/theme.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/api/utils.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/doc_conventions.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/examples/alternate_event_loops.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/examples/examples.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/examples/getting_started.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/examples/index.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/annotated.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/argument_processing.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/async_commands.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/builtin_commands.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/clipboard.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/commands.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/completion.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/disable_commands.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/embedded_python_shells.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/generating_output.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/help.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/history.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/hooks.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/index.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/initialization.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/misc.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/modular_commands.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/multiline_commands.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/os.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/packaging.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/plugins.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/prompt.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/redirection.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/scripting.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/settings.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/shortcuts_aliases_macros.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/startup_commands.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/table_creation.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/features/theme.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/index.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/javascripts/readthedocs.js +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/migrating/incompatibilities.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/migrating/index.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/migrating/minimum.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/migrating/next_steps.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/migrating/why.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/mixins/index.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/mixins/mixin_template.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/overrides/main.html +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/overview/alternatives.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/overview/index.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/overview/installation.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/overview/integrating.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/overview/resources.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/stylesheets/cmd2.css +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/testing.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/docs/upgrades.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/.cmd2rc +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/README.md +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/annotated_example.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/argparse_completion.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/argparse_example.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/async_call.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/async_commands.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/basic_completion.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/cmd2_history.dat +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/cmd_as_argument.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/color.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/command_sets.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/completion_item_choices.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/custom_parser.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/custom_types.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/default_categories.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/dynamic_commands.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/environment.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/event_loops.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/exit_code.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/getting_started.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/hello_cmd2.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/help_categories.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/hooks.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/migrating.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/mixin.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/modular_commands/__init__.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/modular_commands/commandset_basic.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/modular_commands/commandset_complex.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/modular_commands/commandset_custominit.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/modular_commandsets.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/paged_output.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/persistent_history.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/pretty_print.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/python_scripting.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/read_input.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/remove_builtin_commands.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/remove_settable.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/rich_tables.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/rich_theme.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/scripts/arg_printer.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/scripts/conditional.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/scripts/nested.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/scripts/quit.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/scripts/save_help_text.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/scripts/script.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/scripts/script.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/tmux_launch.sh +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/tmux_split.sh +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/examples/unicode_commands.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/mkdocs.yml +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/package.json +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/pyproject.toml +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/ruff.toml +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/scripts/validate_tag.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/setup.cfg +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/.cmd2rc +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/__init__.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/conftest.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/pyscript/echo.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/pyscript/environment.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/pyscript/help.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/pyscript/py_locals.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/pyscript/pyscript_dir.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/pyscript/raises_exception.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/pyscript/recursive.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/pyscript/self_in_py.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/pyscript/stop.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/pyscript/test_print.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/relative_multiple.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/script.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/script.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/scripts/binary.bin +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/scripts/empty.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/scripts/help.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/scripts/nested.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/scripts/one_down.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/scripts/postcmds.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/scripts/precmds.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/scripts/utf8.txt +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_annotated.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_argparse.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_argparse_completer.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_argparse_subcommands.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_argparse_utils.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_categories.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_commandset.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_completion.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_dynamic_complete_style.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_future_annotations.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_history.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_parsing.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_plugin.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_pt_utils.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_py_completion.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_rich_utils.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_run_pyscript.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_string_utils.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_theme.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_utils.py +0 -0
- {cmd2-4.0.1 → cmd2-4.1.1}/tests/test_utils_defining_class.py +0 -0
|
@@ -14,7 +14,7 @@ repos:
|
|
|
14
14
|
- id: trailing-whitespace
|
|
15
15
|
|
|
16
16
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
17
|
-
rev: "v0.15.
|
|
17
|
+
rev: "v0.15.21"
|
|
18
18
|
hooks:
|
|
19
19
|
- id: ruff-format
|
|
20
20
|
args: [--config=ruff.toml]
|
|
@@ -30,7 +30,7 @@ repos:
|
|
|
30
30
|
- prettier-plugin-toml@2.0.6
|
|
31
31
|
|
|
32
32
|
- repo: https://github.com/crate-ci/typos
|
|
33
|
-
rev: v1.
|
|
33
|
+
rev: v1.48.0
|
|
34
34
|
hooks:
|
|
35
35
|
- id: typos
|
|
36
36
|
exclude: |
|
|
@@ -1035,13 +1035,13 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
|
1035
1035
|
|
|
1036
1036
|
self.exit(2, f"{formatted_message}\n")
|
|
1037
1037
|
|
|
1038
|
-
def _get_formatter(self, **_kwargs: Any) -> Cmd2HelpFormatter:
|
|
1038
|
+
def _get_formatter(self, *_args: Any, **_kwargs: Any) -> Cmd2HelpFormatter:
|
|
1039
1039
|
"""Override with customizations for Cmd2HelpFormatter."""
|
|
1040
1040
|
return self.formatter_class(prog=self.prog, file=self._thread_locals.current_output_file)
|
|
1041
1041
|
|
|
1042
|
-
def format_help(self) -> str:
|
|
1042
|
+
def format_help(self, *args: Any, **kwargs: Any) -> str:
|
|
1043
1043
|
"""Override to add a newline."""
|
|
1044
|
-
return super().format_help() + "\n"
|
|
1044
|
+
return super().format_help(*args, **kwargs) + "\n"
|
|
1045
1045
|
|
|
1046
1046
|
def _get_nargs_pattern(self, action: argparse.Action) -> str:
|
|
1047
1047
|
"""Override to support nargs ranges."""
|
|
@@ -312,7 +312,11 @@ class CommandParsers:
|
|
|
312
312
|
class AsyncAlert:
|
|
313
313
|
"""Contents of an asynchronous alert which display while user is at prompt.
|
|
314
314
|
|
|
315
|
-
:param msg: an optional
|
|
315
|
+
:param msg: an optional printable object (including Rich renderables) to be
|
|
316
|
+
printed above the prompt.
|
|
317
|
+
:param soft_wrap: Enable soft wrap mode. This only applies with msg is not None.
|
|
318
|
+
Defaults to True. See print_to() docstring for more details on
|
|
319
|
+
this parameter.
|
|
316
320
|
:param prompt: an optional string to dynamically replace the current prompt.
|
|
317
321
|
|
|
318
322
|
:ivar timestamp: monotonic creation time of the alert. If an alert was created
|
|
@@ -320,7 +324,8 @@ class AsyncAlert:
|
|
|
320
324
|
to avoid a stale display, but its msg data will still be displayed.
|
|
321
325
|
"""
|
|
322
326
|
|
|
323
|
-
msg:
|
|
327
|
+
msg: Any | None = None
|
|
328
|
+
soft_wrap: bool = True
|
|
324
329
|
prompt: str | None = None
|
|
325
330
|
timestamp: float = field(default_factory=time.monotonic, init=False)
|
|
326
331
|
|
|
@@ -1572,7 +1577,7 @@ class Cmd:
|
|
|
1572
1577
|
If False, Rich wraps text to fit the terminal width.
|
|
1573
1578
|
Set this to False when printing structured Renderables like
|
|
1574
1579
|
Tables, Panels, or Columns to ensure they render as expected.
|
|
1575
|
-
For example, when soft_wrap is True Panels truncate text
|
|
1580
|
+
For example, when soft_wrap is True, Panels truncate text
|
|
1576
1581
|
which is wider than the terminal.
|
|
1577
1582
|
:param justify: justify method ("left", "center", "right", "full"). Defaults to None.
|
|
1578
1583
|
:param emoji: If True, Rich will replace emoji codes (e.g., :smiley:) with their
|
|
@@ -3657,7 +3662,19 @@ class Cmd:
|
|
|
3657
3662
|
# prevents async alerts from printing once a command starts.
|
|
3658
3663
|
|
|
3659
3664
|
# Print all alerts at once to reduce flicker.
|
|
3660
|
-
|
|
3665
|
+
console = self._get_core_print_console(
|
|
3666
|
+
file=self.stdout,
|
|
3667
|
+
emoji=False,
|
|
3668
|
+
markup=False,
|
|
3669
|
+
highlight=False,
|
|
3670
|
+
)
|
|
3671
|
+
|
|
3672
|
+
with console.capture() as capture:
|
|
3673
|
+
for alert in self._alert_queue:
|
|
3674
|
+
if alert.msg is not None:
|
|
3675
|
+
console.print(alert.msg, soft_wrap=alert.soft_wrap)
|
|
3676
|
+
|
|
3677
|
+
alert_text = capture.get()
|
|
3661
3678
|
|
|
3662
3679
|
# Find the latest prompt update among all pending alerts.
|
|
3663
3680
|
latest_prompt = None
|
|
@@ -3681,7 +3698,7 @@ class Cmd:
|
|
|
3681
3698
|
if alert_text:
|
|
3682
3699
|
# Print the alert messages above the prompt.
|
|
3683
3700
|
with patch_stdout():
|
|
3684
|
-
print_formatted_text(
|
|
3701
|
+
print_formatted_text(ANSI(alert_text), end="")
|
|
3685
3702
|
|
|
3686
3703
|
elif latest_prompt is not None:
|
|
3687
3704
|
# Refresh UI immediately to show the new prompt
|
|
@@ -5616,7 +5633,13 @@ class Cmd:
|
|
|
5616
5633
|
# self.last_result will be set by do_run_script()
|
|
5617
5634
|
return self.do_run_script(su.quote(relative_path))
|
|
5618
5635
|
|
|
5619
|
-
def add_alert(
|
|
5636
|
+
def add_alert(
|
|
5637
|
+
self,
|
|
5638
|
+
*,
|
|
5639
|
+
msg: Any | None = None,
|
|
5640
|
+
soft_wrap: bool = True,
|
|
5641
|
+
prompt: str | None = None,
|
|
5642
|
+
) -> None:
|
|
5620
5643
|
"""Queue an asynchronous alert to be displayed when the prompt is active.
|
|
5621
5644
|
|
|
5622
5645
|
Examples:
|
|
@@ -5624,7 +5647,11 @@ class Cmd:
|
|
|
5624
5647
|
add_alert(prompt="user@host> ") # Update prompt only
|
|
5625
5648
|
add_alert(msg="Done", prompt="> ") # Update both
|
|
5626
5649
|
|
|
5627
|
-
:param msg: an optional
|
|
5650
|
+
:param msg: an optional printable object (including Rich renderables) to be
|
|
5651
|
+
printed above the prompt.
|
|
5652
|
+
:param soft_wrap: Enable soft wrap mode. This only applies with msg is not None.
|
|
5653
|
+
Defaults to True. See print_to() docstring for more details on
|
|
5654
|
+
this parameter.
|
|
5628
5655
|
:param prompt: an optional string to dynamically replace the current prompt.
|
|
5629
5656
|
|
|
5630
5657
|
"""
|
|
@@ -5632,7 +5659,7 @@ class Cmd:
|
|
|
5632
5659
|
return
|
|
5633
5660
|
|
|
5634
5661
|
with self._alert_condition:
|
|
5635
|
-
alert = AsyncAlert(msg=msg, prompt=prompt)
|
|
5662
|
+
alert = AsyncAlert(msg=msg, soft_wrap=soft_wrap, prompt=prompt)
|
|
5636
5663
|
self._alert_queue.append(alert)
|
|
5637
5664
|
self._alert_condition.notify_all()
|
|
5638
5665
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""A simple example demonstrating an application that asynchronously prints alerts,
|
|
3
|
+
updates the prompt, and changes the window title.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import secrets
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from rich.console import Group
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.style import Style
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.text import Text
|
|
16
|
+
|
|
17
|
+
import cmd2
|
|
18
|
+
from cmd2 import (
|
|
19
|
+
Color,
|
|
20
|
+
stylize,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_alerts() -> list[tuple[Any, bool]]:
|
|
25
|
+
"""Return a list of (alert_msg, soft_wrap) tuples."""
|
|
26
|
+
table = Table("Command", "Description", title="Quick Help")
|
|
27
|
+
table.add_row("start_alerts", "Start the async alert generator")
|
|
28
|
+
table.add_row("stop_alerts", "Stop the async alert generator")
|
|
29
|
+
table.add_row("help", "Show help menu")
|
|
30
|
+
|
|
31
|
+
# Set soft_wrap to False when printing structured Renderables like Tables, Panels, or Columns
|
|
32
|
+
# to ensure they render as expected. For example, when soft_wrap is True, Panels truncate
|
|
33
|
+
# text which is wider than the terminal.
|
|
34
|
+
return [
|
|
35
|
+
(
|
|
36
|
+
Text(
|
|
37
|
+
"Watch as this application prints alerts asynchronously!",
|
|
38
|
+
style=Style(
|
|
39
|
+
color=Color.BRIGHT_CYAN,
|
|
40
|
+
bold=True,
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
True,
|
|
44
|
+
),
|
|
45
|
+
("Notice how alerts don't interfere with your typing or cursor location.", True),
|
|
46
|
+
(
|
|
47
|
+
Panel(
|
|
48
|
+
"This message is wrapped in a Rich Panel!",
|
|
49
|
+
title="System Alert",
|
|
50
|
+
border_style=Color.BRIGHT_BLUE,
|
|
51
|
+
expand=False,
|
|
52
|
+
),
|
|
53
|
+
False,
|
|
54
|
+
),
|
|
55
|
+
(table, False),
|
|
56
|
+
("You can stop and start the alerts by typing stop_alerts and start_alerts.", True),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AlerterApp(cmd2.Cmd):
|
|
61
|
+
"""An app that shows off add_alert() and set_window_title()."""
|
|
62
|
+
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
"""Initialize AlerterApp."""
|
|
65
|
+
super().__init__()
|
|
66
|
+
|
|
67
|
+
self.prompt = "(APR)> "
|
|
68
|
+
|
|
69
|
+
# The thread that will asynchronously alert the user of events
|
|
70
|
+
self._stop_event = threading.Event()
|
|
71
|
+
self._add_alert_thread = threading.Thread()
|
|
72
|
+
self._alert_count = 0
|
|
73
|
+
|
|
74
|
+
# Create some hooks to handle the starting and stopping of our thread
|
|
75
|
+
self.register_preloop_hook(self._preloop_hook)
|
|
76
|
+
self.register_postloop_hook(self._postloop_hook)
|
|
77
|
+
|
|
78
|
+
# Create an instance of SystemRandom
|
|
79
|
+
self._secure_generator = secrets.SystemRandom()
|
|
80
|
+
|
|
81
|
+
def _preloop_hook(self) -> None:
|
|
82
|
+
"""Start the alerter thread."""
|
|
83
|
+
self._stop_event.clear()
|
|
84
|
+
self._add_alert_thread = threading.Thread(name="alerter", target=self._add_alerts_func)
|
|
85
|
+
self._add_alert_thread.start()
|
|
86
|
+
|
|
87
|
+
def _postloop_hook(self) -> None:
|
|
88
|
+
"""Stop the alerter thread."""
|
|
89
|
+
self._stop_event.set()
|
|
90
|
+
if self._add_alert_thread.is_alive():
|
|
91
|
+
self._add_alert_thread.join()
|
|
92
|
+
|
|
93
|
+
def do_start_alerts(self, _: cmd2.Statement) -> None:
|
|
94
|
+
"""Start the alerter thread."""
|
|
95
|
+
if self._add_alert_thread.is_alive():
|
|
96
|
+
print("The alert thread is already started")
|
|
97
|
+
else:
|
|
98
|
+
self._stop_event.clear()
|
|
99
|
+
self._add_alert_thread = threading.Thread(name="alerter", target=self._add_alerts_func)
|
|
100
|
+
self._add_alert_thread.start()
|
|
101
|
+
|
|
102
|
+
def do_stop_alerts(self, _: cmd2.Statement) -> None:
|
|
103
|
+
"""Stop the alerter thread."""
|
|
104
|
+
self._stop_event.set()
|
|
105
|
+
if self._add_alert_thread.is_alive():
|
|
106
|
+
self._add_alert_thread.join()
|
|
107
|
+
else:
|
|
108
|
+
print("The alert thread is already stopped")
|
|
109
|
+
|
|
110
|
+
def _build_colored_prompt(self) -> str:
|
|
111
|
+
"""Randomly build a colored prompt.
|
|
112
|
+
|
|
113
|
+
:return: the new prompt.
|
|
114
|
+
"""
|
|
115
|
+
rand_num = self._secure_generator.randint(1, 6)
|
|
116
|
+
|
|
117
|
+
status_color = Color.DEFAULT
|
|
118
|
+
|
|
119
|
+
if rand_num == 1:
|
|
120
|
+
status_color = Color.BRIGHT_RED
|
|
121
|
+
elif rand_num == 2:
|
|
122
|
+
status_color = Color.BRIGHT_YELLOW
|
|
123
|
+
elif rand_num == 3:
|
|
124
|
+
status_color = Color.CYAN
|
|
125
|
+
elif rand_num == 4:
|
|
126
|
+
status_color = Color.BRIGHT_GREEN
|
|
127
|
+
elif rand_num == 5:
|
|
128
|
+
status_color = Color.BRIGHT_BLUE
|
|
129
|
+
|
|
130
|
+
return stylize(self.visible_prompt, style=status_color)
|
|
131
|
+
|
|
132
|
+
def _add_alerts_func(self) -> None:
|
|
133
|
+
"""Print alerts and update the prompt any time the prompt is showing."""
|
|
134
|
+
self._alert_count = 0
|
|
135
|
+
|
|
136
|
+
alerts = get_alerts()
|
|
137
|
+
alert_index = 0
|
|
138
|
+
last_alert_time = 0.0
|
|
139
|
+
|
|
140
|
+
while not self._stop_event.is_set():
|
|
141
|
+
cur_time = time.monotonic()
|
|
142
|
+
alert_msg = None
|
|
143
|
+
soft_wrap = True
|
|
144
|
+
|
|
145
|
+
# Trigger the next alert every 4 seconds
|
|
146
|
+
if cur_time - last_alert_time >= 4.0:
|
|
147
|
+
alert_msg, soft_wrap = alerts[alert_index]
|
|
148
|
+
alert_index = (alert_index + 1) % len(alerts)
|
|
149
|
+
self._alert_count += 1
|
|
150
|
+
last_alert_time = cur_time
|
|
151
|
+
|
|
152
|
+
# Build a new prompt (color changes randomly)
|
|
153
|
+
new_prompt = self._build_colored_prompt()
|
|
154
|
+
|
|
155
|
+
# Check if we have an alert to print
|
|
156
|
+
if alert_msg is not None:
|
|
157
|
+
# Wrap the alert message and an empty string in a Rich Group.
|
|
158
|
+
# This is a clean way to append a blank line after any RenderableType
|
|
159
|
+
# (strings, panels, tables, etc.) for visual separation.
|
|
160
|
+
self.add_alert(
|
|
161
|
+
msg=Group(alert_msg, ""),
|
|
162
|
+
soft_wrap=soft_wrap,
|
|
163
|
+
prompt=new_prompt,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
new_title = f"Alerts Printed: {self._alert_count}"
|
|
167
|
+
self.set_window_title(new_title)
|
|
168
|
+
|
|
169
|
+
# Otherwise, check if the prompt needs to be updated or refreshed
|
|
170
|
+
elif self.prompt != new_prompt:
|
|
171
|
+
self.add_alert(prompt=new_prompt)
|
|
172
|
+
|
|
173
|
+
self._stop_event.wait(1.0)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
import sys
|
|
178
|
+
|
|
179
|
+
app = AlerterApp()
|
|
180
|
+
app.set_window_title("Asynchronous Printer Test")
|
|
181
|
+
sys.exit(app.cmdloop())
|
|
@@ -1258,7 +1258,7 @@ def test_ctrl_d_at_prompt(say_app, monkeypatch) -> None:
|
|
|
1258
1258
|
(None, "", False),
|
|
1259
1259
|
],
|
|
1260
1260
|
)
|
|
1261
|
-
def test_async_alert(base_app, msg, prompt, is_stale) -> None:
|
|
1261
|
+
def test_async_alert(base_app: cmd2.Cmd, msg: str, prompt: str, is_stale: bool) -> None:
|
|
1262
1262
|
import time
|
|
1263
1263
|
|
|
1264
1264
|
with (
|
|
@@ -1311,18 +1311,82 @@ def test_async_alert(base_app, msg, prompt, is_stale) -> None:
|
|
|
1311
1311
|
assert base_app.prompt == prompt
|
|
1312
1312
|
|
|
1313
1313
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1314
|
+
@pytest.mark.skipif(
|
|
1315
|
+
sys.platform.startswith("win"),
|
|
1316
|
+
reason="Don't have a real Windows console with how we are currently running tests in GitHub Actions",
|
|
1317
|
+
)
|
|
1318
|
+
def test_async_alert_rich(base_app: cmd2.Cmd) -> None:
|
|
1319
|
+
from prompt_toolkit.formatted_text import ANSI
|
|
1320
|
+
from rich.table import Table
|
|
1321
|
+
|
|
1322
|
+
table = Table("Header")
|
|
1323
|
+
table.add_row("Value")
|
|
1324
|
+
|
|
1325
|
+
with (
|
|
1326
|
+
mock.patch("cmd2.cmd2.print_formatted_text") as mock_print,
|
|
1327
|
+
mock.patch("cmd2.cmd2.get_app") as mock_get_app,
|
|
1328
|
+
):
|
|
1329
|
+
mock_app = mock.MagicMock()
|
|
1330
|
+
mock_get_app.return_value = mock_app
|
|
1331
|
+
|
|
1332
|
+
# Add alert with a Rich table
|
|
1333
|
+
base_app.add_alert(msg=table, soft_wrap=False)
|
|
1334
|
+
|
|
1335
|
+
with create_pipe_input() as pipe_input:
|
|
1336
|
+
base_app.main_session = PromptSession(
|
|
1337
|
+
input=pipe_input,
|
|
1338
|
+
output=DummyOutput(),
|
|
1339
|
+
history=base_app.main_session.history,
|
|
1340
|
+
completer=base_app.main_session.completer,
|
|
1341
|
+
)
|
|
1342
|
+
pipe_input.send_text("quit\n")
|
|
1343
|
+
base_app._cmdloop()
|
|
1344
|
+
|
|
1345
|
+
# Verify that print_formatted_text was called with a formatted ANSI object
|
|
1346
|
+
assert mock_print.called
|
|
1347
|
+
args, _kwargs = mock_print.call_args
|
|
1348
|
+
printed_arg = args[0]
|
|
1349
|
+
assert isinstance(printed_arg, ANSI)
|
|
1350
|
+
|
|
1351
|
+
# The printed text should contain our table values
|
|
1352
|
+
assert "Header" in printed_arg.value
|
|
1353
|
+
assert "Value" in printed_arg.value
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
def test_add_alert(base_app: cmd2.Cmd) -> None:
|
|
1357
|
+
base_app._alert_queue.clear()
|
|
1316
1358
|
|
|
1317
1359
|
# Nothing is added when both are None
|
|
1318
1360
|
base_app.add_alert(msg=None, prompt=None)
|
|
1319
|
-
assert
|
|
1361
|
+
assert not base_app._alert_queue
|
|
1320
1362
|
|
|
1321
|
-
#
|
|
1363
|
+
# Test that soft_wrap defaults to True
|
|
1322
1364
|
base_app.add_alert(msg="Hello", prompt=None)
|
|
1323
|
-
|
|
1365
|
+
last_alert = base_app._alert_queue[-1]
|
|
1366
|
+
assert last_alert.msg == "Hello"
|
|
1367
|
+
assert last_alert.soft_wrap is True
|
|
1368
|
+
assert last_alert.prompt is None
|
|
1369
|
+
|
|
1370
|
+
# Test that soft_wrap can be set to True
|
|
1371
|
+
base_app.add_alert(msg="Hello", soft_wrap=True, prompt=None)
|
|
1372
|
+
last_alert = base_app._alert_queue[-1]
|
|
1373
|
+
assert last_alert.msg == "Hello"
|
|
1374
|
+
assert last_alert.soft_wrap is True
|
|
1375
|
+
assert last_alert.prompt is None
|
|
1376
|
+
|
|
1377
|
+
# Test that soft_wrap can be set to False
|
|
1378
|
+
base_app.add_alert(msg="Hello", soft_wrap=False, prompt=None)
|
|
1379
|
+
last_alert = base_app._alert_queue[-1]
|
|
1380
|
+
assert last_alert.msg == "Hello"
|
|
1381
|
+
assert last_alert.soft_wrap is False
|
|
1382
|
+
assert last_alert.prompt is None
|
|
1383
|
+
|
|
1384
|
+
# Test only prompt
|
|
1324
1385
|
base_app.add_alert(msg=None, prompt="prompt> ")
|
|
1325
|
-
|
|
1386
|
+
last_alert = base_app._alert_queue[-1]
|
|
1387
|
+
assert last_alert.msg is None
|
|
1388
|
+
assert last_alert.soft_wrap is True
|
|
1389
|
+
assert last_alert.prompt == "prompt> "
|
|
1326
1390
|
|
|
1327
1391
|
|
|
1328
1392
|
def test_visible_prompt() -> None:
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
"""A simple example demonstrating an application that asynchronously prints alerts, updates the prompt
|
|
3
|
-
and changes the window title.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import secrets
|
|
7
|
-
import threading
|
|
8
|
-
import time
|
|
9
|
-
|
|
10
|
-
import cmd2
|
|
11
|
-
from cmd2 import (
|
|
12
|
-
Color,
|
|
13
|
-
stylize,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
ALERTS = [
|
|
17
|
-
"Watch as this application prints alerts and updates the prompt",
|
|
18
|
-
"This will only happen when the prompt is present",
|
|
19
|
-
"Notice how it doesn't interfere with your typing or cursor location",
|
|
20
|
-
"Go ahead and type some stuff and move the cursor throughout the line",
|
|
21
|
-
"Keep typing...",
|
|
22
|
-
"Move that cursor...",
|
|
23
|
-
"Pretty seamless, eh?",
|
|
24
|
-
"Feedback can also be given in the window title. Notice the alert count up there?",
|
|
25
|
-
"You can stop and start the alerts by typing stop_alerts and start_alerts",
|
|
26
|
-
"This demo will now continue to print alerts at random intervals",
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class AlerterApp(cmd2.Cmd):
|
|
31
|
-
"""An app that shows off async_alert() and async_update_prompt()."""
|
|
32
|
-
|
|
33
|
-
def __init__(self) -> None:
|
|
34
|
-
"""Initializer."""
|
|
35
|
-
super().__init__()
|
|
36
|
-
|
|
37
|
-
self.prompt = "(APR)> "
|
|
38
|
-
|
|
39
|
-
# The thread that will asynchronously alert the user of events
|
|
40
|
-
self._stop_event = threading.Event()
|
|
41
|
-
self._add_alert_thread = threading.Thread()
|
|
42
|
-
self._alert_count = 0
|
|
43
|
-
self._next_alert_time = 0.0
|
|
44
|
-
|
|
45
|
-
# Create some hooks to handle the starting and stopping of our thread
|
|
46
|
-
self.register_preloop_hook(self._preloop_hook)
|
|
47
|
-
self.register_postloop_hook(self._postloop_hook)
|
|
48
|
-
|
|
49
|
-
# Create an instance of SystemRandom
|
|
50
|
-
self._secure_generator = secrets.SystemRandom()
|
|
51
|
-
|
|
52
|
-
def _preloop_hook(self) -> None:
|
|
53
|
-
"""Start the alerter thread."""
|
|
54
|
-
self._stop_event.clear()
|
|
55
|
-
self._add_alert_thread = threading.Thread(name="alerter", target=self._add_alerts_func)
|
|
56
|
-
self._add_alert_thread.start()
|
|
57
|
-
|
|
58
|
-
def _postloop_hook(self) -> None:
|
|
59
|
-
"""Stops the alerter thread."""
|
|
60
|
-
self._stop_event.set()
|
|
61
|
-
if self._add_alert_thread.is_alive():
|
|
62
|
-
self._add_alert_thread.join()
|
|
63
|
-
|
|
64
|
-
def do_start_alerts(self, _: cmd2.Statement) -> None:
|
|
65
|
-
"""Starts the alerter thread."""
|
|
66
|
-
if self._add_alert_thread.is_alive():
|
|
67
|
-
print("The alert thread is already started")
|
|
68
|
-
else:
|
|
69
|
-
self._stop_event.clear()
|
|
70
|
-
self._add_alert_thread = threading.Thread(name="alerter", target=self._add_alerts_func)
|
|
71
|
-
self._add_alert_thread.start()
|
|
72
|
-
|
|
73
|
-
def do_stop_alerts(self, _: cmd2.Statement) -> None:
|
|
74
|
-
"""Stops the alerter thread."""
|
|
75
|
-
self._stop_event.set()
|
|
76
|
-
if self._add_alert_thread.is_alive():
|
|
77
|
-
self._add_alert_thread.join()
|
|
78
|
-
else:
|
|
79
|
-
print("The alert thread is already stopped")
|
|
80
|
-
|
|
81
|
-
def _get_alerts(self) -> list[str]:
|
|
82
|
-
"""Reports alerts
|
|
83
|
-
:return: the list of alerts.
|
|
84
|
-
"""
|
|
85
|
-
cur_time = time.monotonic()
|
|
86
|
-
if cur_time < self._next_alert_time:
|
|
87
|
-
return []
|
|
88
|
-
|
|
89
|
-
alerts = []
|
|
90
|
-
|
|
91
|
-
if self._alert_count < len(ALERTS):
|
|
92
|
-
alerts.append(ALERTS[self._alert_count])
|
|
93
|
-
self._alert_count += 1
|
|
94
|
-
self._next_alert_time = cur_time + 4
|
|
95
|
-
|
|
96
|
-
else:
|
|
97
|
-
rand_num = self._secure_generator.randint(1, 20)
|
|
98
|
-
if rand_num > 2:
|
|
99
|
-
return []
|
|
100
|
-
|
|
101
|
-
for _ in range(rand_num):
|
|
102
|
-
self._alert_count += 1
|
|
103
|
-
alerts.append(f"Alert {self._alert_count}")
|
|
104
|
-
|
|
105
|
-
self._next_alert_time = 0
|
|
106
|
-
|
|
107
|
-
return alerts
|
|
108
|
-
|
|
109
|
-
def _build_alert_str(self) -> str:
|
|
110
|
-
"""Combines alerts into one string that can be printed to the terminal
|
|
111
|
-
:return: the alert string.
|
|
112
|
-
"""
|
|
113
|
-
alert_str = ""
|
|
114
|
-
alerts = self._get_alerts()
|
|
115
|
-
|
|
116
|
-
longest_alert = max(ALERTS, key=len)
|
|
117
|
-
num_asterisks = len(longest_alert) + 8
|
|
118
|
-
|
|
119
|
-
for i, cur_alert in enumerate(alerts):
|
|
120
|
-
# Use padding to center the alert
|
|
121
|
-
padding = " " * int((num_asterisks - len(cur_alert)) / 2)
|
|
122
|
-
|
|
123
|
-
if i > 0:
|
|
124
|
-
alert_str += "\n"
|
|
125
|
-
alert_str += "*" * num_asterisks + "\n"
|
|
126
|
-
alert_str += padding + cur_alert + padding + "\n"
|
|
127
|
-
alert_str += "*" * num_asterisks + "\n"
|
|
128
|
-
|
|
129
|
-
return alert_str
|
|
130
|
-
|
|
131
|
-
def _build_colored_prompt(self) -> str:
|
|
132
|
-
"""Randomly builds a colored prompt
|
|
133
|
-
:return: the new prompt.
|
|
134
|
-
"""
|
|
135
|
-
rand_num = self._secure_generator.randint(1, 6)
|
|
136
|
-
|
|
137
|
-
status_color = Color.DEFAULT
|
|
138
|
-
|
|
139
|
-
if rand_num == 1:
|
|
140
|
-
status_color = Color.BRIGHT_RED
|
|
141
|
-
elif rand_num == 2:
|
|
142
|
-
status_color = Color.BRIGHT_YELLOW
|
|
143
|
-
elif rand_num == 3:
|
|
144
|
-
status_color = Color.CYAN
|
|
145
|
-
elif rand_num == 4:
|
|
146
|
-
status_color = Color.BRIGHT_GREEN
|
|
147
|
-
elif rand_num == 5:
|
|
148
|
-
status_color = Color.BRIGHT_BLUE
|
|
149
|
-
|
|
150
|
-
return stylize(self.visible_prompt, style=status_color)
|
|
151
|
-
|
|
152
|
-
def _add_alerts_func(self) -> None:
|
|
153
|
-
"""Prints alerts and updates the prompt any time the prompt is showing."""
|
|
154
|
-
self._alert_count = 0
|
|
155
|
-
self._next_alert_time = 0
|
|
156
|
-
|
|
157
|
-
while not self._stop_event.is_set():
|
|
158
|
-
# Get any alerts that need to be printed
|
|
159
|
-
alert_str = self._build_alert_str()
|
|
160
|
-
|
|
161
|
-
# Build a new prompt
|
|
162
|
-
new_prompt = self._build_colored_prompt()
|
|
163
|
-
|
|
164
|
-
# Check if we have alerts to print
|
|
165
|
-
if alert_str:
|
|
166
|
-
self.add_alert(msg=alert_str, prompt=new_prompt)
|
|
167
|
-
new_title = f"Alerts Printed: {self._alert_count}"
|
|
168
|
-
self.set_window_title(new_title)
|
|
169
|
-
|
|
170
|
-
# Otherwise check if the prompt needs to be updated or refreshed
|
|
171
|
-
elif self.prompt != new_prompt:
|
|
172
|
-
self.add_alert(prompt=new_prompt)
|
|
173
|
-
|
|
174
|
-
self._stop_event.wait(0.5)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if __name__ == "__main__":
|
|
178
|
-
import sys
|
|
179
|
-
|
|
180
|
-
app = AlerterApp()
|
|
181
|
-
app.set_window_title("Asynchronous Printer Test")
|
|
182
|
-
sys.exit(app.cmdloop())
|
|
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
|