cmd2 4.0.0b1__tar.gz → 4.0.0b2__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.0b1 → cmd2-4.0.0b2}/.pre-commit-config.yaml +16 -1
  2. cmd2-4.0.0b2/.typos.toml +5 -0
  3. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/CHANGELOG.md +14 -4
  4. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/Makefile +4 -4
  5. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/PKG-INFO +2 -2
  6. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/README.md +1 -1
  7. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/__init__.py +1 -1
  8. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/argparse_completer.py +57 -36
  9. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/argparse_custom.py +67 -52
  10. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/cmd2.py +206 -79
  11. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/completion.py +14 -12
  12. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/decorators.py +1 -1
  13. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/pt_utils.py +9 -6
  14. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/rich_utils.py +103 -101
  15. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/string_utils.py +23 -0
  16. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2.egg-info/PKG-INFO +2 -2
  17. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2.egg-info/SOURCES.txt +2 -2
  18. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/doc_conventions.md +1 -1
  19. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/generating_output.md +1 -1
  20. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/misc.md +4 -0
  21. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/README.md +4 -4
  22. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/argparse_completion.py +4 -4
  23. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/command_sets.py +3 -3
  24. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/custom_types.py +1 -1
  25. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/read_input.py +28 -1
  26. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/pyproject.toml +2 -2
  27. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/ruff.toml +1 -1
  28. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_argparse_completer.py +219 -85
  29. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_argparse_custom.py +59 -12
  30. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_cmd2.py +282 -12
  31. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_commandset.py +3 -3
  32. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_completion.py +4 -4
  33. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_pt_utils.py +17 -10
  34. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_rich_utils.py +37 -38
  35. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_string_utils.py +26 -0
  36. cmd2-4.0.0b1/examples/cmd2_history.dat +0 -0
  37. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/.prettierignore +0 -0
  38. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/.prettierrc +0 -0
  39. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/GEMINI.md +0 -0
  40. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/LICENSE +0 -0
  41. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/MANIFEST.in +0 -0
  42. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/clipboard.py +0 -0
  43. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/colors.py +0 -0
  44. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/command_definition.py +0 -0
  45. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/constants.py +0 -0
  46. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/exceptions.py +0 -0
  47. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/history.py +0 -0
  48. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/parsing.py +0 -0
  49. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/plugin.py +0 -0
  50. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/py.typed +0 -0
  51. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/py_bridge.py +0 -0
  52. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/styles.py +0 -0
  53. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/types.py +0 -0
  54. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2/utils.py +0 -0
  55. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2.egg-info/dependency_links.txt +0 -0
  56. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2.egg-info/requires.txt +0 -0
  57. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2.egg-info/top_level.txt +0 -0
  58. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/cmd2.png +0 -0
  59. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/codecov.yml +0 -0
  60. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/argparse_completer.md +0 -0
  61. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/argparse_custom.md +0 -0
  62. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/clipboard.md +0 -0
  63. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/cmd.md +0 -0
  64. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/colors.md +0 -0
  65. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/command_definition.md +0 -0
  66. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/completion.md +0 -0
  67. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/constants.md +0 -0
  68. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/decorators.md +0 -0
  69. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/exceptions.md +0 -0
  70. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/history.md +0 -0
  71. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/index.md +0 -0
  72. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/parsing.md +0 -0
  73. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/plugin.md +0 -0
  74. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/pt_utils.md +0 -0
  75. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/py_bridge.md +0 -0
  76. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/rich_utils.md +0 -0
  77. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/string_utils.md +0 -0
  78. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/styles.md +0 -0
  79. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/api/utils.md +0 -0
  80. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/examples/alternate_event_loops.md +0 -0
  81. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/examples/examples.md +0 -0
  82. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/examples/getting_started.md +0 -0
  83. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/examples/index.md +0 -0
  84. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/argument_processing.md +0 -0
  85. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/async_commands.md +0 -0
  86. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/builtin_commands.md +0 -0
  87. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/clipboard.md +0 -0
  88. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/commands.md +0 -0
  89. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/completion.md +0 -0
  90. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/disable_commands.md +0 -0
  91. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/embedded_python_shells.md +0 -0
  92. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/help.md +0 -0
  93. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/history.md +0 -0
  94. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/hooks.md +0 -0
  95. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/index.md +0 -0
  96. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/initialization.md +0 -0
  97. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/modular_commands.md +0 -0
  98. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/multiline_commands.md +0 -0
  99. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/os.md +0 -0
  100. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/packaging.md +0 -0
  101. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/plugins.md +0 -0
  102. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/prompt.md +0 -0
  103. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/redirection.md +0 -0
  104. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/scripting.md +0 -0
  105. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/settings.md +0 -0
  106. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/shortcuts_aliases_macros.md +0 -0
  107. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/startup_commands.md +0 -0
  108. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/table_creation.md +0 -0
  109. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/features/theme.md +0 -0
  110. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/index.md +0 -0
  111. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/javascripts/readthedocs.js +0 -0
  112. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/migrating/incompatibilities.md +0 -0
  113. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/migrating/index.md +0 -0
  114. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/migrating/minimum.md +0 -0
  115. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/migrating/next_steps.md +0 -0
  116. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/migrating/why.md +0 -0
  117. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/mixins/index.md +0 -0
  118. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/mixins/mixin_template.md +0 -0
  119. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/overrides/main.html +0 -0
  120. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/overview/alternatives.md +0 -0
  121. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/overview/index.md +0 -0
  122. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/overview/installation.md +0 -0
  123. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/overview/integrating.md +0 -0
  124. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/overview/resources.md +0 -0
  125. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/stylesheets/cmd2.css +0 -0
  126. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/testing.md +0 -0
  127. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/docs/upgrades.md +0 -0
  128. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/.cmd2rc +0 -0
  129. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/argparse_example.py +0 -0
  130. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/async_call.py +0 -0
  131. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/async_commands.py +0 -0
  132. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/async_printing.py +0 -0
  133. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/basic_completion.py +0 -0
  134. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/cmd_as_argument.py +0 -0
  135. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/color.py +0 -0
  136. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/custom_parser.py +0 -0
  137. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/default_categories.py +0 -0
  138. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/dynamic_commands.py +0 -0
  139. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/environment.py +0 -0
  140. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/event_loops.py +0 -0
  141. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/exit_code.py +0 -0
  142. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/getting_started.py +0 -0
  143. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/hello_cmd2.py +0 -0
  144. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/help_categories.py +0 -0
  145. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/hooks.py +0 -0
  146. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/migrating.py +0 -0
  147. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/mixin.py +0 -0
  148. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/modular_commands/__init__.py +0 -0
  149. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/modular_commands/commandset_basic.py +0 -0
  150. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/modular_commands/commandset_complex.py +0 -0
  151. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/modular_commands/commandset_custominit.py +0 -0
  152. /cmd2-4.0.0b1/examples/modular_commands.py → /cmd2-4.0.0b2/examples/modular_commandsets.py +0 -0
  153. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/paged_output.py +0 -0
  154. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/persistent_history.py +0 -0
  155. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/pretty_print.py +0 -0
  156. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/python_scripting.py +0 -0
  157. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/remove_builtin_commands.py +0 -0
  158. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/remove_settable.py +0 -0
  159. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/rich_tables.py +0 -0
  160. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/rich_theme.py +0 -0
  161. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/scripts/arg_printer.py +0 -0
  162. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/scripts/conditional.py +0 -0
  163. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/scripts/nested.txt +0 -0
  164. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/scripts/quit.txt +0 -0
  165. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/scripts/save_help_text.py +0 -0
  166. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/scripts/script.py +0 -0
  167. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/scripts/script.txt +0 -0
  168. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/tmux_launch.sh +0 -0
  169. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/tmux_split.sh +0 -0
  170. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/examples/unicode_commands.py +0 -0
  171. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/mkdocs.yml +0 -0
  172. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/package.json +0 -0
  173. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/scripts/validate_tag.py +0 -0
  174. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/setup.cfg +0 -0
  175. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/.cmd2rc +0 -0
  176. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/__init__.py +0 -0
  177. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/conftest.py +0 -0
  178. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/pyscript/echo.py +0 -0
  179. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/pyscript/environment.py +0 -0
  180. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/pyscript/help.py +0 -0
  181. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/pyscript/py_locals.py +0 -0
  182. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/pyscript/pyscript_dir.py +0 -0
  183. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/pyscript/raises_exception.py +0 -0
  184. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/pyscript/recursive.py +0 -0
  185. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/pyscript/self_in_py.py +0 -0
  186. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/pyscript/stdout_capture.py +0 -0
  187. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/pyscript/stop.py +0 -0
  188. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/relative_multiple.txt +0 -0
  189. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/script.py +0 -0
  190. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/script.txt +0 -0
  191. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/scripts/binary.bin +0 -0
  192. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/scripts/empty.txt +0 -0
  193. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/scripts/help.txt +0 -0
  194. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/scripts/nested.txt +0 -0
  195. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/scripts/one_down.txt +0 -0
  196. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/scripts/postcmds.txt +0 -0
  197. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/scripts/precmds.txt +0 -0
  198. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/scripts/utf8.txt +0 -0
  199. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_argparse.py +0 -0
  200. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_argparse_subcommands.py +0 -0
  201. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_categories.py +0 -0
  202. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_dynamic_complete_style.py +0 -0
  203. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_future_annotations.py +0 -0
  204. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_history.py +0 -0
  205. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_parsing.py +0 -0
  206. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_plugin.py +0 -0
  207. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_py_completion.py +0 -0
  208. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_run_pyscript.py +0 -0
  209. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_utils.py +0 -0
  210. {cmd2-4.0.0b1 → cmd2-4.0.0b2}/tests/test_utils_defining_class.py +0 -0
