invoke-toolkit 0.0.62__tar.gz → 0.0.63__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 (127) hide show
  1. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/PKG-INFO +2 -2
  2. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/pyproject.toml +1 -1
  3. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/context/context.py +4 -2
  4. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/executor.py +8 -3
  5. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/tasks/tasks.py +11 -4
  6. invoke_toolkit-0.0.63/tests/test_invoke_3_compat.py +184 -0
  7. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/.gitignore +0 -0
  8. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/LICENSE.txt +0 -0
  9. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/README.md +0 -0
  10. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/__init__.py +0 -0
  11. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/__main__.py +0 -0
  12. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/collections.py +0 -0
  13. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/completion.py +0 -0
  14. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/config/__init__.py +0 -0
  15. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/config/config.py +0 -0
  16. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/config/registry.py +0 -0
  17. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/config/schema.py +0 -0
  18. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/config/status_helper.py +0 -0
  19. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/context/__init__.py +0 -0
  20. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/context/types.py +0 -0
  21. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/extensions/__init__.py +0 -0
  22. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/extensions/tasks/__init__.py +0 -0
  23. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/extensions/tasks/config.py +0 -0
  24. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/extensions/tasks/create.py +0 -0
  25. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/extensions/tasks/dist.py +0 -0
  26. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/extensions/tasks/shell.py +0 -0
  27. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/loader/entrypoint.py +0 -0
  28. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/log/__init__.py +0 -0
  29. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/log/logger.py +0 -0
  30. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/output/__init__.py +0 -0
  31. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/output/console.py +0 -0
  32. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/output/utils.py +0 -0
  33. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/parser.py +0 -0
  34. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/program/__init__.py +0 -0
  35. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/program/main.py +0 -0
  36. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/program/program.py +0 -0
  37. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/py.typed +0 -0
  38. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/runners/__init__.py +0 -0
  39. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/runners/rich.py +0 -0
  40. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/scripts/__init__.py +0 -0
  41. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/scripts/loader.py +0 -0
  42. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/tasks/__init__.py +0 -0
  43. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/tasks/autocomplete.py +0 -0
  44. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/tasks/cache.py +0 -0
  45. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/tasks/types.py +0 -0
  46. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/testing.py +0 -0
  47. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/utils/__init__.py +0 -0
  48. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/utils/fzf.py +0 -0
  49. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/utils/inspection.py +0 -0
  50. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/utils/singleton.py +0 -0
  51. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/src/invoke_toolkit/utils/text.py +0 -0
  52. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/templates/package-template/.gitignore.jinja +0 -0
  53. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/templates/package-template/README.md.jinja +0 -0
  54. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/templates/package-template/copier.yml +0 -0
  55. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/templates/package-template/pyproject.toml.jinja +0 -0
  56. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/templates/package-template/src/{{package_slug}}/__init__.py.jinja +0 -0
  57. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/templates/package-template/src/{{package_slug}}/tasks.py.jinja +0 -0
  58. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/__init__.py +0 -0
  59. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/conftest.py +0 -0
  60. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/examples/cached_completion/tasks.py +0 -0
  61. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/examples/completion_with_config/.gitignore +0 -0
  62. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/examples/completion_with_config/tasks.py +0 -0
  63. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/examples/config_schema/tasks.py +0 -0
  64. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/examples/enum_select_size/tasks.py +0 -0
  65. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/examples/fzf_selector/tasks.py +0 -0
  66. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/examples/literal_set_level/tasks.py +0 -0
  67. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/extensions/conftest.py +0 -0
  68. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/extensions/test_config_tasks.py +0 -0
  69. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/extensions/test_create.py +0 -0
  70. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/extensions/test_package_template_entrypoint.py +0 -0
  71. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/extensions/test_shell_tasks.py +0 -0
  72. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/__init__.py +0 -0
  73. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/all-four/invoke.json +0 -0
  74. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/all-four/invoke.py +0 -0
  75. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/all-four/invoke.yml +0 -0
  76. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/collection.py +0 -0
  77. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/echo.yaml +0 -0
  78. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/json/invoke.json +0 -0
  79. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/json-and-python/invoke.json +0 -0
  80. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/json-and-python/invoke.py +0 -0
  81. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/no-dedupe.yaml +0 -0
  82. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/no-echo.yaml +0 -0
  83. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/package/invoke.yml +0 -0
  84. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/package/tasks/__init__.py +0 -0
  85. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/python/invoke.py +0 -0
  86. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/runtime.py +0 -0
  87. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/three-of-em/invoke.json +0 -0
  88. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/three-of-em/invoke.py +0 -0
  89. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/three-of-em/invoke.yml +0 -0
  90. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/underscores/tasks.py +0 -0
  91. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/yaml/explicit.py +0 -0
  92. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/yaml/tasks.py +0 -0
  93. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/yml/explicit.py +0 -0
  94. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/yml/invoke.yml +0 -0
  95. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/configs/yml/tasks.py +0 -0
  96. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/_support/has_modules.py +0 -0
  97. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/conftest.py +0 -0
  98. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/original_invoke/test_config.py +0 -0
  99. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/program/main.py +0 -0
  100. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/program/tasks/__init__.py +0 -0
  101. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/program/tasks/coll1.py +0 -0
  102. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/script/test_script.py +0 -0
  103. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/tasks/test_cache.py +0 -0
  104. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/tasks/test_extensions_config.py +0 -0
  105. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_annotated_help.py +0 -0
  106. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_cofig_class.py +0 -0
  107. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_collection.py +0 -0
  108. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_collection_configure.py +0 -0
  109. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_completion_with_choices.py +0 -0
  110. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_config_helper.py +0 -0
  111. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_config_registry.py +0 -0
  112. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_config_schema.py +0 -0
  113. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_console.py +0 -0
  114. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_context_class.py +0 -0
  115. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_disable_status_cli.py +0 -0
  116. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_enum_arguments.py +0 -0
  117. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_executor.py +0 -0
  118. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_file_completion.py +0 -0
  119. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_global_context.py +0 -0
  120. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_help_flags.py +0 -0
  121. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_invoke_compatibility.py +0 -0
  122. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_loader.py +0 -0
  123. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_parsing.py +0 -0
  124. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_proctitle.py +0 -0
  125. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_program_with_collection.py +0 -0
  126. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/test_toplevel.py +0 -0
  127. {invoke_toolkit-0.0.62 → invoke_toolkit-0.0.63}/tests/utils/test_fzf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invoke-toolkit
