cmd2 4.0.0b2__tar.gz → 4.0.0b4__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 (222) hide show
  1. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/.pre-commit-config.yaml +1 -1
  2. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/CHANGELOG.md +47 -0
  3. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/PKG-INFO +3 -3
  4. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/__init__.py +53 -41
  5. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/argparse_completer.py +102 -94
  6. cmd2-4.0.0b4/cmd2/argparse_utils.py +847 -0
  7. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/cmd2.py +572 -514
  8. cmd2-4.0.0b4/cmd2/command_set.py +137 -0
  9. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/completion.py +79 -37
  10. cmd2-4.0.0b4/cmd2/constants.py +100 -0
  11. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/decorators.py +148 -129
  12. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/history.py +19 -19
  13. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/parsing.py +43 -43
  14. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/pt_utils.py +13 -13
  15. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/py_bridge.py +4 -4
  16. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/rich_utils.py +329 -115
  17. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/string_utils.py +1 -1
  18. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/styles.py +7 -1
  19. cmd2-4.0.0b4/cmd2/types.py +113 -0
  20. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/utils.py +41 -40
  21. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2.egg-info/PKG-INFO +3 -3
  22. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2.egg-info/SOURCES.txt +5 -5
  23. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2.egg-info/requires.txt +2 -2
  24. cmd2-4.0.0b4/docs/api/argparse_utils.md +3 -0
  25. cmd2-4.0.0b4/docs/api/command_set.md +3 -0
  26. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/index.md +3 -3
  27. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/argument_processing.md +20 -24
  28. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/completion.md +3 -3
  29. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/help.md +56 -30
  30. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/initialization.md +9 -2
  31. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/modular_commands.md +98 -52
  32. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/migrating/next_steps.md +2 -2
  33. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/README.md +1 -2
  34. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/argparse_completion.py +13 -13
  35. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/argparse_example.py +38 -39
  36. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/async_call.py +5 -5
  37. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/async_commands.py +6 -6
  38. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/async_printing.py +15 -15
  39. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/basic_completion.py +7 -7
  40. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/cmd_as_argument.py +22 -22
  41. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/color.py +4 -4
  42. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/command_sets.py +38 -36
  43. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/custom_parser.py +7 -7
  44. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/custom_types.py +9 -9
  45. cmd2-4.0.0b4/examples/default_categories.py +68 -0
  46. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/dynamic_commands.py +3 -3
  47. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/environment.py +5 -5
  48. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/event_loops.py +1 -1
  49. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/exit_code.py +2 -2
  50. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/getting_started.py +14 -19
  51. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/hello_cmd2.py +2 -2
  52. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/help_categories.py +34 -34
  53. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/hooks.py +6 -6
  54. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/migrating.py +6 -6
  55. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/mixin.py +3 -3
  56. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/modular_commands/commandset_basic.py +15 -13
  57. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/modular_commands/commandset_complex.py +17 -15
  58. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/modular_commands/commandset_custominit.py +8 -5
  59. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/modular_commandsets.py +6 -6
  60. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/paged_output.py +4 -4
  61. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/persistent_history.py +3 -3
  62. cmd2-4.0.0b4/examples/pretty_print.py +28 -0
  63. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/python_scripting.py +14 -14
  64. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/read_input.py +10 -10
  65. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/remove_builtin_commands.py +2 -2
  66. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/remove_settable.py +2 -2
  67. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/rich_tables.py +13 -18
  68. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/scripts/conditional.py +10 -10
  69. cmd2-4.0.0b4/examples/scripts/save_help_text.py +107 -0
  70. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/unicode_commands.py +2 -2
  71. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/mkdocs.yml +2 -2
  72. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/pyproject.toml +2 -2
  73. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/ruff.toml +1 -4
  74. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/scripts/validate_tag.py +5 -5
  75. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/conftest.py +6 -20
  76. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/pyscript/echo.py +2 -2
  77. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/pyscript/environment.py +1 -1
  78. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/pyscript/help.py +1 -1
  79. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/pyscript/raises_exception.py +1 -1
  80. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/pyscript/recursive.py +1 -1
  81. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/pyscript/self_in_py.py +1 -1
  82. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/pyscript/stop.py +2 -2
  83. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_argparse.py +182 -152
  84. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_argparse_completer.py +385 -384
  85. cmd2-4.0.0b4/tests/test_argparse_subcommands.py +131 -0
  86. cmd2-4.0.0b4/tests/test_argparse_utils.py +567 -0
  87. cmd2-4.0.0b4/tests/test_categories.py +130 -0
  88. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_cmd2.py +865 -710
  89. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_commandset.py +288 -273
  90. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_completion.py +411 -255
  91. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_dynamic_complete_style.py +6 -6
  92. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_future_annotations.py +2 -2
  93. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_history.py +263 -263
  94. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_parsing.py +425 -425
  95. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_plugin.py +86 -86
  96. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_pt_utils.py +46 -46
  97. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_py_completion.py +5 -5
  98. cmd2-4.0.0b4/tests/test_rich_utils.py +363 -0
  99. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_run_pyscript.py +44 -44
  100. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_string_utils.py +29 -29
  101. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_utils.py +63 -63
  102. cmd2-4.0.0b2/cmd2/argparse_custom.py +0 -1325
  103. cmd2-4.0.0b2/cmd2/command_definition.py +0 -202
  104. cmd2-4.0.0b2/cmd2/constants.py +0 -53
  105. cmd2-4.0.0b2/cmd2/types.py +0 -61
  106. cmd2-4.0.0b2/docs/api/argparse_custom.md +0 -3
  107. cmd2-4.0.0b2/docs/api/command_definition.md +0 -3
  108. cmd2-4.0.0b2/examples/default_categories.py +0 -76
  109. cmd2-4.0.0b2/examples/pretty_print.py +0 -44
  110. cmd2-4.0.0b2/examples/scripts/save_help_text.py +0 -97
  111. cmd2-4.0.0b2/tests/test_argparse_custom.py +0 -402
  112. cmd2-4.0.0b2/tests/test_argparse_subcommands.py +0 -133
  113. cmd2-4.0.0b2/tests/test_categories.py +0 -109
  114. cmd2-4.0.0b2/tests/test_rich_utils.py +0 -193
  115. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/.prettierignore +0 -0
  116. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/.prettierrc +0 -0
  117. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/.typos.toml +0 -0
  118. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/GEMINI.md +0 -0
  119. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/LICENSE +0 -0
  120. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/MANIFEST.in +0 -0
  121. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/Makefile +0 -0
  122. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/README.md +0 -0
  123. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/clipboard.py +0 -0
  124. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/colors.py +0 -0
  125. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/exceptions.py +0 -0
  126. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/plugin.py +0 -0
  127. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2/py.typed +0 -0
  128. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2.egg-info/dependency_links.txt +0 -0
  129. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2.egg-info/top_level.txt +0 -0
  130. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/cmd2.png +0 -0
  131. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/codecov.yml +0 -0
  132. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/argparse_completer.md +0 -0
  133. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/clipboard.md +0 -0
  134. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/cmd.md +0 -0
  135. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/colors.md +0 -0
  136. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/completion.md +0 -0
  137. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/constants.md +0 -0
  138. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/decorators.md +0 -0
  139. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/exceptions.md +0 -0
  140. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/history.md +0 -0
  141. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/parsing.md +0 -0
  142. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/plugin.md +0 -0
  143. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/pt_utils.md +0 -0
  144. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/py_bridge.md +0 -0
  145. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/rich_utils.md +0 -0
  146. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/string_utils.md +0 -0
  147. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/styles.md +0 -0
  148. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/api/utils.md +0 -0
  149. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/doc_conventions.md +0 -0
  150. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/examples/alternate_event_loops.md +0 -0
  151. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/examples/examples.md +0 -0
  152. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/examples/getting_started.md +0 -0
  153. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/examples/index.md +0 -0
  154. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/async_commands.md +0 -0
  155. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/builtin_commands.md +0 -0
  156. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/clipboard.md +0 -0
  157. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/commands.md +0 -0
  158. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/disable_commands.md +0 -0
  159. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/embedded_python_shells.md +0 -0
  160. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/generating_output.md +0 -0
  161. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/history.md +0 -0
  162. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/hooks.md +0 -0
  163. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/index.md +0 -0
  164. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/misc.md +0 -0
  165. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/multiline_commands.md +0 -0
  166. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/os.md +0 -0
  167. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/packaging.md +0 -0
  168. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/plugins.md +0 -0
  169. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/prompt.md +0 -0
  170. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/redirection.md +0 -0
  171. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/scripting.md +0 -0
  172. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/settings.md +0 -0
  173. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/shortcuts_aliases_macros.md +0 -0
  174. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/startup_commands.md +0 -0
  175. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/table_creation.md +0 -0
  176. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/features/theme.md +0 -0
  177. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/index.md +0 -0
  178. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/javascripts/readthedocs.js +0 -0
  179. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/migrating/incompatibilities.md +0 -0
  180. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/migrating/index.md +0 -0
  181. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/migrating/minimum.md +0 -0
  182. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/migrating/why.md +0 -0
  183. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/mixins/index.md +0 -0
  184. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/mixins/mixin_template.md +0 -0
  185. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/overrides/main.html +0 -0
  186. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/overview/alternatives.md +0 -0
  187. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/overview/index.md +0 -0
  188. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/overview/installation.md +0 -0
  189. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/overview/integrating.md +0 -0
  190. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/overview/resources.md +0 -0
  191. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/stylesheets/cmd2.css +0 -0
  192. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/testing.md +0 -0
  193. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/docs/upgrades.md +0 -0
  194. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/.cmd2rc +0 -0
  195. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/modular_commands/__init__.py +0 -0
  196. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/rich_theme.py +0 -0
  197. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/scripts/arg_printer.py +0 -0
  198. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/scripts/nested.txt +0 -0
  199. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/scripts/quit.txt +0 -0
  200. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/scripts/script.py +0 -0
  201. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/scripts/script.txt +0 -0
  202. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/tmux_launch.sh +0 -0
  203. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/examples/tmux_split.sh +0 -0
  204. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/package.json +0 -0
  205. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/setup.cfg +0 -0
  206. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/.cmd2rc +0 -0
  207. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/__init__.py +0 -0
  208. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/pyscript/py_locals.py +0 -0
  209. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/pyscript/pyscript_dir.py +0 -0
  210. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/pyscript/stdout_capture.py +0 -0
  211. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/relative_multiple.txt +0 -0
  212. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/script.py +0 -0
  213. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/script.txt +0 -0
  214. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/scripts/binary.bin +0 -0
  215. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/scripts/empty.txt +0 -0
  216. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/scripts/help.txt +0 -0
  217. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/scripts/nested.txt +0 -0
  218. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/scripts/one_down.txt +0 -0
  219. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/scripts/postcmds.txt +0 -0
  220. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/scripts/precmds.txt +0 -0
  221. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/scripts/utf8.txt +0 -0
  222. {cmd2-4.0.0b2 → cmd2-4.0.0b4}/tests/test_utils_defining_class.py +0 -0