@@ -3,13 +3,18 @@ repos:
3
3
  rev: "v6.0.0"
4
4
  hooks:
5
5
  - id: check-case-conflict
6
+ - id: check-executables-have-shebangs
6
7
  - id: check-merge-conflict
7
8
  - id: check-toml
9
+ - id: check-yaml
10
+ - id: detect-private-key
8
11
  - id: end-of-file-fixer
12
+ - id: fix-byte-order-marker
13
+ - id: mixed-line-ending
9
14
  - id: trailing-whitespace
10
15
 
11
16
  - repo: https://github.com/astral-sh/ruff-pre-commit
12
- rev: "v0.15.4"
17
+ rev: "v0.15.6"
13
18
  hooks:
14
19
  - id: ruff-format
15
20
  args: [--config=ruff.toml]
@@ -23,3 +28,13 @@ repos:
23
28
  additional_dependencies:
24
29
  - prettier@3.8.1
25
30
  - prettier-plugin-toml@2.0.6
31
+
32
+ - repo: https://github.com/crate-ci/typos
33
+ rev: v1.44.0
34
+ hooks:
35
+ - id: typos
36
+ exclude: |
37
+ (?x)^(
38
+ ruff.toml|
39
+ tests/.*
40
+ )$
@@ -0,0 +1,5 @@
1
+ [default.extend-words]
2
+ EXPLIoT = "EXPLIoT"
3
+ Counterfit = "Counterfit"
4
+ expliot = "expliot"
5
+ counterfit = "counterfit"
@@ -37,13 +37,15 @@ prompt is displayed.
37
37
  longer needed
