cmd2 4.0.0b4__tar.gz → 4.0.0b5__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.
Files changed (210) hide show
  1. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/CHANGELOG.md +7 -2
  2. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/PKG-INFO +1 -1
  3. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/__init__.py +0 -2
  4. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/argparse_utils.py +77 -34
  5. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/cmd2.py +168 -103
  6. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/completion.py +22 -6
  7. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/decorators.py +4 -4
  8. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/rich_utils.py +0 -22
  9. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2.egg-info/PKG-INFO +1 -1
  10. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2.egg-info/SOURCES.txt +1 -0
  11. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/argument_processing.md +1 -1
  12. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/builtin_commands.md +1 -0
  13. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/generating_output.md +3 -1
  14. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/README.md +2 -0
  15. cmd2-4.0.0b5/examples/completion_item_choices.py +145 -0
  16. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/scripts/save_help_text.py +2 -2
  17. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_argparse_utils.py +54 -7
  18. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_cmd2.py +122 -38
  19. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_commandset.py +5 -5
  20. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_completion.py +31 -0
  21. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/.pre-commit-config.yaml +0 -0
  22. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/.prettierignore +0 -0
  23. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/.prettierrc +0 -0
  24. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/.typos.toml +0 -0
  25. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/GEMINI.md +0 -0
  26. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/LICENSE +0 -0
  27. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/MANIFEST.in +0 -0
  28. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/Makefile +0 -0
  29. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/README.md +0 -0
  30. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/argparse_completer.py +0 -0
  31. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/clipboard.py +0 -0
  32. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/colors.py +0 -0
  33. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/command_set.py +0 -0
  34. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/constants.py +0 -0
  35. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/exceptions.py +0 -0
  36. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/history.py +0 -0
  37. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/parsing.py +0 -0
  38. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/plugin.py +0 -0
  39. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/pt_utils.py +0 -0
  40. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/py.typed +0 -0
  41. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/py_bridge.py +0 -0
  42. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/string_utils.py +0 -0
  43. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/styles.py +0 -0
  44. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/types.py +0 -0
  45. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2/utils.py +0 -0
  46. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2.egg-info/dependency_links.txt +0 -0
  47. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2.egg-info/requires.txt +0 -0
  48. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2.egg-info/top_level.txt +0 -0
  49. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/cmd2.png +0 -0
  50. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/codecov.yml +0 -0
  51. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/argparse_completer.md +0 -0
  52. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/argparse_utils.md +0 -0
  53. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/clipboard.md +0 -0
  54. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/cmd.md +0 -0
  55. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/colors.md +0 -0
  56. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/command_set.md +0 -0
  57. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/completion.md +0 -0
  58. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/constants.md +0 -0
  59. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/decorators.md +0 -0
  60. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/exceptions.md +0 -0
  61. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/history.md +0 -0
  62. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/index.md +0 -0
  63. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/parsing.md +0 -0
  64. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/plugin.md +0 -0
  65. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/pt_utils.md +0 -0
  66. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/py_bridge.md +0 -0
  67. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/rich_utils.md +0 -0
  68. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/string_utils.md +0 -0
  69. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/styles.md +0 -0
  70. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/api/utils.md +0 -0
  71. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/doc_conventions.md +0 -0
  72. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/examples/alternate_event_loops.md +0 -0
  73. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/examples/examples.md +0 -0
  74. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/examples/getting_started.md +0 -0
  75. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/examples/index.md +0 -0
  76. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/async_commands.md +0 -0
  77. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/clipboard.md +0 -0
  78. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/commands.md +0 -0
  79. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/completion.md +0 -0
  80. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/disable_commands.md +0 -0
  81. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/embedded_python_shells.md +0 -0
  82. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/help.md +0 -0
  83. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/history.md +0 -0
  84. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/hooks.md +0 -0
  85. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/index.md +0 -0
  86. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/initialization.md +0 -0
  87. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/misc.md +0 -0
  88. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/modular_commands.md +0 -0
  89. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/multiline_commands.md +0 -0
  90. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/os.md +0 -0
  91. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/packaging.md +0 -0
  92. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/plugins.md +0 -0
  93. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/prompt.md +0 -0
  94. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/redirection.md +0 -0
  95. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/scripting.md +0 -0
  96. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/settings.md +0 -0
  97. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/shortcuts_aliases_macros.md +0 -0
  98. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/startup_commands.md +0 -0
  99. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/table_creation.md +0 -0
  100. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/features/theme.md +0 -0
  101. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/index.md +0 -0
  102. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/javascripts/readthedocs.js +0 -0
  103. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/migrating/incompatibilities.md +0 -0
  104. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/migrating/index.md +0 -0
  105. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/migrating/minimum.md +0 -0
  106. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/migrating/next_steps.md +0 -0
  107. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/migrating/why.md +0 -0
  108. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/mixins/index.md +0 -0
  109. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/mixins/mixin_template.md +0 -0
  110. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/overrides/main.html +0 -0
  111. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/overview/alternatives.md +0 -0
  112. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/overview/index.md +0 -0
  113. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/overview/installation.md +0 -0
  114. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/overview/integrating.md +0 -0
  115. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/overview/resources.md +0 -0
  116. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/stylesheets/cmd2.css +0 -0
  117. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/testing.md +0 -0
  118. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/docs/upgrades.md +0 -0
  119. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/.cmd2rc +0 -0
  120. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/argparse_completion.py +0 -0
  121. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/argparse_example.py +0 -0
  122. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/async_call.py +0 -0
  123. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/async_commands.py +0 -0
  124. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/async_printing.py +0 -0
  125. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/basic_completion.py +0 -0
  126. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/cmd_as_argument.py +0 -0
  127. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/color.py +0 -0
  128. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/command_sets.py +0 -0
  129. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/custom_parser.py +0 -0
  130. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/custom_types.py +0 -0
  131. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/default_categories.py +0 -0
  132. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/dynamic_commands.py +0 -0
  133. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/environment.py +0 -0
  134. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/event_loops.py +0 -0
  135. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/exit_code.py +0 -0
  136. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/getting_started.py +0 -0
  137. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/hello_cmd2.py +0 -0
  138. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/help_categories.py +0 -0
  139. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/hooks.py +0 -0
  140. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/migrating.py +0 -0
  141. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/mixin.py +0 -0
  142. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/modular_commands/__init__.py +0 -0
  143. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/modular_commands/commandset_basic.py +0 -0
  144. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/modular_commands/commandset_complex.py +0 -0
  145. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/modular_commands/commandset_custominit.py +0 -0
  146. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/modular_commandsets.py +0 -0
  147. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/paged_output.py +0 -0
  148. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/persistent_history.py +0 -0
  149. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/pretty_print.py +0 -0
  150. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/python_scripting.py +0 -0
  151. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/read_input.py +0 -0
  152. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/remove_builtin_commands.py +0 -0
  153. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/remove_settable.py +0 -0
  154. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/rich_tables.py +0 -0
  155. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/rich_theme.py +0 -0
  156. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/scripts/arg_printer.py +0 -0
  157. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/scripts/conditional.py +0 -0
  158. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/scripts/nested.txt +0 -0
  159. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/scripts/quit.txt +0 -0
  160. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/scripts/script.py +0 -0
  161. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/scripts/script.txt +0 -0
  162. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/tmux_launch.sh +0 -0
  163. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/tmux_split.sh +0 -0
  164. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/examples/unicode_commands.py +0 -0
  165. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/mkdocs.yml +0 -0
  166. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/package.json +0 -0
  167. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/pyproject.toml +0 -0
  168. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/ruff.toml +0 -0
  169. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/scripts/validate_tag.py +0 -0
  170. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/setup.cfg +0 -0
  171. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/.cmd2rc +0 -0
  172. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/__init__.py +0 -0
  173. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/conftest.py +0 -0
  174. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/pyscript/echo.py +0 -0
  175. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/pyscript/environment.py +0 -0
  176. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/pyscript/help.py +0 -0
  177. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/pyscript/py_locals.py +0 -0
  178. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/pyscript/pyscript_dir.py +0 -0
  179. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/pyscript/raises_exception.py +0 -0
  180. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/pyscript/recursive.py +0 -0
  181. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/pyscript/self_in_py.py +0 -0
  182. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/pyscript/stdout_capture.py +0 -0
  183. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/pyscript/stop.py +0 -0
  184. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/relative_multiple.txt +0 -0
  185. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/script.py +0 -0
  186. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/script.txt +0 -0
  187. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/scripts/binary.bin +0 -0
  188. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/scripts/empty.txt +0 -0
  189. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/scripts/help.txt +0 -0
  190. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/scripts/nested.txt +0 -0
  191. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/scripts/one_down.txt +0 -0
  192. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/scripts/postcmds.txt +0 -0
  193. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/scripts/precmds.txt +0 -0
  194. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/scripts/utf8.txt +0 -0
  195. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_argparse.py +0 -0
  196. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_argparse_completer.py +0 -0
  197. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_argparse_subcommands.py +0 -0
  198. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_categories.py +0 -0
  199. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_dynamic_complete_style.py +0 -0
  200. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_future_annotations.py +0 -0
  201. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_history.py +0 -0
  202. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_parsing.py +0 -0
  203. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_plugin.py +0 -0
  204. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_pt_utils.py +0 -0
  205. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_py_completion.py +0 -0
  206. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_rich_utils.py +0 -0
  207. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_run_pyscript.py +0 -0
  208. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_string_utils.py +0 -0
  209. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_utils.py +0 -0
  210. {cmd2-4.0.0b4 → cmd2-4.0.0b5}/tests/test_utils_defining_class.py +0 -0
