cmd2 3.1.1__tar.gz → 3.1.3__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 (228) hide show
  1. {cmd2-3.1.1 → cmd2-3.1.3}/.pre-commit-config.yaml +2 -2
  2. {cmd2-3.1.1 → cmd2-3.1.3}/CHANGELOG.md +11 -0
  3. {cmd2-3.1.1 → cmd2-3.1.3}/PKG-INFO +2 -1
  4. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/argparse_completer.py +2 -2
  5. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/argparse_custom.py +7 -2
  6. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/cmd2.py +98 -47
  7. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/command_definition.py +5 -3
  8. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/parsing.py +5 -1
  9. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/utils.py +1 -5
  10. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2.egg-info/PKG-INFO +2 -1
  11. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2.egg-info/requires.txt +1 -0
  12. {cmd2-3.1.1 → cmd2-3.1.3}/package.json +1 -1
  13. {cmd2-3.1.1 → cmd2-3.1.3}/pyproject.toml +1 -0
  14. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_cmd2.py +57 -2
  15. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_completion.py +51 -13
  16. {cmd2-3.1.1 → cmd2-3.1.3}/.prettierignore +0 -0
  17. {cmd2-3.1.1 → cmd2-3.1.3}/.prettierrc +0 -0
  18. {cmd2-3.1.1 → cmd2-3.1.3}/GEMINI.md +0 -0
  19. {cmd2-3.1.1 → cmd2-3.1.3}/LICENSE +0 -0
  20. {cmd2-3.1.1 → cmd2-3.1.3}/MANIFEST.in +0 -0
  21. {cmd2-3.1.1 → cmd2-3.1.3}/Makefile +0 -0
  22. {cmd2-3.1.1 → cmd2-3.1.3}/README.md +0 -0
  23. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/__init__.py +0 -0
  24. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/clipboard.py +0 -0
  25. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/colors.py +0 -0
  26. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/constants.py +0 -0
  27. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/decorators.py +0 -0
  28. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/exceptions.py +0 -0
  29. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/history.py +0 -0
  30. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/plugin.py +0 -0
  31. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/py.typed +0 -0
  32. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/py_bridge.py +0 -0
  33. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/rich_utils.py +0 -0
  34. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/rl_utils.py +0 -0
  35. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/string_utils.py +0 -0
  36. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/styles.py +0 -0
  37. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/terminal_utils.py +0 -0
  38. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2/transcript.py +0 -0
  39. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2.egg-info/SOURCES.txt +0 -0
  40. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2.egg-info/dependency_links.txt +0 -0
  41. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2.egg-info/top_level.txt +0 -0
  42. {cmd2-3.1.1 → cmd2-3.1.3}/cmd2.png +0 -0
  43. {cmd2-3.1.1 → cmd2-3.1.3}/codecov.yml +0 -0
  44. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/argparse_completer.md +0 -0
  45. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/argparse_custom.md +0 -0
  46. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/clipboard.md +0 -0
  47. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/cmd.md +0 -0
  48. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/colors.md +0 -0
  49. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/command_definition.md +0 -0
  50. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/constants.md +0 -0
  51. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/decorators.md +0 -0
  52. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/exceptions.md +0 -0
  53. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/history.md +0 -0
  54. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/index.md +0 -0
  55. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/parsing.md +0 -0
  56. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/plugin.md +0 -0
  57. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/py_bridge.md +0 -0
  58. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/rich_utils.md +0 -0
  59. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/rl_utils.md +0 -0
  60. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/string_utils.md +0 -0
  61. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/styles.md +0 -0
  62. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/terminal_utils.md +0 -0
  63. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/transcript.md +0 -0
  64. {cmd2-3.1.1 → cmd2-3.1.3}/docs/api/utils.md +0 -0
  65. {cmd2-3.1.1 → cmd2-3.1.3}/docs/doc_conventions.md +0 -0
  66. {cmd2-3.1.1 → cmd2-3.1.3}/docs/examples/alternate_event_loops.md +0 -0
  67. {cmd2-3.1.1 → cmd2-3.1.3}/docs/examples/examples.md +0 -0
  68. {cmd2-3.1.1 → cmd2-3.1.3}/docs/examples/getting_started.md +0 -0
  69. {cmd2-3.1.1 → cmd2-3.1.3}/docs/examples/index.md +0 -0
  70. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/argument_processing.md +0 -0
  71. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/builtin_commands.md +0 -0
  72. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/clipboard.md +0 -0
  73. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/commands.md +0 -0
  74. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/completion.md +0 -0
  75. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/disable_commands.md +0 -0
  76. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/embedded_python_shells.md +0 -0
  77. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/generating_output.md +0 -0
  78. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/help.md +0 -0
  79. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/history.md +0 -0
  80. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/hooks.md +0 -0
  81. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/index.md +0 -0
  82. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/initialization.md +0 -0
  83. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/misc.md +0 -0
  84. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/modular_commands.md +0 -0
  85. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/multiline_commands.md +0 -0
  86. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/os.md +0 -0
  87. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/packaging.md +0 -0
  88. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/plugins.md +0 -0
  89. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/prompt.md +0 -0
  90. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/redirection.md +0 -0
  91. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/scripting.md +0 -0
  92. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/settings.md +0 -0
  93. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/shortcuts_aliases_macros.md +0 -0
  94. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/startup_commands.md +0 -0
  95. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/table_creation.md +0 -0
  96. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/theme.md +0 -0
  97. {cmd2-3.1.1 → cmd2-3.1.3}/docs/features/transcripts.md +0 -0
  98. {cmd2-3.1.1 → cmd2-3.1.3}/docs/index.md +0 -0
  99. {cmd2-3.1.1 → cmd2-3.1.3}/docs/javascripts/readthedocs.js +0 -0
  100. {cmd2-3.1.1 → cmd2-3.1.3}/docs/migrating/incompatibilities.md +0 -0
  101. {cmd2-3.1.1 → cmd2-3.1.3}/docs/migrating/index.md +0 -0
  102. {cmd2-3.1.1 → cmd2-3.1.3}/docs/migrating/minimum.md +0 -0
  103. {cmd2-3.1.1 → cmd2-3.1.3}/docs/migrating/next_steps.md +0 -0
  104. {cmd2-3.1.1 → cmd2-3.1.3}/docs/migrating/why.md +0 -0
  105. {cmd2-3.1.1 → cmd2-3.1.3}/docs/mixins/index.md +0 -0
  106. {cmd2-3.1.1 → cmd2-3.1.3}/docs/mixins/mixin_template.md +0 -0
  107. {cmd2-3.1.1 → cmd2-3.1.3}/docs/overrides/main.html +0 -0
  108. {cmd2-3.1.1 → cmd2-3.1.3}/docs/overview/alternatives.md +0 -0
  109. {cmd2-3.1.1 → cmd2-3.1.3}/docs/overview/index.md +0 -0
  110. {cmd2-3.1.1 → cmd2-3.1.3}/docs/overview/installation.md +0 -0
  111. {cmd2-3.1.1 → cmd2-3.1.3}/docs/overview/integrating.md +0 -0
  112. {cmd2-3.1.1 → cmd2-3.1.3}/docs/overview/resources.md +0 -0
  113. {cmd2-3.1.1 → cmd2-3.1.3}/docs/stylesheets/cmd2.css +0 -0
  114. {cmd2-3.1.1 → cmd2-3.1.3}/docs/testing.md +0 -0
  115. {cmd2-3.1.1 → cmd2-3.1.3}/docs/upgrades.md +0 -0
  116. {cmd2-3.1.1 → cmd2-3.1.3}/examples/.cmd2rc +0 -0
  117. {cmd2-3.1.1 → cmd2-3.1.3}/examples/README.md +0 -0
  118. {cmd2-3.1.1 → cmd2-3.1.3}/examples/argparse_completion.py +0 -0
  119. {cmd2-3.1.1 → cmd2-3.1.3}/examples/argparse_example.py +0 -0
  120. {cmd2-3.1.1 → cmd2-3.1.3}/examples/async_call.py +0 -0
  121. {cmd2-3.1.1 → cmd2-3.1.3}/examples/async_printing.py +0 -0
  122. {cmd2-3.1.1 → cmd2-3.1.3}/examples/basic_completion.py +0 -0
  123. {cmd2-3.1.1 → cmd2-3.1.3}/examples/cmd2_history.dat +0 -0
  124. {cmd2-3.1.1 → cmd2-3.1.3}/examples/cmd_as_argument.py +0 -0
  125. {cmd2-3.1.1 → cmd2-3.1.3}/examples/color.py +0 -0
  126. {cmd2-3.1.1 → cmd2-3.1.3}/examples/command_sets.py +0 -0
  127. {cmd2-3.1.1 → cmd2-3.1.3}/examples/custom_parser.py +0 -0
  128. {cmd2-3.1.1 → cmd2-3.1.3}/examples/custom_types.py +0 -0
  129. {cmd2-3.1.1 → cmd2-3.1.3}/examples/default_categories.py +0 -0
  130. {cmd2-3.1.1 → cmd2-3.1.3}/examples/dynamic_commands.py +0 -0
  131. {cmd2-3.1.1 → cmd2-3.1.3}/examples/environment.py +0 -0
  132. {cmd2-3.1.1 → cmd2-3.1.3}/examples/event_loops.py +0 -0
  133. {cmd2-3.1.1 → cmd2-3.1.3}/examples/exit_code.py +0 -0
  134. {cmd2-3.1.1 → cmd2-3.1.3}/examples/getting_started.py +0 -0
  135. {cmd2-3.1.1 → cmd2-3.1.3}/examples/hello_cmd2.py +0 -0
  136. {cmd2-3.1.1 → cmd2-3.1.3}/examples/help_categories.py +0 -0
  137. {cmd2-3.1.1 → cmd2-3.1.3}/examples/hooks.py +0 -0
  138. {cmd2-3.1.1 → cmd2-3.1.3}/examples/migrating.py +0 -0
  139. {cmd2-3.1.1 → cmd2-3.1.3}/examples/mixin.py +0 -0
  140. {cmd2-3.1.1 → cmd2-3.1.3}/examples/modular_commands/__init__.py +0 -0
  141. {cmd2-3.1.1 → cmd2-3.1.3}/examples/modular_commands/commandset_basic.py +0 -0
  142. {cmd2-3.1.1 → cmd2-3.1.3}/examples/modular_commands/commandset_complex.py +0 -0
  143. {cmd2-3.1.1 → cmd2-3.1.3}/examples/modular_commands/commandset_custominit.py +0 -0
  144. {cmd2-3.1.1 → cmd2-3.1.3}/examples/modular_commands.py +0 -0
  145. {cmd2-3.1.1 → cmd2-3.1.3}/examples/paged_output.py +0 -0
  146. {cmd2-3.1.1 → cmd2-3.1.3}/examples/persistent_history.py +0 -0
  147. {cmd2-3.1.1 → cmd2-3.1.3}/examples/pretty_print.py +0 -0
  148. {cmd2-3.1.1 → cmd2-3.1.3}/examples/python_scripting.py +0 -0
  149. {cmd2-3.1.1 → cmd2-3.1.3}/examples/read_input.py +0 -0
  150. {cmd2-3.1.1 → cmd2-3.1.3}/examples/remove_builtin_commands.py +0 -0
  151. {cmd2-3.1.1 → cmd2-3.1.3}/examples/remove_settable.py +0 -0
  152. {cmd2-3.1.1 → cmd2-3.1.3}/examples/rich_tables.py +0 -0
  153. {cmd2-3.1.1 → cmd2-3.1.3}/examples/rich_theme.py +0 -0
  154. {cmd2-3.1.1 → cmd2-3.1.3}/examples/scripts/arg_printer.py +0 -0
  155. {cmd2-3.1.1 → cmd2-3.1.3}/examples/scripts/conditional.py +0 -0
  156. {cmd2-3.1.1 → cmd2-3.1.3}/examples/scripts/nested.txt +0 -0
  157. {cmd2-3.1.1 → cmd2-3.1.3}/examples/scripts/quit.txt +0 -0
  158. {cmd2-3.1.1 → cmd2-3.1.3}/examples/scripts/save_help_text.py +0 -0
  159. {cmd2-3.1.1 → cmd2-3.1.3}/examples/scripts/script.py +0 -0
  160. {cmd2-3.1.1 → cmd2-3.1.3}/examples/scripts/script.txt +0 -0
  161. {cmd2-3.1.1 → cmd2-3.1.3}/examples/tmux_launch.sh +0 -0
  162. {cmd2-3.1.1 → cmd2-3.1.3}/examples/tmux_split.sh +0 -0
  163. {cmd2-3.1.1 → cmd2-3.1.3}/examples/transcript_example.py +0 -0
  164. {cmd2-3.1.1 → cmd2-3.1.3}/examples/transcripts/exampleSession.txt +0 -0
  165. {cmd2-3.1.1 → cmd2-3.1.3}/examples/transcripts/pirate.transcript +0 -0
  166. {cmd2-3.1.1 → cmd2-3.1.3}/examples/transcripts/quit.txt +0 -0
  167. {cmd2-3.1.1 → cmd2-3.1.3}/examples/transcripts/transcript_regex.txt +0 -0
  168. {cmd2-3.1.1 → cmd2-3.1.3}/examples/unicode_commands.py +0 -0
  169. {cmd2-3.1.1 → cmd2-3.1.3}/mkdocs.yml +0 -0
  170. {cmd2-3.1.1 → cmd2-3.1.3}/ruff.toml +0 -0
  171. {cmd2-3.1.1 → cmd2-3.1.3}/scripts/validate_tag.py +0 -0
  172. {cmd2-3.1.1 → cmd2-3.1.3}/setup.cfg +0 -0
  173. {cmd2-3.1.1 → cmd2-3.1.3}/tests/.cmd2rc +0 -0
  174. {cmd2-3.1.1 → cmd2-3.1.3}/tests/__init__.py +0 -0
  175. {cmd2-3.1.1 → cmd2-3.1.3}/tests/conftest.py +0 -0
  176. {cmd2-3.1.1 → cmd2-3.1.3}/tests/pyscript/echo.py +0 -0
  177. {cmd2-3.1.1 → cmd2-3.1.3}/tests/pyscript/environment.py +0 -0
  178. {cmd2-3.1.1 → cmd2-3.1.3}/tests/pyscript/help.py +0 -0
  179. {cmd2-3.1.1 → cmd2-3.1.3}/tests/pyscript/py_locals.py +0 -0
  180. {cmd2-3.1.1 → cmd2-3.1.3}/tests/pyscript/pyscript_dir.py +0 -0
  181. {cmd2-3.1.1 → cmd2-3.1.3}/tests/pyscript/raises_exception.py +0 -0
  182. {cmd2-3.1.1 → cmd2-3.1.3}/tests/pyscript/recursive.py +0 -0
  183. {cmd2-3.1.1 → cmd2-3.1.3}/tests/pyscript/self_in_py.py +0 -0
  184. {cmd2-3.1.1 → cmd2-3.1.3}/tests/pyscript/stdout_capture.py +0 -0
  185. {cmd2-3.1.1 → cmd2-3.1.3}/tests/pyscript/stop.py +0 -0
  186. {cmd2-3.1.1 → cmd2-3.1.3}/tests/relative_multiple.txt +0 -0
  187. {cmd2-3.1.1 → cmd2-3.1.3}/tests/script.py +0 -0
  188. {cmd2-3.1.1 → cmd2-3.1.3}/tests/script.txt +0 -0
  189. {cmd2-3.1.1 → cmd2-3.1.3}/tests/scripts/binary.bin +0 -0
  190. {cmd2-3.1.1 → cmd2-3.1.3}/tests/scripts/empty.txt +0 -0
  191. {cmd2-3.1.1 → cmd2-3.1.3}/tests/scripts/help.txt +0 -0
  192. {cmd2-3.1.1 → cmd2-3.1.3}/tests/scripts/nested.txt +0 -0
  193. {cmd2-3.1.1 → cmd2-3.1.3}/tests/scripts/one_down.txt +0 -0
  194. {cmd2-3.1.1 → cmd2-3.1.3}/tests/scripts/postcmds.txt +0 -0
  195. {cmd2-3.1.1 → cmd2-3.1.3}/tests/scripts/precmds.txt +0 -0
  196. {cmd2-3.1.1 → cmd2-3.1.3}/tests/scripts/utf8.txt +0 -0
  197. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_argparse.py +0 -0
  198. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_argparse_completer.py +0 -0
  199. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_argparse_custom.py +0 -0
  200. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_argparse_subcommands.py +0 -0
  201. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_categories.py +0 -0
  202. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_commandset.py +0 -0
  203. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_future_annotations.py +0 -0
  204. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_history.py +0 -0
  205. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_parsing.py +0 -0
  206. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_plugin.py +0 -0
  207. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_rich_utils.py +0 -0
  208. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_run_pyscript.py +0 -0
  209. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_string_utils.py +0 -0
  210. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_terminal_utils.py +0 -0
  211. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_transcript.py +0 -0
  212. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_utils.py +0 -0
  213. {cmd2-3.1.1 → cmd2-3.1.3}/tests/test_utils_defining_class.py +0 -0
  214. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/bol_eol.txt +0 -0
  215. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/characterclass.txt +0 -0
  216. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/dotstar.txt +0 -0
  217. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/extension_notation.txt +0 -0
  218. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/failure.txt +0 -0
  219. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/from_cmdloop.txt +0 -0
  220. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/multiline_no_regex.txt +0 -0
  221. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/multiline_regex.txt +0 -0
  222. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/no_output.txt +0 -0
  223. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/no_output_last.txt +0 -0
  224. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/singleslash.txt +0 -0
  225. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/slashes_escaped.txt +0 -0
  226. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/slashslash.txt +0 -0
  227. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/spaces.txt +0 -0
  228. {cmd2-3.1.1 → cmd2-3.1.3}/tests/transcripts/word_boundaries.txt +0 -0
