cmd2 4.0.0b4__tar.gz → 4.0.0b6__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 (213) hide show
  1. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/CHANGELOG.md +29 -3
  2. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/PKG-INFO +1 -1
  3. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/__init__.py +0 -2
  4. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/argparse_completer.py +2 -15
  5. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/argparse_utils.py +148 -48
  6. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/cmd2.py +232 -184
  7. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/completion.py +22 -6
  8. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/constants.py +3 -3
  9. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/decorators.py +9 -8
  10. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/parsing.py +10 -10
  11. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/pt_utils.py +3 -4
  12. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/py_bridge.py +5 -14
  13. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/rich_utils.py +43 -71
  14. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/string_utils.py +1 -3
  15. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/utils.py +3 -6
  16. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2.egg-info/PKG-INFO +1 -1
  17. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2.egg-info/SOURCES.txt +2 -1
  18. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/argument_processing.md +1 -1
  19. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/builtin_commands.md +1 -1
  20. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/generating_output.md +3 -1
  21. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/initialization.md +0 -1
  22. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/settings.md +1 -6
  23. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/README.md +2 -0
  24. cmd2-4.0.0b6/examples/completion_item_choices.py +145 -0
  25. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/scripts/save_help_text.py +2 -2
  26. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/conftest.py +2 -9
  27. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/pyscript/environment.py +0 -2
  28. cmd2-4.0.0b6/tests/pyscript/test_print.py +29 -0
  29. cmd2-4.0.0b6/tests/scripts/postcmds.txt +1 -0
  30. cmd2-4.0.0b6/tests/scripts/precmds.txt +1 -0
  31. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_argparse_utils.py +191 -8
  32. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_cmd2.py +172 -106
  33. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_commandset.py +5 -5
  34. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_completion.py +32 -1
  35. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_pt_utils.py +0 -24
  36. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_rich_utils.py +33 -45
  37. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_run_pyscript.py +69 -20
  38. cmd2-4.0.0b4/tests/pyscript/stdout_capture.py +0 -4
  39. cmd2-4.0.0b4/tests/scripts/postcmds.txt +0 -1
  40. cmd2-4.0.0b4/tests/scripts/precmds.txt +0 -1
  41. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/.pre-commit-config.yaml +0 -0
  42. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/.prettierignore +0 -0
  43. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/.prettierrc +0 -0
  44. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/.typos.toml +0 -0
  45. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/GEMINI.md +0 -0
  46. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/LICENSE +0 -0
  47. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/MANIFEST.in +0 -0
  48. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/Makefile +0 -0
  49. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/README.md +0 -0
  50. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/clipboard.py +0 -0
  51. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/colors.py +0 -0
  52. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/command_set.py +0 -0
  53. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/exceptions.py +0 -0
  54. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/history.py +0 -0
  55. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/plugin.py +0 -0
  56. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/py.typed +0 -0
  57. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/styles.py +0 -0
  58. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2/types.py +0 -0
  59. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2.egg-info/dependency_links.txt +0 -0
  60. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2.egg-info/requires.txt +0 -0
  61. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2.egg-info/top_level.txt +0 -0
  62. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/cmd2.png +0 -0
  63. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/codecov.yml +0 -0
  64. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/argparse_completer.md +0 -0
  65. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/argparse_utils.md +0 -0
  66. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/clipboard.md +0 -0
  67. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/cmd.md +0 -0
  68. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/colors.md +0 -0
  69. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/command_set.md +0 -0
  70. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/completion.md +0 -0
  71. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/constants.md +0 -0
  72. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/decorators.md +0 -0
  73. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/exceptions.md +0 -0
  74. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/history.md +0 -0
  75. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/index.md +0 -0
  76. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/parsing.md +0 -0
  77. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/plugin.md +0 -0
  78. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/pt_utils.md +0 -0
  79. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/py_bridge.md +0 -0
  80. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/rich_utils.md +0 -0
  81. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/string_utils.md +0 -0
  82. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/styles.md +0 -0
  83. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/api/utils.md +0 -0
  84. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/doc_conventions.md +0 -0
  85. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/examples/alternate_event_loops.md +0 -0
  86. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/examples/examples.md +0 -0
  87. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/examples/getting_started.md +0 -0
  88. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/examples/index.md +0 -0
  89. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/async_commands.md +0 -0
  90. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/clipboard.md +0 -0
  91. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/commands.md +0 -0
  92. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/completion.md +0 -0
  93. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/disable_commands.md +0 -0
  94. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/embedded_python_shells.md +0 -0
  95. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/help.md +0 -0
  96. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/history.md +0 -0
  97. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/hooks.md +0 -0
  98. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/index.md +0 -0
  99. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/misc.md +0 -0
  100. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/modular_commands.md +0 -0
  101. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/multiline_commands.md +0 -0
  102. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/os.md +0 -0
  103. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/packaging.md +0 -0
  104. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/plugins.md +0 -0
  105. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/prompt.md +0 -0
  106. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/redirection.md +0 -0
  107. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/scripting.md +0 -0
  108. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/shortcuts_aliases_macros.md +0 -0
  109. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/startup_commands.md +0 -0
  110. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/table_creation.md +0 -0
  111. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/features/theme.md +0 -0
  112. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/index.md +0 -0
  113. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/javascripts/readthedocs.js +0 -0
  114. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/migrating/incompatibilities.md +0 -0
  115. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/migrating/index.md +0 -0
  116. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/migrating/minimum.md +0 -0
  117. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/migrating/next_steps.md +0 -0
  118. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/migrating/why.md +0 -0
  119. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/mixins/index.md +0 -0
  120. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/mixins/mixin_template.md +0 -0
  121. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/overrides/main.html +0 -0
  122. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/overview/alternatives.md +0 -0
  123. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/overview/index.md +0 -0
  124. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/overview/installation.md +0 -0
  125. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/overview/integrating.md +0 -0
  126. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/overview/resources.md +0 -0
  127. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/stylesheets/cmd2.css +0 -0
  128. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/testing.md +0 -0
  129. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/docs/upgrades.md +0 -0
  130. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/.cmd2rc +0 -0
  131. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/argparse_completion.py +0 -0
  132. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/argparse_example.py +0 -0
  133. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/async_call.py +0 -0
  134. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/async_commands.py +0 -0
  135. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/async_printing.py +0 -0
  136. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/basic_completion.py +0 -0
  137. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/cmd_as_argument.py +0 -0
  138. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/color.py +0 -0
  139. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/command_sets.py +0 -0
  140. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/custom_parser.py +0 -0
  141. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/custom_types.py +0 -0
  142. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/default_categories.py +0 -0
  143. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/dynamic_commands.py +0 -0
  144. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/environment.py +0 -0
  145. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/event_loops.py +0 -0
  146. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/exit_code.py +0 -0
  147. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/getting_started.py +0 -0
  148. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/hello_cmd2.py +0 -0
  149. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/help_categories.py +0 -0
  150. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/hooks.py +0 -0
  151. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/migrating.py +0 -0
  152. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/mixin.py +0 -0
  153. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/modular_commands/__init__.py +0 -0
  154. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/modular_commands/commandset_basic.py +0 -0
  155. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/modular_commands/commandset_complex.py +0 -0
  156. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/modular_commands/commandset_custominit.py +0 -0
  157. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/modular_commandsets.py +0 -0
  158. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/paged_output.py +0 -0
  159. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/persistent_history.py +0 -0
  160. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/pretty_print.py +0 -0
  161. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/python_scripting.py +0 -0
  162. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/read_input.py +0 -0
  163. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/remove_builtin_commands.py +0 -0
  164. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/remove_settable.py +0 -0
  165. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/rich_tables.py +0 -0
  166. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/rich_theme.py +0 -0
  167. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/scripts/arg_printer.py +0 -0
  168. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/scripts/conditional.py +0 -0
  169. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/scripts/nested.txt +0 -0
  170. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/scripts/quit.txt +0 -0
  171. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/scripts/script.py +0 -0
  172. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/scripts/script.txt +0 -0
  173. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/tmux_launch.sh +0 -0
  174. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/tmux_split.sh +0 -0
  175. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/examples/unicode_commands.py +0 -0
  176. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/mkdocs.yml +0 -0
  177. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/package.json +0 -0
  178. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/pyproject.toml +0 -0
  179. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/ruff.toml +0 -0
  180. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/scripts/validate_tag.py +0 -0
  181. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/setup.cfg +0 -0
  182. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/.cmd2rc +0 -0
  183. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/__init__.py +0 -0
  184. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/pyscript/echo.py +0 -0
  185. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/pyscript/help.py +0 -0
  186. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/pyscript/py_locals.py +0 -0
  187. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/pyscript/pyscript_dir.py +0 -0
  188. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/pyscript/raises_exception.py +0 -0
  189. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/pyscript/recursive.py +0 -0
  190. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/pyscript/self_in_py.py +0 -0
  191. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/pyscript/stop.py +0 -0
  192. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/relative_multiple.txt +0 -0
  193. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/script.py +0 -0
  194. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/script.txt +0 -0
  195. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/scripts/binary.bin +0 -0
  196. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/scripts/empty.txt +0 -0
  197. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/scripts/help.txt +0 -0
  198. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/scripts/nested.txt +0 -0
  199. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/scripts/one_down.txt +0 -0
  200. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/scripts/utf8.txt +0 -0
  201. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_argparse.py +0 -0
  202. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_argparse_completer.py +0 -0
  203. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_argparse_subcommands.py +0 -0
  204. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_categories.py +0 -0
  205. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_dynamic_complete_style.py +0 -0
  206. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_future_annotations.py +0 -0
  207. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_history.py +0 -0
  208. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_parsing.py +0 -0
  209. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_plugin.py +0 -0
  210. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_py_completion.py +0 -0
  211. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_string_utils.py +0 -0
  212. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_utils.py +0 -0
  213. {cmd2-4.0.0b4 → cmd2-4.0.0b6}/tests/test_utils_defining_class.py +0 -0