38
38
  - `completer` functions must now return a `cmd2.Completions` object instead of `list[str]`.
39
39
  - `choices_provider` functions must now return a `cmd2.Choices` object instead of `list[str]`.
40
- - An argparse argument's `descriptive_headers` field is now called `table_header`.
41
- - `CompletionItem.descriptive_data` is now called `CompletionItem.table_row`.
40
+ - An argparse argument's `descriptive_headers` field is now called `table_columns`.
41
+ - `CompletionItem.descriptive_data` is now called `CompletionItem.table_data`.
42
+ - Removed `DEFAULT_DESCRIPTIVE_HEADERS`. This means you must define `table_columns` when using
43
+ `CompletionItem.table_data` data.
42
44
  - `Cmd.default_sort_key` moved to `utils.DEFAULT_STR_SORT_KEY`.
43
45
  - Moved completion state data, which previously resided in `Cmd`, into other classes.
44
46
  - `Cmd.matches_sorted` -> `Completions.is_sorted` and `Choices.is_sorted`
45
- - `Cmd.completion_hint` -> `Completions.completion_hint`
46
- - `Cmd.formatted_completions` -> `Completions.completion_table`
47
+ - `Cmd.completion_hint` -> `Completions.hint`
48
+ - `Cmd.formatted_completions` -> `Completions.table` (Now a Rich Table)
47
49
  - `Cmd.allow_appended_space/allow_closing_quote` -> `Completions.allow_finalization`
48
50
  - Removed `Cmd.matches_delimited` since it's no longer used.
49
51
  - Removed `flag_based_complete` and `index_based_complete` functions since their functionality
@@ -61,6 +63,7 @@ prompt is displayed.
61
63
  `add_alert()`. This new function is thread-safe and does not require you to acquire a mutex
62
64
  before calling it like the previous functions did.
63
65
  - Removed `Cmd.default_to_shell`.
66
+ - Removed `Cmd.ruler` since `cmd2` no longer uses it.
64
67
  - Enhancements
65
68
  - New `cmd2.Cmd` parameters
66
69
  - **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These
@@ -75,9 +78,16 @@ prompt is displayed.
75
78
  - **get_rprompt**: override to populate right prompt
76
79
  - **pre_prompt**: hook method that is called before the prompt is displayed, but after
77
80
  `prompt-toolkit` event loop has started
81
+ - **read_secret**: read secrets like passwords without displaying them to the terminal
78
82
  - New settables:
79
83
  - **max_column_completion_results**: (int) the maximum number of completion results to
80
84
  display in a single column