@@ -9,7 +9,7 @@ repos:
9
9
  - id: trailing-whitespace
10
10
 
11
11
  - repo: https://github.com/astral-sh/ruff-pre-commit
12
- rev: "v0.14.14"
12
+ rev: "v0.15.0"
13
13
  hooks:
14
14
  - id: ruff-format
15
15
  args: [--config=ruff.toml]
@@ -21,5 +21,5 @@ repos:
21
21
  hooks:
22
22
  - id: prettier
23
23
  additional_dependencies:
24
- - prettier@3.8.0
24
+ - prettier@3.8.1
25
25
  - prettier-plugin-toml@2.0.6
@@ -1,3 +1,14 @@
1
+ ## 3.1.3 (February 3, 2026)
2
+
3
+ - Bug Fixes
4
+ - Fixed issue where `delimiter_complete()` could cause more matches than display matches
5
+ - Fixed issue where `CommandSet` registration did not respect disabled categories
6
+
7
+ ## 3.1.2 (January 26, 2026)
8
+
9
+ - Bug Fixes
10
+ - Fixed missing `typing-extensions` dependency for Python 3.10
11
+
1
12
  ## 3.1.1 (January 26, 2026)
2
13
 
3
14
  - Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmd2
3
- Version: 3.1.1
3
+ Version: 3.1.3
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
@@ -28,6 +28,7 @@ Requires-Dist: pyperclip>=1.8.2
28
28
  Requires-Dist: pyreadline3>=3.4; platform_system == "Windows"