@@ -14,7 +14,7 @@ repos:
14
14
  - id: trailing-whitespace
15
15
 
16
16
  - repo: https://github.com/astral-sh/ruff-pre-commit
17
- rev: "v0.15.6"
17
+ rev: "v0.15.8"
18
18
  hooks:
19
19
  - id: ruff-format
20
20
  args: [--config=ruff.toml]
@@ -64,6 +64,38 @@ prompt is displayed.
64
64
  before calling it like the previous functions did.
65
65
  - Removed `Cmd.default_to_shell`.
66
66
  - Removed `Cmd.ruler` since `cmd2` no longer uses it.
67
+ - All parsers used with `cmd2` commands must be an instance of `Cmd2ArgumentParser` or a child
68
+ class of it.
69
+ - Removed `set_ap_completer_type()` and `get_ap_completer_type()` since `ap_completer_type` is
70
+ now a public member of `Cmd2ArgumentParser`.
71
+ - Moved `set_parser_prog()` to `Cmd2ArgumentParser.update_prog()`.
72
+ - Renamed `cmd2_handler` to `cmd2_subcmd_handler` in the `argparse.Namespace` for clarity.
73
+ - Removed `Cmd2AttributeWrapper` class. `argparse.Namespace` objects passed to command functions
74
+ now contain direct attributes for `cmd2_statement` and `cmd2_subcmd_handler`.
75
+ - Renamed `cmd2/command_definition.py` to `cmd2/command_set.py`.
76
+ - Removed `Cmd.doc_header` and the `with_default_category` decorator. Help categorization is now
77
+ driven by the `DEFAULT_CATEGORY` class variable (see **Simplified command categorization** in
78
+ the Enhancements section below for details).
79
+ - Removed `Cmd.undoc_header` since all commands are now considered categorized.
80
+ - Renamed `Cmd.cmd_func()` to `Cmd.get_command_func()`.
81
+ - `cmd2` no longer sets a default title for a subparsers group. If you desire a title, you will
82
+ need to pass one in like this `parser.add_subparsers(title="subcommands")`. This is standard
83
+ `argparse` behavior.
84
+ - `TextGroup` is now a standalone Rich renderable.
85
+ - Removed `formatter_creator` parameter from `TextGroup.__init__()`.
86
+ - Removed `Cmd2ArgumentParser.create_text_group()` method.
87
+ - `argparse` and `Rich` integration refactoring:
88
+ - Renamed `argparse_custom` module to `argparse_utils`.
89
+ - Moved the following classes from `argparse_utils` to `rich_utils`:
90
+ - `Cmd2HelpFormatter`
91
+ - `ArgumentDefaultsCmd2HelpFormatter`
92
+ - `MetavarTypeCmd2HelpFormatter`
93
+ - `RawDescriptionCmd2HelpFormatter`
94
+ - `RawTextCmd2HelpFormatter`
95
+ - `TextGroup`
96
+ - Replaced the global `APP_THEME` constant in `rich_utils.py` with `get_theme()` and
97
+ `set_theme()` functions to support lazy initialization and safer in-place updates of the
98
+ theme.
67
99
  - Enhancements