@@ -96,6 +96,9 @@ prompt is displayed.
96
96
  - Replaced the global `APP_THEME` constant in `rich_utils.py` with `get_theme()` and
97
97
  `set_theme()` functions to support lazy initialization and safer in-place updates of the
98
98
  theme.
99
+ - Renamed `Cmd._command_parsers` to `Cmd.command_parsers`.
100
+ - Removed `RichPrintKwargs` `TypedDict` in favor of using `Mapping[str, Any]`, allowing for
101
+ greater flexibility in passing keyword arguments to `console.print()` calls.
99
102
  - Enhancements
100
103
  - New `cmd2.Cmd` parameters
101
104
  - **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These
@@ -113,8 +116,9 @@ prompt is displayed.
113
116
  - **read_secret**: read secrets like passwords without displaying them to the terminal
114
117
  - **ppretty**: a cmd2-compatible replacement for `rich.pretty.pprint()`
115
118
  - New settables:
116
- - **max_column_completion_results**: (int) the maximum number of completion results to
117
- display in a single column
119
+ - **max_column_completion_results**: (int) Maximum number of completion results to display
120
+ in a single column
121
+ - **traceback_show_locals**: (bool) Display local variables in tracebacks
118
122
  - `cmd2.Cmd.select` has been revamped to use the