29
29
  Requires-Dist: rich>=14.3.0
30
30
  Requires-Dist: rich-argparse>=1.7.1
31
+ Requires-Dist: typing-extensions; python_version == "3.10"
31
32
  Dynamic: license-file
32
33
 
33
34
  <h1 align="center">cmd2 : immersive interactive command line applications</h1>
@@ -539,7 +539,7 @@ class ArgparseCompleter:
539
539
  # Check if there are too many CompletionItems to display as a table
540
540
  if len(completions) <= self._cmd2_app.max_completion_items:
541
541
  # If a metavar was defined, use that instead of the dest field
542
- destination = arg_state.action.metavar if arg_state.action.metavar else arg_state.action.dest
542
+ destination = arg_state.action.metavar or arg_state.action.dest
543
543
 
544
544
  # Handle case where metavar was a tuple
545
545
  if isinstance(destination, tuple):
@@ -587,7 +587,7 @@ class ArgparseCompleter:
587
587
  # Generate the hint table string
588
588
  console = Cmd2GeneralConsole()
589
589
  with console.capture() as capture:
590
- console.print(hint_table, end="")
590
+ console.print(hint_table, end="", soft_wrap=False)
591
591
  self._cmd2_app.formatted_completions = capture.get()
592
592
 
593
593
  # Return sorted list of completions