@@ -81,7 +81,9 @@ prompt is displayed.
81
81
  - `cmd2` no longer sets a default title for a subparsers group. If you desire a title, you will
82
82
  need to pass one in like this `parser.add_subparsers(title="subcommands")`. This is standard
83
83
  `argparse` behavior.
84
- - `TextGroup` is now a standalone Rich renderable.
84
+ - Added `HelpFormatterRenderable` protocol and `HelpContent` type alias to support context-aware
85
+ help content in `argparse`.
86
+ - `TextGroup` now implements `HelpFormatterRenderable`.
85
87
  - Removed `formatter_creator` parameter from `TextGroup.__init__()`.
86
88
  - Removed `Cmd2ArgumentParser.create_text_group()` method.
87
89
  - `argparse` and `Rich` integration refactoring:
@@ -96,6 +98,17 @@ prompt is displayed.
96
98
  - Replaced the global `APP_THEME` constant in `rich_utils.py` with `get_theme()` and
97
99
  `set_theme()` functions to support lazy initialization and safer in-place updates of the
98
100
  theme.
101
+ - Renamed `Cmd._command_parsers` to `Cmd.command_parsers`.
102
+ - Removed `RichPrintKwargs` `TypedDict` in favor of using `Mapping[str, Any]`, allowing for
103
+ greater flexibility in passing keyword arguments to `console.print()` calls.
104
+ - Removed `always_show_hint` settable as it provided a poor user experience with
105
+ `prompt-toolkit`
106
+ - `cmd2` redirection only captures output directed to `self.stdout` (e.g., via
107
+ `self.poutput()`). Standard `print()` calls write directly to `sys.stdout` and are not
108
+ captured. However, `print()` calls within `pyscripts` and the interactive Python shell are
109
+ treated as command output and sent to `self.stdout`, allowing them to be captured.
110
+ - Verbose help table descriptions are no longer generated from help function output. The system
111
+ now relies exclusively on command function docstrings.
99
112
  - Enhancements