68
100
  - New `cmd2.Cmd` parameters
69
101
  - **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These
@@ -79,6 +111,7 @@ prompt is displayed.
79
111
  - **pre_prompt**: hook method that is called before the prompt is displayed, but after
80
112
  `prompt-toolkit` event loop has started
81
113
  - **read_secret**: read secrets like passwords without displaying them to the terminal
114
+ - **ppretty**: a cmd2-compatible replacement for `rich.pretty.pprint()`
82
115
  - New settables:
83
116
  - **max_column_completion_results**: (int) the maximum number of completion results to
84
117
  display in a single column
@@ -88,6 +121,20 @@ prompt is displayed.
88
121
  - Add support for Python 3.15 by fixing various bugs related to internal `argparse` changes
89
122
  - Added `common_prefix` method to `cmd2.string_utils` module as a replacement for
90
123
  `os.path.commonprefix` since that is now deprecated in Python 3.15
124
+ - Simplified command categorization:
125
+ - By default, all commands in a class are grouped under its `DEFAULT_CATEGORY`.
126
+ - Individual commands can still be manually moved using the `with_category()` decorator.
127
+ - For more details and examples, see the [Help](docs/features/help.md) documentation and the
128
+ `examples/default_categories.py` file.
129
+ - `CommandSet` is now a generic class, which allows developers to parameterize it with their
130
+ specific `cmd2.Cmd` subclass (e.g.,`class MyCommandSet(CommandSet[MyApp]):`). This provides
131
+ full type hints and IDE autocompletion for `self._cmd` without needing to override and cast
132
+ the property.
133
+
134
+ ## 3.5.0 (April 13, 2026)
135
+
136
+ - Bug Fixes
137
+ - Fixed issue where Rich stripped colors from text in test environments where TERM=dumb.
91
138
 