3
- Version: 0.0.62
3
+ Version: 0.0.63
4
4
  Summary: Extended APIs for PyInvoke: composable task plugins, typed CLI args, Rich output, shell completion, and PEP 723 script support
5
5
  Project-URL: Documentation, https://github.com/D3f0/invoke-toolkit#readme
6
6
  Project-URL: Issues, https://github.com/D3f0/invoke-toolkit/issues
@@ -36,7 +36,7 @@ Requires-Dist: appdirs
36
36
  Requires-Dist: attrs>=24.1.0
37
37
  Requires-Dist: cattrs>=24.1.0
38
38
  Requires-Dist: httpx>=0.28.1
39
- Requires-Dist: invoke>=2.2.1
39
+ Requires-Dist: invoke>=3.0.3
40
40
  Requires-Dist: platformdirs>=4.5.0
41
41
  Requires-Dist: pyyaml>=6.0.3
42
42
  Requires-Dist: rich>=13
@@ -45,7 +45,7 @@ classifiers = [
45
45
  "Typing :: Typed",
46
46
  ]
47
47
  dependencies = [
48
- "invoke>=2.2.1",
48
+ "invoke>=3.0.3",
49
49
  "rich>=13",
50
50
  "typing-extensions>=4.13.2",
51
51
  "appdirs",
@@ -70,8 +70,10 @@ class ToolkitContext(Context, ConfigProtocol):
70
70
  [Union[PathLike[str], str]], _GeneratorContextManager[None, None, None]
71
71
  ]
72
72
 
73
- def __init__(self, config: Optional[ToolkitConfig] = None) -> None:
74
- super().__init__(config)
73
+ def __init__(
74
+ self, config: Optional[ToolkitConfig] = None, remainder: str = ""
75
+ ) -> None:
76
+ super().__init__(config, remainder=remainder)
75
77
  self._set("_console", get_console())
76
78
  # Check if status is disabled in the config
77
79
  disabled = False
@@ -39,11 +39,16 @@ class ToolkitExecutor(Executor):
39
39
 
40
40
  :param core:
41
41
  An optional `.ParseResult` holding parsed core program arguments.
42
- Defaults to ``None``.
42
+ Defaults to an empty `.ParseResult` if not given.
43
+
44
+ .. versionchanged:: invoke 3.0
45
+ ``self.core`` now defaults to ``ParseResult()`` instead of
46
+ ``None``, matching upstream ``Executor`` behaviour and ensuring
47
+ ``core_parse_result.remainder`` is always accessible.
43
48
  """
44
49
  self.collection = collection
45
50
  self.config = config if config is not None else ToolkitConfig()
46
- self.core = core
51
+ self.core = core if core is not None else ParseResult()
47
52
 
48
53
  def execute(
49
54
  self, *tasks: Union[str, Tuple[str, Dict[str, Any]], ParserContext]
@@ -134,7 +139,7 @@ class ToolkitExecutor(Executor):
134
139
  # Get final context from the Call (which will know how to generate
135
140
  # an appropriate one; e.g. subclasses might use extra data from
136
141
  # being parameterized), handing in this config for use there.
137
- context = call.make_context(config) # type: ignore[attr-defined]
142
+ context = call.make_context(config, core_parse_result=self.core) # type: ignore[attr-defined]
138
143
  args = (context, *call.args) # type: ignore[attr-defined]
139
144
  result = call.task(*args, **call.kwargs) # type: ignore[attr-defined]
140
145
  if autoprint:
@@ -2,7 +2,7 @@
2
2
  Type annotated tasks and and overrides over invoke
3
3
  """