85
+ - `cmd2.Cmd.select` has been revamped to use the
86
+ [choice](https://python-prompt-toolkit.readthedocs.io/en/3.0.52/pages/asking_for_a_choice.html)
87
+ function from `prompt-toolkit` when both **stdin** and **stdout** are TTYs
88
+ - Add support for Python 3.15 by fixing various bugs related to internal `argparse` changes
89
+ - Added `common_prefix` method to `cmd2.string_utils` module as a replacement for
90
+ `os.path.commonprefix` since that is now deprecated in Python 3.15
81
91
 
82
92
  ## 3.4.0 (March 3, 2026)
83
93
 
@@ -7,8 +7,8 @@ install: ## Install the virtual environment with dependencies
7
7
  @echo "🚀 Creating uv Python virtual environment"
8
8
  @uv python install 3.14
9
9
  @uv sync --python=3.14
10
- @echo "🚀 Installing Git pre-commit hooks locally"
11
- @uv run pre-commit install
10
+ @echo "🚀 Installing Git prek hooks locally"
11
+ @uv run prek install -f
12
12
  @echo "🚀 Installing Prettier using npm"
13
13
  @npm install -q --no-fund --include=dev
14
14
 
@@ -16,8 +16,8 @@ install: ## Install the virtual environment with dependencies
16
16
  check: ## Run code quality tools.
17
17
  @echo "🚀 Checking lock file consistency with 'pyproject.toml'"
18
18
  @uv lock --locked
19
- @echo "🚀 Linting code and documentation: Running pre-commit"
20
- @uv run pre-commit run -a
19
+ @echo "🚀 Auto-formatting/Linting code and documentation: Running prek"
20
+ @uv run prek run -a
21
21
  @echo "🚀 Static type checking: Running mypy"
22
22
  @uv run mypy
23
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmd2
3
- Version: 4.0.0b1
3
+ Version: 4.0.0b2
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
@@ -139,7 +139,7 @@ examples.
139
139
  - [cmd2 example applications](https://github.com/python-cmd2/cmd2/tree/main/examples)
140
140
  - Basic cmd2 examples to demonstrate how to use various features
141
141
  - [Advanced Examples](https://github.com/jayrod/cmd2-example-apps)
142
- - More complex examples that demonstrate more featuers about how to put together a complete
142
+ - More complex examples that demonstrate more features about how to put together a complete
143
143
  application
144
144
  - [Cookiecutter](https://github.com/cookiecutter/cookiecutter) Templates from community
145
145
  - Basic cookiecutter template for cmd2 application :
@@ -107,7 +107,7 @@ examples.
107
107
  - [cmd2 example applications](https://github.com/python-cmd2/cmd2/tree/main/examples)
108
108
  - Basic cmd2 examples to demonstrate how to use various features
109
109
  - [Advanced Examples](https://github.com/jayrod/cmd2-example-apps)
110
- - More complex examples that demonstrate more featuers about how to put together a complete
110
+ - More complex examples that demonstrate more features about how to put together a complete
111
111
  application
112
112
  - [Cookiecutter](https://github.com/cookiecutter/cookiecutter) Templates from community
113
113
  - Basic cookiecutter template for cmd2 application :
@@ -99,7 +99,7 @@ __all__: list[str] = [ # noqa: RUF022
99
99
  # String Utils
100
100
  'stylize',
101
101
  # Styles,
102
- "Cmd2Style",
102
+ 'Cmd2Style',
103
103
  # Utilities
104
104
  'categorize',
105
105
  'CustomCompletionSettings',
@@ -25,16 +25,12 @@ from typing import (
25
25
  from rich.text import Text
26
26
 
27
27
  from .constants import INFINITY
28
- from .rich_utils import Cmd2GeneralConsole
28
+ from .rich_utils import Cmd2SimpleTable
29
29
 
30
30
  if TYPE_CHECKING: # pragma: no cover
31
31
  from .cmd2 import Cmd
32
32
 
33
- from rich.box import SIMPLE_HEAD
34
- from rich.table import (
35
- Column,
36
- Table,
37
- )
33
+ from rich.table import Column
38
34
 
39
35
  from .argparse_custom import (
40
36
  ChoicesCallable,
@@ -47,10 +43,6 @@ from .completion import (
47
43
  all_display_numeric,
48
44
  )
49
45
  from .exceptions import CompletionError
50
- from .styles import Cmd2Style
51
-
52
- # If no table header is supplied, then this will be used instead
53
- DEFAULT_TABLE_HEADER: Sequence[str | Column] = ['Description']
54
46
 
55
47
  # Name of the choice/completer function argument that, if present, will be passed a dictionary of
56
48
  # command line tokens up through the token being completed mapped to their argparse destination name.
@@ -500,11 +492,11 @@ class ArgparseCompleter:
500
492
 
501
493
  # If we have results, then return them
502
494
  if completions:
503
- if not completions.completion_hint:
495
+ if not completions.hint:
504
496
  # Add a hint even though there are results in case Cmd.always_show_hint is True.
505
497
  completions = dataclasses.replace(
506
498
  completions,
507
- completion_hint=_build_hint(self._parser, flag_arg_state.action),
499
+ hint=_build_hint(self._parser, flag_arg_state.action),
508
500
  )
509
501
 
510
502
  return completions
@@ -528,11 +520,11 @@ class ArgparseCompleter:
528
520
 
529
521
  # If we have results, then return them
530
522
  if completions:
531
- if not completions.completion_hint:
523
+ if not completions.hint:
532
524
  # Add a hint even though there are results in case Cmd.always_show_hint is True.
533
525
  completions = dataclasses.replace(
534
526
  completions,
535
- completion_hint=_build_hint(self._parser, pos_arg_state.action),
527
+ hint=_build_hint(self._parser, pos_arg_state.action),
536
528
  )
537
529
  return completions
538
530
 
@@ -592,15 +584,52 @@ class ArgparseCompleter:
592
584
 
593
585
  return Completions(items)
594
586
 
595
- def _format_completions(self, arg_state: _ArgumentState, completions: Completions) -> Completions:
596
- """Format CompletionItems into completion table."""
597
- # Skip table generation for single results or if the list exceeds the
598
- # user-defined threshold for table display.
599
- if len(completions) < 2 or len(completions) > self._cmd2_app.max_completion_table_items:
600
- return completions
587
+ @staticmethod
588
+ def _validate_table_data(arg_state: _ArgumentState, completions: Completions) -> None:
589
+ """Verify the integrity of completion table data.
590
+
591
+ :raises ValueError: if there is an error with the data.
592
+ """
593
+ table_columns = arg_state.action.get_table_columns() # type: ignore[attr-defined]
594
+ has_table_data = any(item.table_data for item in completions)
595
+
596
+ if table_columns is None:
597
+ if has_table_data:
598
+ raise ValueError(
599
+ f"Argument '{arg_state.action.dest}' has CompletionItems with table_data, "
600
+ f"but no table_columns were defined in add_argument()."
601
+ )
602
+ return
603
+
604
+ # If columns are defined, then every item must have data, and lengths must match
605
+ for item in completions:
606
+ if not item.table_data:
607
+ raise ValueError(
608
+ f"Argument '{arg_state.action.dest}' has table_columns defined, "
609
+ f"but the CompletionItem for '{item.text}' is missing table_data."
610
+ )
611
+ if len(item.table_data) != len(table_columns):
612
+ raise ValueError(
613
+ f"Argument '{arg_state.action.dest}': table_data length ({len(item.table_data)}) "
614
+ f"does not match table_columns length ({len(table_columns)}) for item '{item.text}'."
615
+ )
616
+
617
+ def _build_completion_table(self, arg_state: _ArgumentState, completions: Completions) -> Completions:
618
+ """Build a rich.Table for completion results if applicable."""
619
+ # Verify integrity of completion data
620
+ self._validate_table_data(arg_state, completions)
601
621
 
602
- # Ensure every item provides table metadata to avoid an incomplete table.
603
- if not all(item.table_row for item in completions):
622
+ table_columns = cast(
623
+ Sequence[str | Column] | None,
624
+ arg_state.action.get_table_columns(), # type: ignore[attr-defined]
625
+ )
626
+
627
+ # Skip table generation if results are outside thresholds or no columns are defined
628
+ if (
629
+ len(completions) < 2
630
+ or len(completions) > self._cmd2_app.max_completion_table_items
631
+ or table_columns is None
632
+ ): # fmt: skip
604
633
  return completions
605
634
 
606
635
  # If a metavar was defined, use that instead of the dest field
@@ -620,26 +649,18 @@ class ArgparseCompleter:
620
649
  # Build header row
621
650
  rich_columns: list[Column] = []
622
651
  rich_columns.append(Column(destination.upper(), justify="right" if all_nums else "left", no_wrap=True))
623
- table_header = cast(Sequence[str | Column] | None, arg_state.action.get_table_header()) # type: ignore[attr-defined]
624
- if table_header is None:
625
- table_header = DEFAULT_TABLE_HEADER
626
652
  rich_columns.extend(
627
- column if isinstance(column, Column) else Column(column, overflow="fold") for column in table_header
653
+ column if isinstance(column, Column) else Column(column, overflow="fold") for column in table_columns
628
654
  )
629
655
 
630
- # Add the data rows
631
- hint_table = Table(*rich_columns, box=SIMPLE_HEAD, show_edge=False, border_style=Cmd2Style.TABLE_BORDER)
656
+ # Build the table
657
+ table = Cmd2SimpleTable(*rich_columns)
632
658
  for item in completions:
633
- hint_table.add_row(Text.from_ansi(item.display), *item.table_row)
634
-
635
- # Generate the table string
636
- console = Cmd2GeneralConsole(file=self._cmd2_app.stdout)
637
- with console.capture() as capture:
638
- console.print(hint_table, end="", soft_wrap=False)
659
+ table.add_row(Text.from_ansi(item.display), *item.table_data)
639
660
 
640
661
  return dataclasses.replace(
641
662
  completions,
642
- completion_table=capture.get(),
663
+ table=table,
643
664
  )
644
665
 
645
666
  def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: int, tokens: Sequence[str]) -> Completions:
@@ -780,7 +801,7 @@ class ArgparseCompleter:
780
801
  filtered = [choice for choice in all_choices if choice.text not in used_values]
781
802
  completions = self._cmd2_app.basic_complete(text, line, begidx, endidx, filtered)
782
803
 
783
- return self._format_completions(arg_state, completions)
804
+ return self._build_completion_table(arg_state, completions)
784
805
 
785
806
 
786
807
  # The default ArgparseCompleter class for a cmd2 app
@@ -127,7 +127,7 @@ completion results displayed to the screen.
127
127
 
128
128
  1. display - string for displaying the completion differently in the completion menu
129
129
  2. display_meta - meta information about completion which displays in the completion menu
130
- 3. table_row - row data for completion tables
130
+ 3. table_data - supplemental data for completion tables
131
131
 
132
132
  They can also be used as argparse choices. When a ``CompletionItem`` is created, it
133
133
  stores the original value (e.g. ID number) and makes it accessible through a property
@@ -139,8 +139,8 @@ is compared to ``CompletionItem.value`` instead of the ``CompletionItem`` instan
139
139
  These were added to help in cases where uninformative data is being completed.
140
140
  For instance, completing ID numbers isn't very helpful to a user without context.
141
141
 
142
- Providing ``table_row`` data in your ``CompletionItem`` signals ArgparseCompleter
143
- to output the completion results in a table with descriptive data instead of just a table
142
+ Providing ``table_data`` in your ``CompletionItem`` signals ArgparseCompleter
143
+ to output the completion results in a table with supplemental data instead of just a table
144
144
  of tokens::
145
145
 
146
146
  Instead of this:
@@ -155,22 +155,21 @@ of tokens::
155
155
 
156
156
 
157
157
  The left-most column is the actual value being completed and its header is
158
- that value's name. The right column header is defined using the
159
- ``table_header`` parameter of add_argument(), which is a list of header
160
- names that defaults to ["Description"]. The right column values come from the
161
- ``table_row`` argument to ``CompletionItem``. It's a ``Sequence`` with the
162
- same number of items as ``table_header``.
158
+ that value's name. Any additional column headers are defined using the
159
+ ``table_columns`` parameter of add_argument(), which is a list of header
160
+ names. The supplemental column values come from the
161
+ ``table_data`` argument to ``CompletionItem``. It's a ``Sequence`` with the
162
+ same number of items as ``table_columns``.
163
163
 
164
164
  Example::
165
165
 
166
- Add an argument and define its table_header.
166
+ Add an argument and define its table_columns.
167
167
 
168
168
  parser.add_argument(
169
- add_argument(
170
169
  "item_id",
171
170
  type=int,
172
171
  choices_provider=get_choices,
173
- table_header=["Item Name", "Checked Out", "Due Date"],
172
+ table_columns=["Item Name", "Checked Out", "Due Date"],
174
173
  )
175
174
 
176
175
  Implement the choices_provider to return Choices.
@@ -178,12 +177,12 @@ Example::
178
177
  def get_choices(self) -> Choices:
179
178
  \"\"\"choices_provider which returns CompletionItems\"\"\"
180
179
 
181
- # Populate CompletionItem's table_row argument.
182
- # Its item count should match that of table_header.
180
+ # Populate CompletionItem's table_data argument.
181
+ # Its item count should match that of table_columns.
183
182
  items = [
184
- CompletionItem(1, table_row=["My item", True, "02/02/2022"]),
185
- CompletionItem(2, table_row=["Another item", False, ""]),
186
- CompletionItem(3, table_row=["Yet another item", False, ""]),
183
+ CompletionItem(1, table_data=["My item", True, "02/02/2022"]),
184
+ CompletionItem(2, table_data=["Another item", False, ""]),
185
+ CompletionItem(3, table_data=["Yet another item", False, ""]),
187
186
  ]
188
187
  return Choices(items)
189
188
 
@@ -195,7 +194,7 @@ Example::
195
194
  2 Another item False
196
195
  3 Yet another item False
197
196
 
198
- ``table_header`` can be strings or ``Rich.table.Columns`` for more
197
+ ``table_columns`` can be strings or ``Rich.table.Columns`` for more
199
198
  control over things like alignment.
200
199
 
201
200
  - If a header is a string, it will render as a left-aligned column with its
@@ -207,9 +206,9 @@ This means a long string which exceeds the width of its column will be
207
206
  truncated with an ellipsis at the end. You can override this and other settings
208
207
  when you create the ``Column``.
209
208
 
210
- ``table_row`` items can include Rich objects, including styled Text and Tables.
209
+ ``table_data`` items can include Rich objects, including styled Text and Tables.
211
210
 
212
- To avoid printing a excessive information to the screen at once when a user
211
+ To avoid printing excessive information to the screen at once when a user
213
212
  presses tab, there is a maximum threshold for the number of ``CompletionItems``
214
213
  that will be shown. Its value is defined in ``cmd2.Cmd.max_completion_table_items``.
215
214
  It defaults to 50, but can be changed. If the number of completion suggestions
@@ -240,8 +239,8 @@ for cases in which you need to manually access the cmd2-specific attributes.
240
239
  - ``argparse.Action.get_choices_callable()`` - See `action_get_choices_callable` for more details.
241
240
  - ``argparse.Action.set_choices_provider()`` - See `_action_set_choices_provider` for more details.
242
241
  - ``argparse.Action.set_completer()`` - See `_action_set_completer` for more details.
243
- - ``argparse.Action.get_table_header()`` - See `_action_get_table_header` for more details.
244
- - ``argparse.Action.set_table_header()`` - See `_action_set_table_header` for more details.
242
+ - ``argparse.Action.get_table_columns()`` - See `_action_get_table_columns` for more details.
243
+ - ``argparse.Action.set_table_columns()`` - See `_action_set_table_columns` for more details.
245
244
  - ``argparse.Action.get_nargs_range()`` - See `_action_get_nargs_range` for more details.
246
245
  - ``argparse.Action.set_nargs_range()`` - See `_action_set_nargs_range` for more details.
247
246
  - ``argparse.Action.get_suppress_tab_hint()`` - See `_action_get_suppress_tab_hint` for more details.
@@ -309,21 +308,21 @@ if TYPE_CHECKING: # pragma: no cover
309
308
 
310
309
  def generate_range_error(range_min: int, range_max: float) -> str:
311
310
  """Generate an error message when the the number of arguments provided is not within the expected range."""
312
- err_str = "expected "
311
+ err_msg = "expected "
313
312
 
314
313
  if range_max == constants.INFINITY:
315
314
  plural = '' if range_min == 1 else 's'
316
- err_str += f"at least {range_min}"
315
+ err_msg += f"at least {range_min}"
317
316
  else:
318
317
  plural = '' if range_max == 1 else 's'
319
318
  if range_min == range_max:
320
- err_str += f"{range_min}"
319
+ err_msg += f"{range_min}"
321
320
  else:
322
- err_str += f"{range_min} to {range_max}"
321
+ err_msg += f"{range_min} to {range_max}"
323
322
 
324
- err_str += f" argument{plural}"
323
+ err_msg += f" argument{plural}"
325
324
 
326
- return err_str
325
+ return err_msg
327
326
 
328
327
 
329
328
  def set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None:
@@ -398,14 +397,14 @@ class ChoicesCallable:
398
397
 
399
398
  @property
400
399
  def choices_provider(self) -> ChoicesProviderUnbound[CmdOrSet]:
401
- """Retreive the internal choices_provider function."""
400
+ """Retrieve the internal choices_provider function."""
402
401
  if self.is_completer:
403
402
  raise AttributeError("This instance is configured as a completer, not a choices_provider")
404
403
  return cast(ChoicesProviderUnbound[CmdOrSet], self.to_call)
405
404
 
406
405
  @property
407
406
  def completer(self) -> CompleterUnbound[CmdOrSet]:
408
- """Retreive the internal completer function."""
407
+ """Retrieve the internal completer function."""
409
408
  if not self.is_completer:
410
409
  raise AttributeError("This instance is configured as a choices_provider, not a completer")
411
410
  return cast(CompleterUnbound[CmdOrSet], self.to_call)
@@ -418,8 +417,8 @@ class ChoicesCallable:
418
417
  # ChoicesCallable object that specifies the function to be called which provides choices to the argument
419
418
  ATTR_CHOICES_CALLABLE = 'choices_callable'
420
419
 
421
- # A completion table header
422
- ATTR_TABLE_HEADER = 'table_header'
420
+ # Completion table columns
421
+ ATTR_TABLE_COLUMNS = 'table_columns'
423
422
 
424
423
  # A tuple specifying nargs as a range (min, max)
425
424
  ATTR_NARGS_RANGE = 'nargs_range'
@@ -516,38 +515,38 @@ setattr(argparse.Action, 'set_completer', _action_set_completer)
516
515
 
517
516
 
518
517
  ############################################################################################################
519
- # Patch argparse.Action with accessors for table_header attribute
518
+ # Patch argparse.Action with accessors for table_columns attribute
520
519
  ############################################################################################################
521
- def _action_get_table_header(self: argparse.Action) -> Sequence[str | Column] | None:
522
- """Get the table_header attribute of an argparse Action.
520
+ def _action_get_table_columns(self: argparse.Action) -> Sequence[str | Column] | None:
521
+ """Get the table_columns attribute of an argparse Action.
523
522
 
524
- This function is added by cmd2 as a method called ``get_table_header()`` to ``argparse.Action`` class.
523
+ This function is added by cmd2 as a method called ``get_table_columns()`` to ``argparse.Action`` class.
525
524
 
526
- To call: ``action.get_table_header()``
525
+ To call: ``action.get_table_columns()``
527
526
 
528
527
  :param self: argparse Action being queried
529
- :return: The value of table_header or None if attribute does not exist
528
+ :return: The value of table_columns or None if attribute does not exist
530
529
  """
531
- return cast(Sequence[str | Column] | None, getattr(self, ATTR_TABLE_HEADER, None))
530
+ return cast(Sequence[str | Column] | None, getattr(self, ATTR_TABLE_COLUMNS, None))
532
531
 
533
532
 
534
- setattr(argparse.Action, 'get_table_header', _action_get_table_header)
533
+ setattr(argparse.Action, 'get_table_columns', _action_get_table_columns)
535
534
 
536
535
 
537
- def _action_set_table_header(self: argparse.Action, table_header: Sequence[str | Column] | None) -> None:
538
- """Set the table_header attribute of an argparse Action.
536
+ def _action_set_table_columns(self: argparse.Action, table_columns: Sequence[str | Column] | None) -> None:
537
+ """Set the table_columns attribute of an argparse Action.
539
538
 
540
- This function is added by cmd2 as a method called ``set_table_header()`` to ``argparse.Action`` class.
539
+ This function is added by cmd2 as a method called ``set_table_columns()`` to ``argparse.Action`` class.
541
540
 
542
- To call: ``action.set_table_header(table_header)``
541
+ To call: ``action.set_table_columns(table_columns)``
543
542
 
544
543
  :param self: argparse Action being updated
545
- :param table_header: value being assigned
544
+ :param table_columns: value being assigned
546
545
  """
547
- setattr(self, ATTR_TABLE_HEADER, table_header)
546
+ setattr(self, ATTR_TABLE_COLUMNS, table_columns)
548
547
 
549
548
 
550
- setattr(argparse.Action, 'set_table_header', _action_set_table_header)
549
+ setattr(argparse.Action, 'set_table_columns', _action_set_table_columns)
551
550
 
552
551
 
553
552
  ############################################################################################################
@@ -698,7 +697,7 @@ def _add_argument_wrapper(
698
697
  choices_provider: ChoicesProviderUnbound[CmdOrSet] | None = None,
699
698
  completer: CompleterUnbound[CmdOrSet] | None = None,
700
699
  suppress_tab_hint: bool = False,
701
- table_header: Sequence[str | Column] | None = None,
700
+ table_columns: Sequence[str | Column] | None = None,
702
701
  **kwargs: Any,
703
702
  ) -> argparse.Action:
704
703
  """Wrap ActionsContainer.add_argument() which supports more settings used by cmd2.
@@ -718,7 +717,7 @@ def _add_argument_wrapper(
718
717
  current argument's help text as a hint. Set this to True to suppress the hint. If this
719
718
  argument's help text is set to argparse.SUPPRESS, then tab hints will not display
720
719
  regardless of the value passed for suppress_tab_hint. Defaults to False.
721
- :param table_header: optional header for when displaying a completion table. Defaults to None.
720
+ :param table_columns: optional headers for when displaying a completion table. Defaults to None.
722
721
 
723
722
  # Args from original function
724
723
  :param kwargs: keyword-arguments recognized by argparse._ActionsContainer.add_argument
@@ -809,7 +808,7 @@ def _add_argument_wrapper(
809
808
  new_arg.set_completer(completer) # type: ignore[attr-defined]
810
809
 
811
810
  new_arg.set_suppress_tab_hint(suppress_tab_hint) # type: ignore[attr-defined]
812
- new_arg.set_table_header(table_header) # type: ignore[attr-defined]
811
+ new_arg.set_table_columns(table_columns) # type: ignore[attr-defined]
813
812
 
814
813
  for keyword, value in custom_attribs.items():
815
814
  attr_setter = getattr(new_arg, f'set_{keyword}', None)
@@ -1030,6 +1029,22 @@ class Cmd2HelpFormatter(RichHelpFormatter):
1030
1029
  """Set our console instance."""
1031
1030
  self._console = console
1032
1031
 
1032
+ def _set_color(self, color: bool, **kwargs: Any) -> None:
1033
+ """Set the color for the help output.
1034
+
1035
+ This override is needed because Python 3.15 added a 'file' keyword argument
1036
+ to _set_color() which some versions of RichHelpFormatter don't support.
1037
+ """
1038
+ # Argparse didn't add color support until 3.14
1039
+ if sys.version_info < (3, 14):
1040
+ return
1041
+
1042
+ try: # type: ignore[unreachable]
1043
+ super()._set_color(color, **kwargs)
1044
+ except TypeError:
1045
+ # Fallback for older versions of RichHelpFormatter that don't support keyword arguments
1046
+ super()._set_color(color)
1047
+
1033
1048
  def _build_nargs_range_str(self, nargs_range: tuple[int, int | float]) -> str:
1034
1049
  """Generate nargs range string for help text."""
1035
1050
  if nargs_range[1] == constants.INFINITY:
@@ -1134,7 +1149,7 @@ class TextGroup:
1134
1149
  self,
1135
1150
  title: str,
1136
1151
  text: RenderableType,
1137
- formatter_creator: Callable[[], Cmd2HelpFormatter],
1152
+ formatter_creator: Callable[..., Cmd2HelpFormatter],
1138
1153
  ) -> None:
1139
1154
  """TextGroup initializer.
1140
1155
 
@@ -1258,9 +1273,9 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
1258
1273
 
1259
1274
  self.exit(2, f'{formatted_message}\n')
1260
1275
 
1261
- def _get_formatter(self) -> Cmd2HelpFormatter:
1276
+ def _get_formatter(self, **kwargs: Any) -> Cmd2HelpFormatter:
1262
1277
  """Override with customizations for Cmd2HelpFormatter."""
1263
- return cast(Cmd2HelpFormatter, super()._get_formatter())
1278
+ return cast(Cmd2HelpFormatter, super()._get_formatter(**kwargs))
1264
1279
 
1265
1280
  def format_help(self) -> str:
1266
1281
  """Override to add a newline."""