119
123
  [choice](https://python-prompt-toolkit.readthedocs.io/en/3.0.52/pages/asking_for_a_choice.html)
120
124
  function from `prompt-toolkit` when both **stdin** and **stdout** are TTYs
@@ -130,6 +134,7 @@ prompt is displayed.
130
134
  specific `cmd2.Cmd` subclass (e.g.,`class MyCommandSet(CommandSet[MyApp]):`). This provides
131
135
  full type hints and IDE autocompletion for `self._cmd` without needing to override and cast
132
136
  the property.
137
+ - Added `traceback_kwargs` attribute to allow customization of Rich-based tracebacks.
133
138
 
134
139
  ## 3.5.0 (April 13, 2026)
135
140
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmd2
3
- Version: 4.0.0b4
3
+ Version: 4.0.0b5
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
@@ -50,7 +50,6 @@ from .rich_utils import (
50
50
  MetavarTypeCmd2HelpFormatter,
51
51
  RawDescriptionCmd2HelpFormatter,
52
52
  RawTextCmd2HelpFormatter,
53
- RichPrintKwargs,
54
53
  TextGroup,
55
54
  get_theme,
56
55
  set_theme,
@@ -105,7 +104,6 @@ __all__: list[str] = [ # noqa: RUF022
105
104
  "MetavarTypeCmd2HelpFormatter",
106
105
  "RawDescriptionCmd2HelpFormatter",
107
106
  "RawTextCmd2HelpFormatter",
108
- "RichPrintKwargs",
109
107
  "set_theme",
110
108
  "TextGroup",
111
109
  # String Utils
@@ -494,6 +494,52 @@ def _ActionsContainer_add_argument( # noqa: N802
494
494
  # Overwrite _ActionsContainer.add_argument with our patch
495
495
  argparse._ActionsContainer.add_argument = _ActionsContainer_add_argument # type: ignore[method-assign]
496
496
 
497
+ ############################################################################################################
498
+ # Patch argparse._SubParsersAction by adding remove_parser() function
499
+ ############################################################################################################
500
+
501
+
502
+ def _SubParsersAction_remove_parser( # noqa: N802
503
+ self: argparse._SubParsersAction, # type: ignore[type-arg]
504
+ name: str,
505
+ ) -> argparse.ArgumentParser:
506
+ """Remove a subparser from a subparsers group.
507
+
508
+ This function is added by cmd2 as a method called ``remove_parser()``
509
+ to ``argparse._SubParsersAction`` class.
510
+
511
+ To call: ``action.remove_parser(name)``
512
+
513
+ :param self: instance of the _SubParsersAction being edited
514
+ :param name: name of the subcommand for the subparser to remove
515
+ :return: the removed parser
516
+ :raises ValueError: if the subcommand doesn't exist
517
+ """
518
+ if name not in self._name_parser_map:
519
+ raise ValueError(f"Subcommand '{name}' does not exist")
520
+
521
+ subparser = self._name_parser_map[name]
522
+
523
+ # Find all names (primary and aliases) that map to this subparser
524
+ all_names = [cur_name for cur_name, cur_parser in self._name_parser_map.items() if cur_parser is subparser]
525
+
526
+ # Remove the help entry for this subparser. To handle the case where
527
+ # name is an alias, we remove the action whose 'dest' matches any of
528
+ # the names mapped to this subparser.
529
+ for choice_action in self._choices_actions:
530
+ if choice_action.dest in all_names:
531
+ self._choices_actions.remove(choice_action)
532
+ break
533
+
534
+ # Remove all references to this subparser, including aliases.
535
+ for cur_name in all_names:
536
+ del self._name_parser_map[cur_name]
537
+
538
+ return cast(argparse.ArgumentParser, subparser)
539
+
540
+
541
+ argparse._SubParsersAction.remove_parser = _SubParsersAction_remove_parser # type: ignore[attr-defined]
542
+
497
543
 
498
544
  class Cmd2ArgumentParser(argparse.ArgumentParser):
499
545
  """Custom ArgumentParser class that improves error and help output."""
@@ -556,7 +602,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
556
602
  self.description: RenderableType | None # type: ignore[assignment]
557
603
  self.epilog: RenderableType | None # type: ignore[assignment]
558
604
 
559
- def _get_subparsers_action(self) -> "argparse._SubParsersAction[Cmd2ArgumentParser]":
605
+ def get_subparsers_action(self) -> "argparse._SubParsersAction[Cmd2ArgumentParser]":
560
606
  """Get the _SubParsersAction for this parser if it exists.
561
607
 
562
608
  :return: the _SubParsersAction for this parser
@@ -619,7 +665,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
619
665
  self.prog = prog
620
666
 
621
667
  try:
622
- subparsers_action = self._get_subparsers_action()
668
+ subparsers_action = self.get_subparsers_action()
623
669
  except ValueError:
624
670
  # This parser has no subcommands
625
671
  return
@@ -638,12 +684,14 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
638
684
  # add_parser() will have the correct prog value.
639
685
  subparsers_action._prog_prefix = self._build_subparsers_prog_prefix(positionals)
640
686
 
641
- # subparsers_action.choices includes aliases. Since primary names are inserted first,
642
- # we skip already updated parsers to ensure primary names are used in 'prog'.
687
+ # subparsers_action._name_parser_map includes aliases. Since primary names are inserted
688
+ # first, we skip already updated parsers to ensure primary names are used in 'prog'.
689
+ # We can't rely on subparsers_action._choices_actions to filter out aliases because while
690
+ # it contains only primary names, it omits any subcommands that lack help text.
643
691
  updated_parsers: set[Cmd2ArgumentParser] = set()
644
692
 
645
693
  # Set the prog value for each subcommand's parser
646
- for subcmd_name, subcmd_parser in subparsers_action.choices.items():
694
+ for subcmd_name, subcmd_parser in subparsers_action._name_parser_map.items():
647
695
  if subcmd_parser in updated_parsers:
648
696
  continue
649
697
 
@@ -651,7 +699,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
651
699
  subcmd_parser.update_prog(subcmd_prog)
652
700
  updated_parsers.add(subcmd_parser)
653
701
 
654
- def _find_parser(self, subcommand_path: Iterable[str]) -> "Cmd2ArgumentParser":
702
+ def find_parser(self, subcommand_path: Iterable[str]) -> "Cmd2ArgumentParser":
655
703
  """Find a parser in the hierarchy based on a sequence of subcommand names.
656
704
 
657
705
  :param subcommand_path: sequence of subcommand names leading to the target parser
@@ -660,10 +708,10 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
660
708
  """
661
709
  parser = self
662
710
  for name in subcommand_path:
663
- subparsers_action = parser._get_subparsers_action()
664
- if name not in subparsers_action.choices:
665
- raise ValueError(f"Subcommand '{name}' not found in '{parser.prog}'")
666
- parser = subparsers_action.choices[name]
711
+ subparsers_action = parser.get_subparsers_action()
712
+ if name not in subparsers_action._name_parser_map:
713
+ raise ValueError(f"Subcommand '{name}' does not exist for '{parser.prog}'")
714
+ parser = subparsers_action._name_parser_map[name]
667
715
  return parser
668
716
 
669
717
  def attach_subcommand(
@@ -683,7 +731,8 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
683
731
  :raises TypeError: if subcommand_parser is not an instance of the following or their subclasses:
684
732
  1. Cmd2ArgumentParser
685
733
  2. The parser_class configured for the target subcommand group
686
- :raises ValueError: if the command path is invalid or doesn't support subcommands
734
+ :raises ValueError: if the command path is invalid, doesn't support subcommands, or the
735
+ subcommand already exists
687
736
  """
688
737
  if not isinstance(subcommand_parser, Cmd2ArgumentParser):
689
738
  raise TypeError(
@@ -691,8 +740,8 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
691
740
  f"Received: '{type(subcommand_parser).__name__}'."
692
741
  )
693
742
 
694
- target_parser = self._find_parser(subcommand_path)
695
- subparsers_action = target_parser._get_subparsers_action()
743
+ target_parser = self.find_parser(subcommand_path)
744
+ subparsers_action = target_parser.get_subparsers_action()
696
745
 
697
746
  # Verify the parser is compatible with the 'parser_class' configured for this
698
747
  # subcommand group. We use isinstance() here to allow for subclasses, providing
@@ -705,6 +754,12 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
705
754
  f"Received: '{type(subcommand_parser).__name__}'."
706
755
  )
707
756
 
757
+ # Do not overwrite existing subcommands or aliases
758
+ all_names = (subcommand, *add_parser_kwargs.get("aliases", ()))
759
+ for name in all_names:
760
+ if name in subparsers_action._name_parser_map:
761
+ raise ValueError(f"Subcommand '{name}' already exists for '{target_parser.prog}'")
762
+
708
763
  # Use add_parser to register the subcommand name and any aliases
709
764
  placeholder_parser = subparsers_action.add_parser(subcommand, **add_parser_kwargs)
710
765
 
@@ -728,28 +783,16 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
728
783
  :return: the detached parser
729
784
  :raises ValueError: if the command path is invalid or the subcommand doesn't exist
730
785
  """
731
- target_parser = self._find_parser(subcommand_path)
732
- subparsers_action = target_parser._get_subparsers_action()
733
-
734
- subparser = subparsers_action._name_parser_map.get(subcommand)
735
- if subparser is None:
736
- raise ValueError(f"Subcommand '{subcommand}' not found in '{target_parser.prog}'")
737
-
738
- # Remove this subcommand and all its aliases from the base command
739
- to_remove = []
740
- for cur_name, cur_parser in subparsers_action._name_parser_map.items():
741
- if cur_parser is subparser:
742
- to_remove.append(cur_name)
743
- for cur_name in to_remove:
744
- del subparsers_action._name_parser_map[cur_name]
745
-
746
- # Remove this subcommand from its base command's help text
747
- for choice_action in subparsers_action._choices_actions:
748
- if choice_action.dest == subcommand:
749
- subparsers_action._choices_actions.remove(choice_action)
750
- break
786
+ target_parser = self.find_parser(subcommand_path)
787
+ subparsers_action = target_parser.get_subparsers_action()
751
788
 
752
- return subparser
789
+ try:
790
+ return cast(
791
+ Cmd2ArgumentParser,
792
+ subparsers_action.remove_parser(subcommand), # type: ignore[attr-defined]
793
+ )
794
+ except ValueError:
795
+ raise ValueError(f"Subcommand '{subcommand}' does not exist for '{target_parser.prog}'") from None
753
796
 
754
797
  def error(self, message: str) -> NoReturn:
755
798
  """Override that applies custom formatting to the error message."""