100
113
  - New `cmd2.Cmd` parameters
101
114
  - **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These
@@ -113,8 +126,9 @@ prompt is displayed.
113
126
  - **read_secret**: read secrets like passwords without displaying them to the terminal
114
127
  - **ppretty**: a cmd2-compatible replacement for `rich.pretty.pprint()`
115
128
  - New settables:
116
- - **max_column_completion_results**: (int) the maximum number of completion results to
117
- display in a single column
129
+ - **max_column_completion_results**: (int) Maximum number of completion results to display
130
+ in a single column
131
+ - **traceback_show_locals**: (bool) Display local variables in tracebacks
118
132
  - `cmd2.Cmd.select` has been revamped to use the
119
133
  [choice](https://python-prompt-toolkit.readthedocs.io/en/3.0.52/pages/asking_for_a_choice.html)
120
134
  function from `prompt-toolkit` when both **stdin** and **stdout** are TTYs
@@ -130,6 +144,18 @@ prompt is displayed.
130
144
  specific `cmd2.Cmd` subclass (e.g.,`class MyCommandSet(CommandSet[MyApp]):`). This provides
131
145
  full type hints and IDE autocompletion for `self._cmd` without needing to override and cast
132
146
  the property.
147
+ - Added `traceback_kwargs` attribute to allow customization of Rich-based tracebacks.
148
+ - The `print()` function available in a `pyscript` writes to `self.stdout` and respects the
149
+ `allow_style` setting. It also supports printing `Rich` objects.
150
+ - Added `Cmd2ArgumentParser.output_to()` context manager to temporarily set the output stream
151
+ during `argparse` operations. This is helpful for directing output for functions like
152
+ `parse_args()`, which default to `sys.stdout` and lack a `file` argument.
153
+
154
+ ## 3.5.1 (April 24, 2026)
155
+
156
+ - Bug Fixes
157
+ - Fixed `ArgparseCompleter.print_help()` not passing file stream to recursive call.
158
+ - Fixed issue where `constants.REDIRECTION_TOKENS` was being mutated.
133
159
 
134
160
  ## 3.5.0 (April 13, 2026)
135
161
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmd2
3
- Version: 4.0.0b4
3
+ Version: 4.0.0b6
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
@@ -491,13 +491,6 @@ class ArgparseCompleter:
491
491
 
492
492
  # If we have results, then return them
493
493
  if completions:
494
- if not completions.hint:
495
- # Add a hint even though there are results in case Cmd.always_show_hint is True.
496
- completions = dataclasses.replace(
497
- completions,
498
- hint=_build_hint(self._parser, flag_arg_state.action),
499
- )
500
-
501
494
  return completions
502
495
 
503
496
  # Otherwise, print a hint if the flag isn't finished or text isn't possibly the start of a flag
@@ -519,12 +512,6 @@ class ArgparseCompleter:
519
512
 
520
513
  # If we have results, then return them
521
514
  if completions:
522
- if not completions.hint:
523
- # Add a hint even though there are results in case Cmd.always_show_hint is True.
524
- completions = dataclasses.replace(
525
- completions,
526
- hint=_build_hint(self._parser, pos_arg_state.action),
527
- )
528
515
  return completions
529
516
 
530
517
  # Otherwise, print a hint if text isn't possibly the start of a flag
@@ -705,9 +692,9 @@ class ArgparseCompleter:
705
692
  if parser is not None:
706
693
  completer_type = self._cmd2_app._determine_ap_completer_type(parser)
707
694
  completer = completer_type(parser, self._cmd2_app)
708
- completer.print_help(tokens[1:])
695
+ completer.print_help(tokens[1:], file)
709
696
  return
710
- self._parser.print_help(file=file)
697
+ self._parser.print_help(file)
711
698
 
712
699
  def _choices_to_items(self, arg_state: _ArgumentState) -> list[CompletionItem]:
713
700
  """Convert choices from action to list of CompletionItems."""
@@ -225,27 +225,35 @@ available on the resulting ``Action`` object to access its underlying attribute:
225
225
  """
226
226
 
227
227
  import argparse
228
+ import contextlib
228
229
  import re
229
230
  import sys
231
+ import threading
230
232
  from argparse import ArgumentError
231
233
  from collections.abc import (
232
234
  Callable,
233
235
  Iterable,
236
+ Iterator,
234
237
  Sequence,
235
238
  )
239
+ from dataclasses import dataclass
236
240
  from typing import (
241
+ IO,
237
242
  TYPE_CHECKING,
238
243
  Any,
244
+ ClassVar,
239
245
  NoReturn,
240
246
  cast,
241
247
  )
242
248
 
243
- from rich.console import RenderableType
244
249
  from rich.table import Column
245
250
 
246
251
  from . import constants
247
252
  from .completion import CompletionItem
248
- from .rich_utils import Cmd2HelpFormatter
253
+ from .rich_utils import (
254
+ Cmd2HelpFormatter,
255
+ HelpContent,
256
+ )
249
257
  from .styles import Cmd2Style
250
258
  from .types import (
251
259
  CmdOrSetT,
@@ -494,16 +502,93 @@ def _ActionsContainer_add_argument( # noqa: N802
494
502
  # Overwrite _ActionsContainer.add_argument with our patch
495
503
  argparse._ActionsContainer.add_argument = _ActionsContainer_add_argument # type: ignore[method-assign]
496
504
 
505
+ ############################################################################################################
506
+ # Patch argparse._SubParsersAction by adding remove_parser() function
507
+ ############################################################################################################
508
+
509
+
510
+ def _SubParsersAction_remove_parser( # noqa: N802
511
+ self: argparse._SubParsersAction, # type: ignore[type-arg]
512
+ name: str,
513
+ ) -> argparse.ArgumentParser:
514
+ """Remove a subparser from a subparsers group.
515
+
516
+ This function is added by cmd2 as a method called ``remove_parser()``
517
+ to ``argparse._SubParsersAction`` class.
518
+
519
+ To call: ``action.remove_parser(name)``
520
+
521
+ :param self: instance of the _SubParsersAction being edited
522
+ :param name: name of the subcommand for the subparser to remove
523
+ :return: the removed parser
524
+ :raises ValueError: if the subcommand doesn't exist
525
+ """
526
+ if name not in self._name_parser_map:
527
+ raise ValueError(f"Subcommand '{name}' does not exist")
528
+
529
+ subparser = self._name_parser_map[name]
530
+
531
+ # Find all names (primary and aliases) that map to this subparser
532
+ all_names = [cur_name for cur_name, cur_parser in self._name_parser_map.items() if cur_parser is subparser]
533
+
534
+ # Remove the help entry for this subparser. To handle the case where
535
+ # name is an alias, we remove the action whose 'dest' matches any of
536
+ # the names mapped to this subparser.
537
+ for choice_action in self._choices_actions:
538
+ if choice_action.dest in all_names:
539
+ self._choices_actions.remove(choice_action)
540
+ break
541
+
542
+ # Remove all references to this subparser, including aliases.
543
+ for cur_name in all_names:
544
+ del self._name_parser_map[cur_name]
545
+
546
+ return cast(argparse.ArgumentParser, subparser)
547
+
548
+
549
+ argparse._SubParsersAction.remove_parser = _SubParsersAction_remove_parser # type: ignore[attr-defined]
550
+
551
+
552
+ @dataclass
553
+ class _ParserThreadLocals(threading.local):
554
+ """Thread-local storage used by Cmd2ArgumentParser to manage execution context."""
555
+
556
+ # The active output stream for help, usage, and errors. Since argparse does not
557
+ # pass the destination stream to the formatter factory, this transient value
558
+ # provides the context needed to synchronize Rich's rendering with the specific
559
+ # capabilities of the destination file descriptor. It is managed via the
560
+ # output_to() context manager.
561
+ current_output_file: IO[str] | None = None
562
+
497
563
 
498
564
  class Cmd2ArgumentParser(argparse.ArgumentParser):
499
565
  """Custom ArgumentParser class that improves error and help output."""
500
566
 
567
+ # Thread-local storage shared by all parser instances (including subparsers)
568
+ _thread_locals: ClassVar[_ParserThreadLocals] = _ParserThreadLocals()
569
+
570
+ @contextlib.contextmanager
571
+ def output_to(self, file: IO[str] | None) -> Iterator[None]:
572
+ """Context manager to temporarily set the output stream during argparse operations.
573
+
574
+ This is helpful for directing output for functions like `parse_args()`, which
575
+ default to `sys.stdout` and lack a `file` argument.
576
+
577
+ :param file: the file stream to use for output
578
+ """
579
+ previous = self._thread_locals.current_output_file
580
+ self._thread_locals.current_output_file = file
581
+ try:
582
+ yield
583
+ finally:
584
+ self._thread_locals.current_output_file = previous
585
+
501
586
  def __init__(
502
587
  self,
503
588
  prog: str | None = None,
504
589
  usage: str | None = None,
505
- description: RenderableType | None = None,
506
- epilog: RenderableType | None = None,
590
+ description: HelpContent | None = None,
591
+ epilog: HelpContent | None = None,
507
592
  parents: Sequence[argparse.ArgumentParser] = (),
508
593
  formatter_class: type[Cmd2HelpFormatter] = Cmd2HelpFormatter,
509
594
  prefix_chars: str = "-",
@@ -553,10 +638,26 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
553
638
 
554
639
  # To assist type checkers, recast these to reflect our usage of rich-argparse.
555
640
  self.formatter_class: type[Cmd2HelpFormatter]
556
- self.description: RenderableType | None # type: ignore[assignment]
557
- self.epilog: RenderableType | None # type: ignore[assignment]
641
+ self.description: HelpContent | None # type: ignore[assignment]
642
+ self.epilog: HelpContent | None # type: ignore[assignment]
643
+
644
+ def print_usage(self, file: IO[str] | None = None) -> None: # type:ignore[override]
645
+ """Override to ensure the formatter is aware of the target file."""
646
+ if file is None:
647
+ file = self._thread_locals.current_output_file
558
648
 
559
- def _get_subparsers_action(self) -> "argparse._SubParsersAction[Cmd2ArgumentParser]":
649
+ with self.output_to(file):
650
+ super().print_usage(file)
651
+
652
+ def print_help(self, file: IO[str] | None = None) -> None: # type:ignore[override]
653
+ """Override to ensure the formatter is aware of the target file."""
654
+ if file is None:
655
+ file = self._thread_locals.current_output_file
656
+
657
+ with self.output_to(file):
658
+ super().print_help(file)
659
+
660
+ def get_subparsers_action(self) -> "argparse._SubParsersAction[Cmd2ArgumentParser]":
560
661
  """Get the _SubParsersAction for this parser if it exists.
561
662
 
562
663
  :return: the _SubParsersAction for this parser
@@ -619,7 +720,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
619
720
  self.prog = prog
620
721
 
621
722
  try:
622
- subparsers_action = self._get_subparsers_action()
723
+ subparsers_action = self.get_subparsers_action()
623
724
  except ValueError:
624
725
  # This parser has no subcommands
625
726
  return
@@ -638,12 +739,14 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
638
739
  # add_parser() will have the correct prog value.
639
740
  subparsers_action._prog_prefix = self._build_subparsers_prog_prefix(positionals)
640
741
 
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'.
742
+ # subparsers_action._name_parser_map includes aliases. Since primary names are inserted
743
+ # first, we skip already updated parsers to ensure primary names are used in 'prog'.
744
+ # We can't rely on subparsers_action._choices_actions to filter out aliases because while
745
+ # it contains only primary names, it omits any subcommands that lack help text.
643
746
  updated_parsers: set[Cmd2ArgumentParser] = set()
644
747
 
645
748
  # Set the prog value for each subcommand's parser
646
- for subcmd_name, subcmd_parser in subparsers_action.choices.items():
749
+ for subcmd_name, subcmd_parser in subparsers_action._name_parser_map.items():
647
750
  if subcmd_parser in updated_parsers:
648
751
  continue
649
752
 
@@ -651,7 +754,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
651
754
  subcmd_parser.update_prog(subcmd_prog)
652
755
  updated_parsers.add(subcmd_parser)
653
756
 
654
- def _find_parser(self, subcommand_path: Iterable[str]) -> "Cmd2ArgumentParser":
757
+ def find_parser(self, subcommand_path: Iterable[str]) -> "Cmd2ArgumentParser":
655
758
  """Find a parser in the hierarchy based on a sequence of subcommand names.
656
759
 
657
760
  :param subcommand_path: sequence of subcommand names leading to the target parser
@@ -660,10 +763,10 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
660
763
  """
661
764
  parser = self
662
765
  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]