4
4
 
5
- # pylint: disable=too-many-statements
5
+ # pylint: disable=too-many-statements,duplicate-code
6
6
 
7
7
  import inspect
8
8
  import os
@@ -822,9 +822,16 @@ def task( # pylint: disable=too-many-arguments,too-many-branches
822
822
 
823
823
 
824
824
  class ToolkitCall(Call):
825
- def make_context(self, config):
826
- """Generates the Context for the task"""
827
- return ToolkitContext(config=config)
825
+ def make_context(self, config, core_parse_result=None):
826
+ """Generates the Context for the task.
827
+
828
+ .. versionchanged:: invoke 3.0
829
+ Added the ``core_parse_result`` parameter so that
830
+ ``Context.remainder`` is populated from the CLI parser's remainder
831
+ value (text after a standalone ``--``).
832
+ """
833
+ remainder = core_parse_result.remainder if core_parse_result is not None else ""
834
+ return ToolkitContext(config=config, remainder=remainder)
828
835
 
829
836
 
830
837
  def call(task_: "Task", *args: Any, **kwargs: Any) -> "Call":
@@ -0,0 +1,184 @@
1
+ """
2
+ Tests for invoke 3.0.x compatibility.
3
+
4
+ invoke 3.0 introduced two related breaking changes that affect invoke-toolkit:
5
+
6
+ 1. ``Call.make_context`` now requires a second argument ``core_parse_result``
7
+ (a ``ParseResult`` instance) so that ``Context.remainder`` can be populated
8
+ from the CLI parser's remainder value (text after a standalone ``--``).
9
+
10
+ 2. ``Context.__init__`` now accepts a ``remainder`` keyword argument and stores
11
+ it as ``self.remainder``.
12
+
13
+ These tests verify that:
14
+ - ``ToolkitCall.make_context`` accepts ``core_parse_result`` and propagates
15
+ ``remainder`` into the resulting ``ToolkitContext``.
16
+ - ``ToolkitContext`` exposes ``ctx.remainder`` correctly.
17
+ - ``ToolkitExecutor`` never passes ``None`` as ``core_parse_result`` (it
18
+ defaults to an empty ``ParseResult()``).
19
+ - End-to-end: a task can read ``ctx.remainder`` when invoked with ``-- extra``.
20
+ """
21
+
22
+ from invoke.parser import ParseResult
23
+ from invoke.tasks import Task
24
+
25
+ from invoke_toolkit import Context, task
26
+ from invoke_toolkit.collections import ToolkitCollection
27
+ from invoke_toolkit.config import ToolkitConfig
28
+ from invoke_toolkit.context.context import ToolkitContext
29
+ from invoke_toolkit.executor import ToolkitExecutor
30
+ from invoke_toolkit.tasks.tasks import ToolkitCall
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Unit: ToolkitCall.make_context
35
+ # ---------------------------------------------------------------------------
36
+
37
+
38
+ def _dummy_task_func(ctx: Context) -> None: # pragma: no cover
39
+ """Minimal real task used as a ToolkitCall target in unit tests."""
40
+
41
+
42
+ _DUMMY_TASK: Task = task(_dummy_task_func) # type: ignore[assignment]
43
+
44
+
45
+ def _make_parse_result(remainder: str = "") -> ParseResult:
46
+ """Return a ParseResult whose .remainder is set to *remainder*."""
47
+ pr = ParseResult()
48
+ pr.remainder = remainder
49
+ return pr
50
+
51
+
52
+ def test_make_context_without_core_parse_result():
53
+ """make_context(config) with no core_parse_result → remainder defaults to ''."""
54
+ config = ToolkitConfig()
55
+ c = ToolkitCall(_DUMMY_TASK)
56
+ ctx = c.make_context(config)
57
+ assert isinstance(ctx, ToolkitContext)
58
+ assert ctx.remainder == ""
59
+
60
+
61
+ def test_make_context_with_empty_parse_result():
62
+ """make_context(config, core_parse_result=ParseResult()) → remainder is ''."""
63
+ config = ToolkitConfig()
64
+ c = ToolkitCall(_DUMMY_TASK)
65
+ ctx = c.make_context(config, core_parse_result=_make_parse_result(""))
66
+ assert isinstance(ctx, ToolkitContext)
67
+ assert ctx.remainder == ""
68
+
69
+
70
+ def test_make_context_propagates_remainder():
71
+ """make_context passes ParseResult.remainder into ToolkitContext.remainder."""
72
+ config = ToolkitConfig()
73
+ c = ToolkitCall(_DUMMY_TASK)
74
+ ctx = c.make_context(config, core_parse_result=_make_parse_result("extra args"))
75
+ assert ctx.remainder == "extra args"
76
+
77
+
78
+ def test_make_context_with_none_core_parse_result():
79
+ """Passing core_parse_result=None explicitly is safe (backward compat)."""
80
+ config = ToolkitConfig()
81
+ c = ToolkitCall(_DUMMY_TASK)
82
+ ctx = c.make_context(config, core_parse_result=None)
83
+ assert ctx.remainder == ""
84
+
85
+
86
+ # ---------------------------------------------------------------------------
87
+ # Unit: ToolkitContext.__init__
88
+ # ---------------------------------------------------------------------------
89
+
90
+
91
+ def test_toolkit_context_remainder_default():
92
+ """ToolkitContext() has remainder='' by default."""
93
+ ctx = ToolkitContext()
94
+ assert ctx.remainder == ""
95
+
96
+
97
+ def test_toolkit_context_remainder_kwarg():
98
+ """ToolkitContext(remainder=...) stores the value on the instance."""
99
+ ctx = ToolkitContext(remainder="docker run --rm alpine")
100
+ assert ctx.remainder == "docker run --rm alpine"
101
+
102
+
103
+ def test_toolkit_context_remainder_with_config():
104
+ """ToolkitContext(config=..., remainder=...) works with both args."""
105
+ config = ToolkitConfig()
106
+ ctx = ToolkitContext(config=config, remainder="--verbose")
107
+ assert ctx.remainder == "--verbose"
108
+
109
+
110
+ # ---------------------------------------------------------------------------
111
+ # Unit: ToolkitExecutor.__init__
112
+ # ---------------------------------------------------------------------------
113
+
114
+
115
+ def test_executor_core_defaults_to_parse_result():
116
+ """ToolkitExecutor.core is never None; defaults to ParseResult()."""
117
+ col = ToolkitCollection()
118
+ executor = ToolkitExecutor(collection=col)
119
+ assert executor.core is not None
120
+ assert isinstance(executor.core, ParseResult)
121
+
122
+
123
+ def test_executor_core_accepts_explicit_parse_result():
124
+ """ToolkitExecutor.core stores an explicitly provided ParseResult."""
125
+ col = ToolkitCollection()
126
+ pr = _make_parse_result("some remainder")
127
+ executor = ToolkitExecutor(collection=col, core=pr)
128
+ assert executor.core is pr
129
+ assert executor.core.remainder == "some remainder"
130
+
131
+
132
+ def test_executor_core_none_becomes_empty_parse_result():
133
+ """Passing core=None explicitly still yields a ParseResult(), not None."""
134
+ col = ToolkitCollection()
135
+ executor = ToolkitExecutor(collection=col, core=None)
136
+ assert executor.core is not None
137
+ assert isinstance(executor.core, ParseResult)
138
+ assert executor.core.remainder == ""
139
+
140
+
141
+ # ---------------------------------------------------------------------------
142
+ # Integration: ctx.remainder is accessible inside a task
143
+ # ---------------------------------------------------------------------------
144
+
145
+
146
+ def test_task_receives_remainder_via_context():
147
+ """A task can read ctx.remainder when the program is given a remainder."""
148
+ captured: list[str] = []
149
+
150
+ ns = ToolkitCollection()
151
+
152
+ @task # type: ignore[arg-type]
153
+ def wrapper_task(ctx: Context):
154
+ """Wrapper task that captures ctx.remainder."""
155
+ captured.append(ctx.remainder)
156
+
157
+ ns.add_task(wrapper_task) # type: ignore[arg-type]
158
+
159
+ pr = _make_parse_result("echo hello world")
160
+ config = ToolkitConfig()
161
+ executor = ToolkitExecutor(collection=ns, config=config, core=pr)
162
+ executor.execute("wrapper-task")
163
+
164
+ assert len(captured) == 1
165
+ assert captured[0] == "echo hello world"
166
+
167
+
168
+ def test_task_remainder_empty_when_not_provided():
169
+ """ctx.remainder is '' when no remainder was parsed."""
170
+ captured: list[str] = []
171
+
172
+ ns = ToolkitCollection()
173
+
174
+ @task # type: ignore[arg-type]
175
+ def simple_task(ctx: Context):
176
+ """Simple task that captures ctx.remainder."""
177
+ captured.append(ctx.remainder)
178
+
179
+ ns.add_task(simple_task) # type: ignore[arg-type]
180
+
181
+ executor = ToolkitExecutor(collection=ns)
182
+ executor.execute("simple-task")
183
+
184
+ assert captured == [""]