@@ -290,7 +290,12 @@ from rich_argparse import (
290
290
  RawTextRichHelpFormatter,
291
291
  RichHelpFormatter,
292
292
  )
293
- from typing_extensions import Self
293
+
294
+ if sys.version_info >= (3, 11):
295
+ from typing import Self
296
+ else:
297
+ from typing_extensions import Self
298
+
294
299
 
295
300
  from . import constants
296
301
  from . import rich_utils as ru
@@ -1472,7 +1477,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
1472
1477
  usage=usage,
1473
1478
  description=description, # type: ignore[arg-type]
1474
1479
  epilog=epilog, # type: ignore[arg-type]
1475
- parents=parents if parents else [],
1480
+ parents=parents or [],
1476
1481
  formatter_class=formatter_class,
1477
1482
  prefix_chars=prefix_chars,
1478
1483
  fromfile_prefix_chars=fromfile_prefix_chars,
@@ -593,11 +593,14 @@ class Cmd:
593
593
  # being printed by a command.
594
594
  self.terminal_lock = threading.RLock()
595
595
 
596
- # Commands that have been disabled from use. This is to support commands that are only available
597
- # during specific states of the application. This dictionary's keys are the command names and its
598
- # values are DisabledCommand objects.
596
+ # Commands disabled during specific application states
597
+ # Key: Command name | Value: DisabledCommand object
599
598
  self.disabled_commands: dict[str, DisabledCommand] = {}
600
599
 
600
+ # Categories of commands to be disabled
601
+ # Key: Category name | Value: Message to display
602
+ self.disabled_categories: dict[str, str] = {}
603
+
601
604
  # The default key for sorting string results. Its default value performs a case-insensitive alphabetical sort.
602
605
  # If natural sorting is preferred, then set this to NATURAL_SORT_KEY.
603
606
  # cmd2 uses this key for sorting:
@@ -755,9 +758,11 @@ class Cmd:
755
758
  list[tuple[str, Callable[..., Any]]],
756
759
  inspect.getmembers(
757
760
  cmdset,
758
- predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
759
- and hasattr(meth, '__name__')
760
- and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
761
+ predicate=lambda meth: ( # type: ignore[arg-type]
762
+ isinstance(meth, Callable) # type: ignore[arg-type]
763
+ and hasattr(meth, '__name__')
764
+ and meth.__name__.startswith(COMMAND_FUNC_PREFIX)
765
+ ),
761
766
  ),
762
767
  )
763
768
 
@@ -788,6 +793,12 @@ class Cmd:
788
793
  if default_category and not hasattr(command_method, constants.CMD_ATTR_HELP_CATEGORY):
789
794
  utils.categorize(command_method, default_category)
790
795
 
796
+ # If this command is in a disabled category, then disable it
797
+ command_category = getattr(command_method, constants.CMD_ATTR_HELP_CATEGORY, None)
798
+ if command_category in self.disabled_categories:
799
+ message_to_print = self.disabled_categories[command_category]
800
+ self.disable_command(command, message_to_print)
801
+
791
802
  self._installed_command_sets.add(cmdset)
792
803
 
793
804
  self._register_subcommands(cmdset)
@@ -904,9 +915,11 @@ class Cmd:
904
915
 
905
916
  methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers(
906
917
  cmdset,
907
- predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
908
- and hasattr(meth, '__name__')
909
- and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
918
+ predicate=lambda meth: ( # type: ignore[arg-type]
919
+ isinstance(meth, Callable) # type: ignore[arg-type]
920
+ and hasattr(meth, '__name__')
921
+ and meth.__name__.startswith(COMMAND_FUNC_PREFIX)
922
+ ),
910
923
  )