766
+ subparsers_action = parser.get_subparsers_action()
767
+ if name not in subparsers_action._name_parser_map:
768
+ raise ValueError(f"Subcommand '{name}' does not exist for '{parser.prog}'")
769
+ parser = subparsers_action._name_parser_map[name]
667
770
  return parser
668
771
 
669
772
  def attach_subcommand(
@@ -683,7 +786,8 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
683
786
  :raises TypeError: if subcommand_parser is not an instance of the following or their subclasses:
684
787
  1. Cmd2ArgumentParser
685
788
  2. The parser_class configured for the target subcommand group
686
- :raises ValueError: if the command path is invalid or doesn't support subcommands
789
+ :raises ValueError: if the command path is invalid, doesn't support subcommands, or the
790
+ subcommand already exists
687
791
  """
688
792
  if not isinstance(subcommand_parser, Cmd2ArgumentParser):
689
793
  raise TypeError(
@@ -691,8 +795,8 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
691
795
  f"Received: '{type(subcommand_parser).__name__}'."
692
796
  )
693
797
 
694
- target_parser = self._find_parser(subcommand_path)
695
- subparsers_action = target_parser._get_subparsers_action()
798
+ target_parser = self.find_parser(subcommand_path)
799
+ subparsers_action = target_parser.get_subparsers_action()
696
800
 
697
801
  # Verify the parser is compatible with the 'parser_class' configured for this
698
802
  # subcommand group. We use isinstance() here to allow for subclasses, providing
@@ -705,6 +809,12 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
705
809
  f"Received: '{type(subcommand_parser).__name__}'."
706
810
  )
707
811
 
812
+ # Do not overwrite existing subcommands or aliases
813
+ all_names = (subcommand, *add_parser_kwargs.get("aliases", ()))
814
+ for name in all_names:
815
+ if name in subparsers_action._name_parser_map:
816
+ raise ValueError(f"Subcommand '{name}' already exists for '{target_parser.prog}'")
817
+
708
818
  # Use add_parser to register the subcommand name and any aliases
709
819
  placeholder_parser = subparsers_action.add_parser(subcommand, **add_parser_kwargs)
710
820
 
@@ -728,28 +838,16 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
728
838
  :return: the detached parser
729
839
  :raises ValueError: if the command path is invalid or the subcommand doesn't exist
730
840
  """
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
841
+ target_parser = self.find_parser(subcommand_path)
842
+ subparsers_action = target_parser.get_subparsers_action()
751
843
 
752
- return subparser
844
+ try:
845
+ return cast(
846
+ Cmd2ArgumentParser,
847
+ subparsers_action.remove_parser(subcommand), # type: ignore[attr-defined]
848
+ )
849
+ except ValueError:
850
+ raise ValueError(f"Subcommand '{subcommand}' does not exist for '{target_parser.prog}'") from None
753
851
 
754
852
  def error(self, message: str) -> NoReturn:
755
853
  """Override that applies custom formatting to the error message."""
@@ -761,19 +859,21 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
761
859
  else:
762
860
  formatted_message += "\n " + line
763
861
 
764
- self.print_usage(sys.stderr)
862
+ with self.output_to(sys.stderr):
863
+ self.print_usage(sys.stderr)
765
864
 
766
- # Use console to add style since it will respect ALLOW_STYLE's value
767
- console = self._get_formatter().console
768
- with console.capture() as capture:
769
- console.print(formatted_message, style=Cmd2Style.ERROR)
770
- formatted_message = f"{capture.get()}"
865
+ # Use console to add style since it will respect ALLOW_STYLE's value.
866
+ # Now _get_formatter() will return a formatter bound to stderr.
867
+ console = self._get_formatter().console
868
+ with console.capture() as capture:
869
+ console.print(formatted_message, style=Cmd2Style.ERROR)
870
+ formatted_message = f"{capture.get()}"
771
871
 
772
872
  self.exit(2, f"{formatted_message}\n")
773
873
 
774
- def _get_formatter(self, **kwargs: Any) -> Cmd2HelpFormatter:
874
+ def _get_formatter(self, **_kwargs: Any) -> Cmd2HelpFormatter:
775
875
  """Override with customizations for Cmd2HelpFormatter."""
776
- return cast(Cmd2HelpFormatter, super()._get_formatter(**kwargs))
876
+ return self.formatter_class(prog=self.prog, file=self._thread_locals.current_output_file)
777
877
 
778
878
  def format_help(self) -> str:
779
879
  """Override to add a newline."""