92
139
  ## 3.4.0 (March 3, 2026)
93
140
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmd2
3
- Version: 4.0.0b2
3
+ Version: 4.0.0b4
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
@@ -25,8 +25,8 @@ License-File: LICENSE
25
25
  Requires-Dist: backports.strenum; python_version == "3.10"
26
26
  Requires-Dist: prompt-toolkit>=3.0.52
27
27
  Requires-Dist: pyperclip>=1.8.2
28
- Requires-Dist: rich>=14.3.0
29
- Requires-Dist: rich-argparse>=1.7.1
28
+ Requires-Dist: rich>=15.0.0
29
+ Requires-Dist: rich-argparse>=1.7.2
30
30
  Requires-Dist: typing-extensions; python_version == "3.10"
31
31
  Dynamic: license-file
32
32
 
@@ -12,18 +12,14 @@ from . import (
12
12
  string_utils,
13
13
  )
14
14
  from .argparse_completer import set_default_ap_completer_type
15
- from .argparse_custom import (
15
+ from .argparse_utils import (
16
16
  Cmd2ArgumentParser,
17
- Cmd2AttributeWrapper,
18
17
  register_argparse_argument_parameter,
19
18
  set_default_argument_parser_type,
20
19
  )
21
20
  from .cmd2 import Cmd
22
21
  from .colors import Color
23
- from .command_definition import (
24
- CommandSet,
25
- with_default_category,
26
- )
22
+ from .command_set import CommandSet
27
23
  from .completion import (
28
24
  Choices,
29
25
  CompletionItem,
@@ -48,7 +44,17 @@ from .exceptions import (
48
44
  )
49
45
  from .parsing import Statement
50
46
  from .py_bridge import CommandResult
51
- from .rich_utils import RichPrintKwargs
47
+ from .rich_utils import (
48
+ ArgumentDefaultsCmd2HelpFormatter,
49
+ Cmd2HelpFormatter,
50
+ MetavarTypeCmd2HelpFormatter,
51
+ RawDescriptionCmd2HelpFormatter,
52
+ RawTextCmd2HelpFormatter,
53
+ RichPrintKwargs,
54
+ TextGroup,
55
+ get_theme,
56
+ set_theme,
57
+ )
52
58
  from .string_utils import stylize
53
59
  from .styles import Cmd2Style
54
60
  from .utils import (
@@ -59,50 +65,56 @@ from .utils import (
59
65
  )
60
66
 
61
67
  __all__: list[str] = [ # noqa: RUF022
62
- 'COMMAND_NAME',
63
- 'DEFAULT_SHORTCUTS',
68
+ "COMMAND_NAME",
69
+ "DEFAULT_SHORTCUTS",
64
70
  # Argparse Exports
65
- 'Cmd2ArgumentParser',
66
- 'Cmd2AttributeWrapper',
67
- 'register_argparse_argument_parameter',
68
- 'set_default_ap_completer_type',
69
- 'set_default_argument_parser_type',
71
+ "Cmd2ArgumentParser",
72
+ "register_argparse_argument_parameter",
73
+ "set_default_ap_completer_type",
74
+ "set_default_argument_parser_type",
70
75
  # Cmd2
71
- 'Cmd',
72
- 'CommandResult',
73
- 'CommandSet',
74
- 'Statement',
76
+ "Cmd",
77
+ "CommandResult",
78
+ "CommandSet",
79
+ "Statement",
75
80
  # Colors
76
81
  "Color",
77
82
  # Completion
78
- 'Choices',
79
- 'CompletionItem',
80
- 'Completions',
83
+ "Choices",
84
+ "CompletionItem",
85
+ "Completions",
81
86
  # Decorators
82
- 'with_argument_list',
83
- 'with_argparser',
84
- 'with_category',
85
- 'with_default_category',
86
- 'as_subcommand_to',
87
+ "with_argument_list",
88
+ "with_argparser",
89
+ "with_category",
90
+ "as_subcommand_to",
87
91
  # Exceptions
88
- 'Cmd2ArgparseError',
89
- 'CommandSetRegistrationError',
90
- 'CompletionError',
91
- 'PassThroughException',
92
- 'SkipPostcommandHooks',
92
+ "Cmd2ArgparseError",
93
+ "CommandSetRegistrationError",
94
+ "CompletionError",
95
+ "PassThroughException",
96
+ "SkipPostcommandHooks",
93
97
  # modules
94
- 'plugin',
95
- 'rich_utils',
96
- 'string_utils',
98
+ "plugin",
99
+ "rich_utils",
100
+ "string_utils",
97
101
  # Rich Utils
98
- 'RichPrintKwargs',
102
+ "ArgumentDefaultsCmd2HelpFormatter",
103
+ "Cmd2HelpFormatter",
104
+ "get_theme",
105
+ "MetavarTypeCmd2HelpFormatter",
106
+ "RawDescriptionCmd2HelpFormatter",
107
+ "RawTextCmd2HelpFormatter",
108
+ "RichPrintKwargs",
109
+ "set_theme",
110
+ "TextGroup",
99
111
  # String Utils
100
- 'stylize',
112
+ "stylize",
101
113
  # Styles,
102
- 'Cmd2Style',
114
+ "Cmd2Style",
103
115
  # Utilities
104
- 'categorize',
105
- 'CustomCompletionSettings',
106
- 'Settable',
107
- 'set_default_str_sort_key',
116
+ "categorize",
117
+ "CustomCompletionSettings",
118
+ "Settable",
119
+ "set_default_str_sort_key",
108
120
  ]
@@ -1,15 +1,12 @@
1
1
  """Module defines the ArgparseCompleter class which provides argparse-based completion to cmd2 apps.
2
2
 
3
- See the header of argparse_custom.py for instructions on how to use these features.
3
+ See the header of argparse_utils.py for instructions on how to use these features.
4
4
  """
5
5
 
6
6
  import argparse
7
7
  import dataclasses
8
8
  import inspect
9
- from collections import (
10
- defaultdict,
11
- deque,
12
- )
9
+ from collections import deque
13
10
  from collections.abc import (
14
11
  Mapping,
15
12
  MutableSequence,
@@ -22,39 +19,42 @@ from typing import (
22
19
  cast,
23
20
  )
24
21
 
25
- from rich.text import Text
26
-
27
- from .constants import INFINITY
28
- from .rich_utils import Cmd2SimpleTable
29
-
30
- if TYPE_CHECKING: # pragma: no cover
31
- from .cmd2 import Cmd
32
-
33
22
  from rich.table import Column
23
+ from rich.text import Text
34
24
 
35
- from .argparse_custom import (
36
- ChoicesCallable,
37
- generate_range_error,
25
+ from .argparse_utils import (
26
+ Cmd2ArgumentParser,
27
+ build_range_error,
38
28
  )
39
- from .command_definition import CommandSet
29
+ from .command_set import CommandSet
40
30
  from .completion import (
41
31
  CompletionItem,
42
32
  Completions,
43
- all_display_numeric,
44
33
  )
34
+ from .constants import INFINITY
45
35
  from .exceptions import CompletionError
36
+ from .rich_utils import Cmd2SimpleTable
37
+ from .types import (
38
+ CmdOrSetT,
39
+ UnboundChoicesProvider,
40
+ UnboundCompleter,
41
+ )
42
+
43
+ if TYPE_CHECKING: # pragma: no cover
44
+ from .cmd2 import Cmd
45
+
46
46
 
47
47
  # Name of the choice/completer function argument that, if present, will be passed a dictionary of
48
48
  # command line tokens up through the token being completed mapped to their argparse destination name.
49
- ARG_TOKENS = 'arg_tokens'
49
+ ARG_TOKENS = "arg_tokens"
50
50
 
51
51
 
52
- def _build_hint(parser: argparse.ArgumentParser, arg_action: argparse.Action) -> str:
52
+ def _build_hint(parser: Cmd2ArgumentParser, arg_action: argparse.Action) -> str:
53
53
  """Build completion hint for a given argument."""
54
54
  # Check if hinting is disabled for this argument
55
55
  suppress_hint = arg_action.get_suppress_tab_hint() # type: ignore[attr-defined]
56
56
  if suppress_hint or arg_action.help == argparse.SUPPRESS:
57
- return ''
57
+ return ""
58
58
 
59
59
  # Use the parser's help formatter to display just this action's help text
60
60
  formatter = parser._get_formatter()
@@ -64,12 +64,12 @@ def _build_hint(parser: argparse.ArgumentParser, arg_action: argparse.Action) ->
64
64
  return formatter.format_help()
65
65
 
66
66
 
67
- def _single_prefix_char(token: str, parser: argparse.ArgumentParser) -> bool:
67
+ def _single_prefix_char(token: str, parser: Cmd2ArgumentParser) -> bool:
68
68
  """Is a token just a single flag prefix character."""
69
69
  return len(token) == 1 and token[0] in parser.prefix_chars
70
70
 
71
71
 
72
- def _looks_like_flag(token: str, parser: argparse.ArgumentParser) -> bool:
72
+ def _looks_like_flag(token: str, parser: Cmd2ArgumentParser) -> bool:
73
73
  """Determine if a token looks like a flag.
74
74
 
75
75
  Unless an argument has nargs set to argparse.REMAINDER, then anything that looks like a flag
@@ -90,7 +90,7 @@ def _looks_like_flag(token: str, parser: argparse.ArgumentParser) -> bool:
90
90
  return False
91
91
 
92
92
  # Flags can't have a space
93
- return ' ' not in token
93
+ return " " not in token
94
94
 
95
95
 
96
96
  class _ArgumentState:
@@ -133,19 +133,19 @@ class _UnfinishedFlagError(CompletionError):
133
133
 
134
134
  :param flag_arg_state: information about the unfinished flag action.
135
135
  """
136
- arg = f'{argparse._get_action_name(flag_arg_state.action)}'
137
- err = f'{generate_range_error(flag_arg_state.min, flag_arg_state.max)}'
136
+ arg = f"{argparse._get_action_name(flag_arg_state.action)}"
137
+ err = f"{build_range_error(flag_arg_state.min, flag_arg_state.max)}"
138
138
  error = f"Error: argument {arg}: {err} ({flag_arg_state.count} entered)"
139
139
  super().__init__(error)
140
140
 
141
141
 
142
142
  class _NoResultsError(CompletionError):
143
- def __init__(self, parser: argparse.ArgumentParser, arg_action: argparse.Action) -> None:
143
+ def __init__(self, parser: Cmd2ArgumentParser, arg_action: argparse.Action) -> None:
144
144
  """CompletionError which occurs when there are no results.
145
145
 
146
146
  If hinting is allowed on this argument, then its hint text will display.
147
147
 
148
- :param parser: ArgumentParser instance which owns the action being completed
148
+ :param parser: Cmd2ArgumentParser instance which owns the action being completed
149
149
  :param arg_action: action being completed.
150
150
  """
151
151
  # Set apply_style to False because we don't want hints to look like errors
@@ -157,14 +157,14 @@ class ArgparseCompleter:
157
157
 
158
158
  def __init__(
159
159
  self,
160
- parser: argparse.ArgumentParser,
161
- cmd2_app: 'Cmd',
160
+ parser: Cmd2ArgumentParser,
161
+ cmd2_app: "Cmd",
162
162
  *,
163
163
  parent_tokens: Mapping[str, MutableSequence[str]] | None = None,
164
164
  ) -> None:
165
165
  """Create an ArgparseCompleter.
166
166
 
167
- :param parser: ArgumentParser instance
167
+ :param parser: Cmd2ArgumentParser instance
168
168
  :param cmd2_app: reference to the Cmd2 application that owns this ArgparseCompleter
169
169
  :param parent_tokens: optional Mapping of parent parsers' arg names to their tokens
170
170
  This is only used by ArgparseCompleter when recursing on subcommand parsers
@@ -187,7 +187,7 @@ class ArgparseCompleter:
187
187
  self._positional_actions: list[argparse.Action] = []
188
188
 
189
189
  # This will be set if self._parser has subcommands
190
- self._subcommand_action: argparse._SubParsersAction[argparse.ArgumentParser] | None = None
190
+ self._subcommand_action: argparse._SubParsersAction[Cmd2ArgumentParser] | None = None
191
191
 
192
192
  # Start digging through the argparse structures.
193
193
  # _actions is the top level container of parameter definitions
@@ -214,7 +214,7 @@ class ArgparseCompleter:
214
214
  endidx: int,
215
215
  tokens: Sequence[str],
216
216
  *,
217
- cmd_set: CommandSet | None = None,
217
+ cmd_set: CommandSet[Any] | None = None,
218
218
  ) -> Completions:
219
219
  """Complete text using argparse metadata.
220
220
 
@@ -248,7 +248,7 @@ class ArgparseCompleter:
248
248
  used_flags: set[str] = set()
249
249
 
250
250
  # Keeps track of arguments we've seen and any tokens they consumed
251
- consumed_arg_values: dict[str, list[str]] = defaultdict(list)
251
+ consumed_arg_values: dict[str, list[str]] = {}
252
252
 
253
253
  # Completed mutually exclusive groups
254
254
  completed_mutex_groups: dict[argparse._MutuallyExclusiveGroup, argparse.Action] = {}
@@ -256,7 +256,7 @@ class ArgparseCompleter:
256
256
  def consume_argument(arg_state: _ArgumentState, arg_token: str) -> None:
257
257
  """Consume token as an argument."""
258
258
  arg_state.count += 1
259
- consumed_arg_values[arg_state.action.dest].append(arg_token)
259
+ consumed_arg_values.setdefault(arg_state.action.dest, []).append(arg_token)
260
260
 
261
261
  #############################################################################################
262
262
  # Parse all but the last token
@@ -269,14 +269,14 @@ class ArgparseCompleter:
269
269
 
270
270
  # If we're in a flag REMAINDER arg, force all future tokens to go to that until a double dash is hit
271
271
  if flag_arg_state is not None and flag_arg_state.is_remainder:
272
- if token == '--': # noqa: S105
272
+ if token == "--": # noqa: S105
273
273
  flag_arg_state = None
274
274
  else:
275
275
  consume_argument(flag_arg_state, token)
276
276
  continue
277
277
 
278
278
  # Handle '--' which tells argparse all remaining arguments are non-flags
279
- if token == '--' and not skip_remaining_flags: # noqa: S105
279
+ if token == "--" and not skip_remaining_flags: # noqa: S105
280
280
  # Check if there is an unfinished flag
281
281
  if (
282
282
  flag_arg_state is not None
@@ -333,7 +333,7 @@ class ArgparseCompleter:
333
333
  # filter them from future completion results and clear any previously
334
334
  # recorded values for this destination.
335
335
  used_flags.update(action.option_strings)
336
- consumed_arg_values[action.dest].clear()
336
+ consumed_arg_values[action.dest] = []
337
337
 
338
338
  new_arg_state = _ArgumentState(action)
339
339
 
@@ -357,9 +357,8 @@ class ArgparseCompleter:
357
357
  action = remaining_positionals.popleft()
358
358
 
359
359
  # Are we at a subcommand? If so, forward to the matching completer
360
- if action == self._subcommand_action:
360
+ if self._subcommand_action is not None and action == self._subcommand_action:
361
361
  if token in self._subcommand_action.choices:
362
- # Merge self._parent_tokens and consumed_arg_values
363
362
  parent_tokens = {**self._parent_tokens, **consumed_arg_values}
364
363
 
365
364
  # Include the subcommand name if its destination was set
@@ -438,8 +437,8 @@ class ArgparseCompleter:
438
437
  if arg_action == completer_action:
439
438
  return
440
439
 
441
- arg_str = f'{argparse._get_action_name(arg_action)}'
442
- completer_str = f'{argparse._get_action_name(completer_action)}'
440
+ arg_str = f"{argparse._get_action_name(arg_action)}"
441
+ completer_str = f"{argparse._get_action_name(completer_action)}"
443
442
  error = f"Error: argument {arg_str}: not allowed with argument {completer_str}"
444
443
  raise CompletionError(error)
445
444
 
@@ -470,7 +469,7 @@ class ArgparseCompleter:
470
469
  consumed_arg_values: dict[str, list[str]],
471
470
  used_flags: set[str],
472
471
  skip_remaining_flags: bool,
473
- cmd_set: CommandSet | None,
472
+ cmd_set: CommandSet[Any] | None,
474
473
  ) -> Completions:
475
474
  """Perform final completion step handling positionals and flags."""
476
475
  # Check if we are completing a flag name. This check ignores strings with a length of one, like '-'.
@@ -554,7 +553,7 @@ class ArgparseCompleter:
554
553
  match_against.append(flag)
555
554
 
556
555
  # Build a dictionary linking actions with their matched flag names
557
- matched_actions: dict[argparse.Action, list[str]] = defaultdict(list)
556
+ matched_actions: dict[argparse.Action, list[str]] = {}
558
557
 
559
558
  # Keep flags sorted in the order provided by argparse so our completion
560
559
  # suggestions display the same as argparse help text.
@@ -562,23 +561,23 @@ class ArgparseCompleter:
562
561
 
563
562
  for flag in matched_flags.to_strings():
564
563
  action = self._flag_to_action[flag]
565
- matched_actions[action].append(flag)
564
+ matched_actions.setdefault(action, []).append(flag)
566
565
 
567
566
  # For completion suggestions, group matched flags by action
568
567
  items: list[CompletionItem] = []
569
568
  for action, option_strings in matched_actions.items():
570
- flag_text = ', '.join(option_strings)
569
+ flag_text = ", ".join(option_strings)
571
570
 
572
571
  # Mark optional flags with brackets
573
572
  if not action.required:
574
- flag_text = '[' + flag_text + ']'
573
+ flag_text = "[" + flag_text + "]"
575
574
 
576
575
  # Use the first option string as the completion result for this action
577
576
  items.append(
578
577
  CompletionItem(
579
578
  option_strings[0],
580
579
  display=flag_text,
581
- display_meta=action.help or '',
580
+ display_meta=action.help or "",
582
581
  )
583
582
  )
584
583
 
@@ -643,12 +642,15 @@ class ArgparseCompleter:
643
642
  # the 3rd or more argument here.
644
643
  destination = destination[min(len(destination) - 1, arg_state.count)]
645
644
 
646
- # Determine if all display values are numeric so we can right-align them
647
- all_nums = all_display_numeric(completions.items)
648
-
649
645
  # Build header row
650
646
  rich_columns: list[Column] = []
651
- rich_columns.append(Column(destination.upper(), justify="right" if all_nums else "left", no_wrap=True))
647
+ rich_columns.append(
648
+ Column(
649
+ destination.upper(),
650
+ justify="right" if completions.numeric_display else "left",
651
+ no_wrap=True,
652
+ )
653
+ )
652
654
  rich_columns.extend(
653
655
  column if isinstance(column, Column) else Column(column, overflow="fold") for column in table_columns
654
656
  )
@@ -707,51 +709,50 @@ class ArgparseCompleter:
707
709
  return
708
710
  self._parser.print_help(file=file)
709
711
 
710
- def _get_raw_choices(self, arg_state: _ArgumentState) -> list[CompletionItem] | ChoicesCallable | None:
711
- """Extract choices from action or return the choices_callable."""
712
- if arg_state.action.choices is not None:
713
- # If choices are subcommands, then get their help text to populate display_meta.
714
- if isinstance(arg_state.action, argparse._SubParsersAction):
715
- parser_help = {}
716
- for action in arg_state.action._choices_actions:
717
- if action.dest in arg_state.action.choices:
718
- subparser = arg_state.action.choices[action.dest]
719
- parser_help[subparser] = action.help or ''
720
-
721
- return [
722
- CompletionItem(name, display_meta=parser_help.get(subparser, ''))
723
- for name, subparser in arg_state.action.choices.items()
724
- ]
725
-
726
- # Standard choices
712
+ def _choices_to_items(self, arg_state: _ArgumentState) -> list[CompletionItem]:
713
+ """Convert choices from action to list of CompletionItems."""
714
+ if arg_state.action.choices is None:
715
+ return []
716
+
717
+ # If choices are subcommands, then get their help text to populate display_meta.
718
+ if isinstance(arg_state.action, argparse._SubParsersAction):
719
+ parser_help = {}
720
+ for action in arg_state.action._choices_actions:
721
+ if action.dest in arg_state.action.choices:
722
+ subparser = arg_state.action.choices[action.dest]
723
+ parser_help[subparser] = action.help or ""
724
+
727
725
  return [
728
- choice if isinstance(choice, CompletionItem) else CompletionItem(choice) for choice in arg_state.action.choices
726
+ CompletionItem(name, display_meta=parser_help.get(subparser, ""))
727
+ for name, subparser in arg_state.action.choices.items()
729
728
  ]
730
729
 
731
- choices_callable: ChoicesCallable | None = arg_state.action.get_choices_callable() # type: ignore[attr-defined]
732
- return choices_callable
730
+ # Standard choices
731
+ return [
732
+ choice if isinstance(choice, CompletionItem) else CompletionItem(choice) for choice in arg_state.action.choices
733
+ ]
733
734
 
734
735
  def _prepare_callable_params(
735
736
  self,
736
- choices_callable: ChoicesCallable,
737
+ to_call: UnboundChoicesProvider[CmdOrSetT] | UnboundCompleter[CmdOrSetT],
737
738
  arg_state: _ArgumentState,
738
739
  text: str,
739
740
  consumed_arg_values: dict[str, list[str]],
740
- cmd_set: CommandSet | None,
741
+ cmd_set: CommandSet[Any] | None,
741
742
  ) -> tuple[list[Any], dict[str, Any]]:
742
743
  """Resolve the instance and arguments required to call a choices/completer function."""
743
744
  args: list[Any] = []
744
745
  kwargs: dict[str, Any] = {}
745
746
 
746
747
  # Resolve the 'self' instance for the method
747
- self_arg = self._cmd2_app._resolve_func_self(choices_callable.to_call, cmd_set)
748
+ self_arg = self._cmd2_app._resolve_func_self(to_call, cmd_set)
748
749
  if self_arg is None:
749
- raise CompletionError("Could not find CommandSet instance matching defining type for completer")
750
+ raise CompletionError("Could not find CommandSet instance matching defining type")
750
751
 
751
752
  args.append(self_arg)
752
753
 
753
754
  # Check if the function expects 'arg_tokens'
754
- to_call_params = inspect.signature(choices_callable.to_call).parameters
755
+ to_call_params = inspect.signature(to_call).parameters
755
756
  if ARG_TOKENS in to_call_params:
756
757
  arg_tokens = {**self._parent_tokens, **consumed_arg_values}
757
758
  arg_tokens.setdefault(arg_state.action.dest, []).append(text)
@@ -768,33 +769,40 @@ class ArgparseCompleter:
768
769
  arg_state: _ArgumentState,
769
770
  consumed_arg_values: dict[str, list[str]],
770
771
  *,
771
- cmd_set: CommandSet | None = None,
772
+ cmd_set: CommandSet[Any] | None = None,
772
773
  ) -> Completions:
773
774
  """Completion routine for an argparse argument.
774
775
 
775
776
  :return: a Completions object
776
777
  :raises CompletionError: if the completer or choices function this calls raises one
777
778
  """
778
- raw_choices = self._get_raw_choices(arg_state)
779
- if not raw_choices:
780
- return Completions()
781
-
782
- # Check if the argument uses a completer function
783
- if isinstance(raw_choices, ChoicesCallable) and raw_choices.is_completer:
784
- args, kwargs = self._prepare_callable_params(raw_choices, arg_state, text, consumed_arg_values, cmd_set)
779
+ # Check if the argument uses a completer
780
+ completer = arg_state.action.get_completer() # type: ignore[attr-defined]
781
+ if completer is not None:
782
+ args, kwargs = self._prepare_callable_params(
783
+ completer,
784
+ arg_state,
785
+ text,
786
+ consumed_arg_values,
787
+ cmd_set,
788
+ )
785
789
  args.extend([text, line, begidx, endidx])
786
- completions = raw_choices.completer(*args, **kwargs)
790
+ completions: Completions = completer(*args, **kwargs)
787
791
 
788
- # Otherwise it uses a choices list or choices provider function
792
+ # Otherwise it uses a choices provider or choices list
789
793
  else:
790
- all_choices: list[CompletionItem] = []
791
-
792
- if isinstance(raw_choices, ChoicesCallable):
793
- args, kwargs = self._prepare_callable_params(raw_choices, arg_state, text, consumed_arg_values, cmd_set)
794
- choices_func = raw_choices.choices_provider
795
- all_choices = list(choices_func(*args, **kwargs))
794
+ choices_provider = arg_state.action.get_choices_provider() # type: ignore[attr-defined]
795
+ if choices_provider is not None:
796
+ args, kwargs = self._prepare_callable_params(
797
+ choices_provider,
798
+ arg_state,
799
+ text,
800
+ consumed_arg_values,
801
+ cmd_set,
802
+ )
803
+ all_choices = list(choices_provider(*args, **kwargs))
796
804
  else:
797
- all_choices = raw_choices
805
+ all_choices = self._choices_to_items(arg_state)
798
806
 
799
807
  # Filter used values and run basic completion
800
808
  used_values = consumed_arg_values.get(arg_state.action.dest, [])