911
924
 
912
925
  for cmd_func_name, command_method in methods:
@@ -950,9 +963,11 @@ class Cmd:
950
963
 
951
964
  methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers(
952
965
  cmdset,
953
- predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
954
- and hasattr(meth, '__name__')
955
- and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
966
+ predicate=lambda meth: ( # type: ignore[arg-type]
967
+ isinstance(meth, Callable) # type: ignore[arg-type]
968
+ and hasattr(meth, '__name__')
969
+ and meth.__name__.startswith(COMMAND_FUNC_PREFIX)
970
+ ),
956
971
  )
957
972
 
958
973
  for cmd_func_name, command_method in methods:
@@ -974,10 +989,12 @@ class Cmd:
974
989
  # find methods that have the required attributes necessary to be recognized as a sub-command
975
990
  methods = inspect.getmembers(
976
991
  cmdset,
977
- predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
978
- and hasattr(meth, constants.SUBCMD_ATTR_NAME)
979
- and hasattr(meth, constants.SUBCMD_ATTR_COMMAND)
980
- and hasattr(meth, constants.CMD_ATTR_ARGPARSER),
992
+ predicate=lambda meth: (
993
+ isinstance(meth, Callable) # type: ignore[arg-type]
994
+ and hasattr(meth, constants.SUBCMD_ATTR_NAME)
995
+ and hasattr(meth, constants.SUBCMD_ATTR_COMMAND)
996
+ and hasattr(meth, constants.CMD_ATTR_ARGPARSER)
997
+ ),
981
998
  )
982
999
 
983
1000
  # iterate through all matching methods
@@ -1063,10 +1080,12 @@ class Cmd:
1063
1080
  # find methods that have the required attributes necessary to be recognized as a sub-command
1064
1081
  methods = inspect.getmembers(
1065
1082
  cmdset,
1066
- predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
1067
- and hasattr(meth, constants.SUBCMD_ATTR_NAME)
1068
- and hasattr(meth, constants.SUBCMD_ATTR_COMMAND)
1069
- and hasattr(meth, constants.CMD_ATTR_ARGPARSER),
1083
+ predicate=lambda meth: (
1084
+ isinstance(meth, Callable) # type: ignore[arg-type]
1085
+ and hasattr(meth, constants.SUBCMD_ATTR_NAME)
1086
+ and hasattr(meth, constants.SUBCMD_ATTR_COMMAND)
1087
+ and hasattr(meth, constants.CMD_ATTR_ARGPARSER)
1088
+ ),
1070
1089
  )
1071
1090
 
1072
1091
  # iterate through all matching methods
@@ -1250,8 +1269,14 @@ class Cmd:
1250
1269
  :param sep: string to write between printed text. Defaults to " ".
1251
1270
  :param end: string to write at end of printed text. Defaults to a newline.
1252
1271
  :param style: optional style to apply to output
1253
- :param soft_wrap: Enable soft wrap mode. If True, lines of text will not be
1254
- word-wrapped or cropped to fit the terminal width. Defaults to True.
1272
+ :param soft_wrap: Enable soft wrap mode. Defaults to True.
1273
+ If True, text that doesn't fit will run on to the following line,
1274
+ just like with print(). This is useful for raw text and logs.
1275
+ If False, Rich wraps text to fit the terminal width.
1276
+ Set this to False when printing structured Renderables like
1277
+ Tables, Panels, or Columns to ensure they render as expected.
1278
+ For example, when soft_wrap is True Panels truncate text
1279
+ which is wider than the terminal.
1255
1280
  :param emoji: If True, Rich will replace emoji codes (e.g., :smiley:) with their
1256
1281
  corresponding Unicode characters. Defaults to False.
1257
1282
  :param markup: If True, Rich will interpret strings with tags (e.g., [bold]hello[/bold])
@@ -1751,31 +1776,45 @@ class Cmd:
1751
1776
  :return: a list of possible tab completions
1752
1777
  """
1753
1778
  matches = self.basic_complete(text, line, begidx, endidx, match_against)
1779
+ if not matches:
1780
+ return []
1754
1781
 
1755
- # Display only the portion of the match that's being completed based on delimiter
1756
- if matches:
1757
- # Set this to True for proper quoting of matches with spaces
1758
- self.matches_delimited = True
1782
+ # Set this to True for proper quoting of matches with spaces
1783
+ self.matches_delimited = True
1759
1784
 
1760
- # Get the common beginning for the matches
1761
- common_prefix = os.path.commonprefix(matches)
1762
- prefix_tokens = common_prefix.split(delimiter)
1785
+ # Get the common beginning for the matches
1786
+ common_prefix = os.path.commonprefix(matches)
1787
+ prefix_tokens = common_prefix.split(delimiter)
1763
1788
 
1764
- # Calculate what portion of the match we are completing
1765
- display_token_index = 0
1766
- if prefix_tokens:
1767
- display_token_index = len(prefix_tokens) - 1
1789
+ # Calculate what portion of the match we are completing
1790
+ display_token_index = 0
1791
+ if prefix_tokens:
1792
+ display_token_index = len(prefix_tokens) - 1
1768
1793
 
1769
- # Get this portion for each match and store them in self.display_matches
1770
- for cur_match in matches:
1771
- match_tokens = cur_match.split(delimiter)
1772
- display_token = match_tokens[display_token_index]
1794
+ # Remove from each match everything after where the user is completing.
1795
+ # This approach can result in duplicates so we will filter those out.
1796
+ unique_results: dict[str, str] = {}
1773
1797
 
1774
- if not display_token:
1775
- display_token = delimiter
1776
- self.display_matches.append(display_token)
1798
+ for cur_match in matches:
1799
+ match_tokens = cur_match.split(delimiter)
1777
1800
 
1778
- return matches
1801
+ filtered_match = delimiter.join(match_tokens[: display_token_index + 1])
1802
+ display_match = match_tokens[display_token_index]
1803
+
1804
+ # If there are more tokens, then we aren't done completing a full item
1805
+ if len(match_tokens) > display_token_index + 1:
1806
+ filtered_match += delimiter
1807
+ display_match += delimiter
1808
+ self.allow_appended_space = False
1809
+ self.allow_closing_quote = False
1810
+
1811
+ if filtered_match not in unique_results:
1812
+ unique_results[filtered_match] = display_match
1813
+
1814
+ filtered_matches = list(unique_results.keys())
1815
+ self.display_matches = list(unique_results.values())
1816
+
1817
+ return filtered_matches
1779
1818
 
1780
1819
  def flag_based_complete(
1781
1820
  self,
@@ -2233,7 +2272,7 @@ class Cmd:
2233
2272
  # Otherwise use pyreadline3's formatter
2234
2273
  else:
2235
2274
  # Check if we should show display_matches
2236
- matches_to_display = self.display_matches if self.display_matches else matches
2275
+ matches_to_display = self.display_matches or matches
2237
2276
 
2238
2277
  # Add padding for visual appeal
2239
2278
  matches_to_display, _ = self._pad_matches_to_display(matches_to_display)
@@ -4236,7 +4275,7 @@ class Cmd:
4236
4275
  header_grid = Table.grid()
4237
4276
  header_grid.add_row(Text(header, style=Cmd2Style.HELP_HEADER))
4238
4277
  header_grid.add_row(Rule(characters=self.ruler, style=Cmd2Style.TABLE_BORDER))
4239
- self.poutput(header_grid)
4278
+ self.poutput(header_grid, soft_wrap=False)
4240
4279
 
4241
4280
  # Subtract 1 from maxcol to account for a one-space right margin.
4242
4281
  maxcol = min(maxcol, ru.console_width()) - 1
@@ -4304,7 +4343,7 @@ class Cmd:
4304
4343
  topics_table.add_row(command, cmd_desc)
4305
4344
 
4306
4345
  category_grid.add_row(topics_table)
4307
- self.poutput(category_grid)
4346
+ self.poutput(category_grid, soft_wrap=False)
4308
4347
  self.poutput()
4309
4348
 
4310
4349
  def render_columns(self, str_list: list[str] | None, display_width: int = 80) -> str:
@@ -4601,7 +4640,7 @@ class Cmd:
4601
4640
  self.last_result[param] = settable.value
4602
4641
 
4603
4642
  self.poutput()
4604
- self.poutput(settable_table)
4643
+ self.poutput(settable_table, soft_wrap=False)
4605
4644
  self.poutput()
4606
4645
 
4607
4646
  @classmethod
@@ -5799,7 +5838,7 @@ class Cmd:
5799
5838
 
5800
5839
  :param command: the command being enabled
5801
5840
  """
5802
- # If the commands is already enabled, then return
5841
+ # If the command is already enabled, then return
5803
5842
  if command not in self.disabled_commands:
5804
5843
  return
5805
5844
 
@@ -5831,11 +5870,17 @@ class Cmd:
5831
5870
 
5832
5871
  :param category: the category to enable
5833
5872
  """
5873
+ # If the category is already enabled, then return
5874
+ if category not in self.disabled_categories:
5875
+ return
5876
+
5834
5877
  for cmd_name in list(self.disabled_commands):
5835
5878
  func = self.disabled_commands[cmd_name].command_function
5836
5879
  if getattr(func, constants.CMD_ATTR_HELP_CATEGORY, None) == category:
5837
5880
  self.enable_command(cmd_name)
5838
5881
 
5882
+ del self.disabled_categories[category]
5883
+
5839
5884
  def disable_command(self, command: str, message_to_print: str) -> None:
5840
5885
  """Disable a command and overwrite its functions.
5841
5886
 
@@ -5846,7 +5891,7 @@ class Cmd:
5846
5891
  command being disabled.
5847
5892
  ex: message_to_print = f"{cmd2.COMMAND_NAME} is currently disabled"
5848
5893
  """
5849
- # If the commands is already disabled, then return
5894
+ # If the command is already disabled, then return
5850
5895
  if command in self.disabled_commands:
5851
5896
  return
5852
5897
 
@@ -5885,6 +5930,10 @@ class Cmd:
5885
5930
  of the command being disabled.
5886
5931
  ex: message_to_print = f"{cmd2.COMMAND_NAME} is currently disabled"
5887
5932
  """
5933
+ # If the category is already disabled, then return
5934
+ if category in self.disabled_categories:
5935
+ return
5936
+
5888
5937
  all_commands = self.get_all_commands()
5889
5938
 
5890
5939
  for cmd_name in all_commands:
@@ -5892,6 +5941,8 @@ class Cmd:
5892
5941
  if getattr(func, constants.CMD_ATTR_HELP_CATEGORY, None) == category:
5893
5942
  self.disable_command(cmd_name, message_to_print)
5894
5943
 
5944
+ self.disabled_categories[category] = message_to_print
5945
+
5895
5946
  def _report_disabled_command_usage(self, *_args: Any, message_to_print: str, **_kwargs: Any) -> None:
5896
5947
  """Report when a disabled command has been run or had help called on it.
5897
5948
 
@@ -63,9 +63,11 @@ def with_default_category(category: str, *, heritable: bool = True) -> Callable[
63
63
  # 3. Must be a member of the class being decorated and not one inherited from a parent declaration
64
64
  methods = inspect.getmembers(
65
65
  cls,
66
- predicate=lambda meth: inspect.isfunction(meth)
67
- and meth.__name__.startswith(COMMAND_FUNC_PREFIX)
68
- and meth in inspect.getmro(cls)[0].__dict__.values(),
66
+ predicate=lambda meth: (
67
+ inspect.isfunction(meth)
68
+ and meth.__name__.startswith(COMMAND_FUNC_PREFIX)
69
+ and meth in inspect.getmro(cls)[0].__dict__.values()
70
+ ),
69
71
  )
70
72
  category_decorator = with_category(category)
71
73
  for method in methods:
@@ -2,6 +2,7 @@
2
2
 
3
3
  import re
4
4
  import shlex
5
+ import sys
5
6
  from collections.abc import Iterable
6
7
  from dataclasses import (
7
8
  dataclass,
@@ -9,7 +10,10 @@ from dataclasses import (
9
10
  )
10
11
  from typing import Any
11
12
 
12
- from typing_extensions import Self
13
+ if sys.version_info >= (3, 11):
14
+ from typing import Self
15
+ else:
16
+ from typing_extensions import Self
13
17
 
14
18
  from . import (
15
19
  constants,
@@ -1,7 +1,6 @@
1
1
  """Shared utility functions."""
2
2
 
3
3
  import argparse
4
- import collections
5
4
  import contextlib
6
5
  import functools
7
6
  import glob
@@ -192,10 +191,7 @@ def remove_duplicates(list_to_prune: list[_T]) -> list[_T]:
192
191
  :param list_to_prune: the list being pruned of duplicates
193
192
  :return: The pruned list
194
193
  """
195
- temp_dict: collections.OrderedDict[_T, Any] = collections.OrderedDict()
196
- for item in list_to_prune:
197
- temp_dict[item] = None
198
-
194
+ temp_dict = dict.fromkeys(list_to_prune)
199
195
  return list(temp_dict.keys())
200
196
 
201
197
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmd2
3
- Version: 3.1.1
3
+ Version: 3.1.3
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
@@ -28,6 +28,7 @@ Requires-Dist: pyperclip>=1.8.2
28
28
  Requires-Dist: pyreadline3>=3.4; platform_system == "Windows"
29
29
  Requires-Dist: rich>=14.3.0
30
30
  Requires-Dist: rich-argparse>=1.7.1
31
+ Requires-Dist: typing-extensions; python_version == "3.10"
31
32
  Dynamic: license-file
32
33
 
33
34
  <h1 align="center">cmd2 : immersive interactive command line applications</h1>
@@ -10,3 +10,4 @@ pyreadline3>=3.4
10
10
 
11
11
  [:python_version == "3.10"]
12
12
  backports.strenum
13
+ typing-extensions
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "devDependencies": {
3
- "prettier": "^3.8.0",
3
+ "prettier": "^3.8.1",
4
4
  "prettier-plugin-toml": "^2.0.6"
5
5
  }
6
6
  }
@@ -35,6 +35,7 @@ dependencies = [
35
35
  "pyreadline3>=3.4; platform_system == 'Windows'",
36
36
  "rich>=14.3.0",
37
37
  "rich-argparse>=1.7.1",
38
+ "typing-extensions; python_version == '3.10'",
38
39
  ]
39
40
 
40
41
  [dependency-groups]
@@ -20,6 +20,7 @@ from cmd2 import (
20
20
  COMMAND_NAME,
21
21
  Cmd2Style,
22
22
  Color,
23
+ CommandSet,
23
24
  RichPrintKwargs,
24
25
  clipboard,
25
26
  constants,
@@ -3106,6 +3107,16 @@ class DisableCommandsApp(cmd2.Cmd):
3106
3107
  self.poutput("The real has_no_helper_funcs")
3107
3108
 
3108
3109
 
3110
+ class DisableCommandSet(CommandSet):
3111
+ """Test registering a command which is in a disabled category"""
3112
+
3113
+ category_name = "CommandSet Test Category"
3114
+
3115
+ @cmd2.with_category(category_name)
3116
+ def do_new_command(self, arg) -> None:
3117
+ self._cmd.poutput("CommandSet function is enabled")
3118
+
3119
+
3109
3120
  @pytest.fixture
3110
3121
  def disable_commands_app():
3111
3122
  return DisableCommandsApp()
@@ -3209,7 +3220,7 @@ def test_enable_enabled_command(disable_commands_app) -> None:
3209
3220
  saved_len = len(disable_commands_app.disabled_commands)
3210
3221
  disable_commands_app.enable_command('has_helper_funcs')
3211
3222
 
3212
- # The number of disabled_commands should not have changed
3223
+ # The number of disabled commands should not have changed
3213
3224
  assert saved_len == len(disable_commands_app.disabled_commands)
3214
3225
 
3215
3226
 
@@ -3223,7 +3234,7 @@ def test_disable_command_twice(disable_commands_app) -> None:
3223
3234
  message_to_print = 'These commands are currently disabled'
3224
3235
  disable_commands_app.disable_command('has_helper_funcs', message_to_print)
3225
3236
 
3226
- # The length of disabled_commands should have increased one
3237
+ # The number of disabled commands should have increased one
3227
3238
  new_len = len(disable_commands_app.disabled_commands)
3228
3239
  assert saved_len == new_len - 1
3229
3240
  saved_len = new_len
@@ -3251,6 +3262,50 @@ def test_disabled_message_command_name(disable_commands_app) -> None:
3251
3262
  assert err[0].startswith('has_helper_funcs is currently disabled')
3252
3263
 
3253
3264
 
3265
+ def test_register_command_in_enabled_category(disable_commands_app) -> None:
3266
+ disable_commands_app.enable_category(DisableCommandSet.category_name)
3267
+ cs = DisableCommandSet()
3268
+ disable_commands_app.register_command_set(cs)
3269
+
3270
+ out, _err = run_cmd(disable_commands_app, 'new_command')
3271
+ assert out[0] == "CommandSet function is enabled"
3272
+
3273
+
3274
+ def test_register_command_in_disabled_category(disable_commands_app) -> None:
3275
+ message_to_print = "CommandSet function is disabled"
3276
+ disable_commands_app.disable_category(DisableCommandSet.category_name, message_to_print)
3277
+ cs = DisableCommandSet()
3278
+ disable_commands_app.register_command_set(cs)
3279
+
3280
+ _out, err = run_cmd(disable_commands_app, 'new_command')
3281
+ assert err[0] == message_to_print
3282
+
3283
+
3284
+ def test_enable_enabled_category(disable_commands_app) -> None:
3285
+ # Test enabling a category that is not disabled
3286
+ saved_len = len(disable_commands_app.disabled_categories)
3287
+ disable_commands_app.enable_category('Test Category')
3288
+
3289
+ # The number of disabled categories should not have changed
3290
+ assert saved_len == len(disable_commands_app.disabled_categories)
3291
+
3292
+
3293
+ def test_disable_category_twice(disable_commands_app) -> None:
3294
+ saved_len = len(disable_commands_app.disabled_categories)
3295
+ message_to_print = 'These commands are currently disabled'
3296
+ disable_commands_app.disable_category('Test Category', message_to_print)
3297
+
3298
+ # The number of disabled categories should have increased one
3299
+ new_len = len(disable_commands_app.disabled_categories)
3300
+ assert saved_len == new_len - 1
3301
+ saved_len = new_len
3302
+
3303
+ # Disable again and the length should not change
3304
+ disable_commands_app.disable_category('Test Category', message_to_print)
3305
+ new_len = len(disable_commands_app.disabled_categories)
3306
+ assert saved_len == new_len
3307
+
3308
+
3254
3309
  @pytest.mark.parametrize('silence_startup_script', [True, False])
3255
3310
  def test_startup_script(request, capsys, silence_startup_script) -> None:
3256
3311
  test_dir = os.path.dirname(request.module.__file__)
@@ -716,19 +716,53 @@ def test_basic_completion_nomatch(cmd2_app) -> None:
716
716
  assert cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs) == []
717
717
 
718
718
 
719
- def test_delimiter_completion(cmd2_app) -> None:
719
+ def test_delimiter_completion_partial(cmd2_app) -> None:
720
+ """Test that a delimiter is added when an item has not been fully completed"""
720
721
  text = '/home/'
721
- line = f'run_script {text}'
722
+ line = f'command {text}'
723
+ endidx = len(line)
724
+ begidx = endidx - len(text)
725
+
726
+ matches = cmd2_app.delimiter_complete(text, line, begidx, endidx, delimited_strs, '/')
727
+
728
+ # All matches end with the delimiter
729
+ matches.sort(key=cmd2_app.default_sort_key)
730
+ expected_matches = sorted(["/home/other user/", "/home/user/"], key=cmd2_app.default_sort_key)
731
+
732
+ cmd2_app.display_matches.sort(key=cmd2_app.default_sort_key)
733
+ expected_display = sorted(["other user/", "user/"], key=cmd2_app.default_sort_key)
734
+
735
+ assert matches == expected_matches
736
+ assert cmd2_app.display_matches == expected_display
737
+
738
+
739
+ def test_delimiter_completion_full(cmd2_app) -> None:
740
+ """Test that no delimiter is added when an item has been fully completed"""
741
+ text = '/home/other user/'
742
+ line = f'command {text}'
722
743
  endidx = len(line)
723
744
  begidx = endidx - len(text)
724
745
 
725
- cmd2_app.delimiter_complete(text, line, begidx, endidx, delimited_strs, '/')
746
+ matches = cmd2_app.delimiter_complete(text, line, begidx, endidx, delimited_strs, '/')
747
+
748
+ # No matches end with the delimiter
749
+ matches.sort(key=cmd2_app.default_sort_key)
750
+ expected_matches = sorted(["/home/other user/maps", "/home/other user/tests"], key=cmd2_app.default_sort_key)
726
751
 
727
- # Remove duplicates from display_matches and sort it. This is typically done in complete().
728
- display_list = utils.remove_duplicates(cmd2_app.display_matches)
729
- display_list = utils.alphabetical_sort(display_list)
752
+ cmd2_app.display_matches.sort(key=cmd2_app.default_sort_key)
753
+ expected_display = sorted(["maps", "tests"], key=cmd2_app.default_sort_key)
730
754
 
731
- assert display_list == ['other user', 'user']
755
+ assert matches == expected_matches
756
+ assert cmd2_app.display_matches == expected_display
757
+
758
+
759
+ def test_delimiter_completion_nomatch(cmd2_app) -> None:
760
+ text = '/nothing_to_see'
761
+ line = f'command {text}'
762
+ endidx = len(line)
763
+ begidx = endidx - len(text)
764
+
765
+ assert cmd2_app.delimiter_complete(text, line, begidx, endidx, delimited_strs, '/') == []
732
766
 
733
767
 
734
768
  def test_flag_based_completion_single(cmd2_app) -> None:
@@ -964,20 +998,24 @@ def test_add_opening_quote_delimited_no_text(cmd2_app) -> None:
964
998
  endidx = len(line)
965
999
  begidx = endidx - len(text)
966
1000
 
967
- # The whole list will be returned with no opening quotes added
1001
+ # Matches returned with no opening quote
1002
+ expected_matches = sorted(["/home/other user/", "/home/user/"], key=cmd2_app.default_sort_key)
1003
+ expected_display = sorted(["other user/", "user/"], key=cmd2_app.default_sort_key)
1004
+
968
1005
  first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
969
1006
  assert first_match is not None
970
- assert cmd2_app.completion_matches == sorted(delimited_strs, key=cmd2_app.default_sort_key)
1007
+ assert cmd2_app.completion_matches == expected_matches
1008
+ assert cmd2_app.display_matches == expected_display
971
1009
 
972
1010
 
973
1011
  def test_add_opening_quote_delimited_nothing_added(cmd2_app) -> None:
974
- text = '/ho'
1012
+ text = '/home/'
975
1013
  line = f'test_delimited {text}'
976
1014
  endidx = len(line)
977
1015
  begidx = endidx - len(text)
978
1016
 
979
- expected_matches = sorted(delimited_strs, key=cmd2_app.default_sort_key)
980
- expected_display = sorted(['other user', 'user'], key=cmd2_app.default_sort_key)
1017
+ expected_matches = sorted(['/home/other user/', '/home/user/'], key=cmd2_app.default_sort_key)
1018
+ expected_display = sorted(['other user/', 'user/'], key=cmd2_app.default_sort_key)
981
1019
 
982
1020
  first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
983
1021
  assert first_match is not None
@@ -1017,7 +1055,7 @@ def test_add_opening_quote_delimited_text_is_common_prefix(cmd2_app) -> None:
1017
1055
 
1018
1056
 
1019
1057
  def test_add_opening_quote_delimited_space_in_prefix(cmd2_app) -> None:
1020
- # This test when a space appears before the part of the string that is the display match
1058
+ # This tests when a space appears before the part of the string that is the display match
1021
1059
  text = '/home/oth'
1022
1060
  line = f'test_delimited {text}'
1023
1061
  endidx = len(line)
File without changes