wexample-cli 1.1.0__tar.gz → 1.3.0__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.
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/PKG-INFO +8 -8
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/README.md +4 -4
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/pyproject.toml +4 -4
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/command/extended_command.py +20 -20
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/common/command_method_wrapper.py +3 -3
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/context/execution_context.py +9 -7
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/decorator/as_sudo.py +5 -4
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/decorator/middleware.py +2 -3
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/decorator/option_stop_on_failure.py +2 -2
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/decorator/screenable.py +8 -12
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/decorator/webhook.py +4 -4
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/helpers/extra_args.py +4 -4
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/middleware/abstract_middleware.py +13 -14
- wexample_cli-1.3.0/tests/helpers/__init__.py +0 -0
- wexample_cli-1.3.0/tests/helpers/test_extra_args.py +38 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/__init__.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/command/__init__.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/common/__init__.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/const/__init__.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/const/middleware.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/const/tags.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/const/types.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/context/__init__.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/decorator/__init__.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/decorator/alias.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/decorator/command.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/decorator/option.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/exception/__init__.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/exception/abstract_command_option_exception.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/exception/command_option_missing_exception.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/exception/command_option_validation_exception.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/helpers/__init__.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/middleware/__init__.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/py.typed +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/testing/__init__.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/testing/kernel.py +0 -0
- {wexample_cli-1.1.0 → wexample_cli-1.3.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wexample-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Reusable CLI primitives — command decorators, options, middlewares, and the enriched command runner — extracted from wex-core so any kernel built on wexample-app can opt in without depending on the full wex framework.
|
|
5
5
|
Author-Email: weeger <contact@wexample.com>
|
|
6
6
|
License: MIT
|
|
@@ -10,9 +10,9 @@ Classifier: Operating System :: OS Independent
|
|
|
10
10
|
Project-URL: homepage, https://github.com/wexample/python-cli
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Requires-Dist: attrs>=23.1.0
|
|
13
|
-
Requires-Dist: wexample-app>=18.
|
|
14
|
-
Requires-Dist: wexample-helpers>=
|
|
15
|
-
Requires-Dist: wexample-prompt>=13.
|
|
13
|
+
Requires-Dist: wexample-app>=18.2.0
|
|
14
|
+
Requires-Dist: wexample-helpers>=17.0.0
|
|
15
|
+
Requires-Dist: wexample-prompt>=13.3.0
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest; extra == "dev"
|
|
18
18
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown
|
|
|
20
20
|
|
|
21
21
|
# cli
|
|
22
22
|
|
|
23
|
-
Version: 1.
|
|
23
|
+
Version: 1.3.0
|
|
24
24
|
|
|
25
25
|
Reusable CLI primitives — command decorators, options, middlewares, and the enriched command runner — extracted from wex-core so any kernel built on wexample-app can opt in without depending on the full wex framework.
|
|
26
26
|
|
|
@@ -100,9 +100,9 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
|
|
|
100
100
|
## Dependencies
|
|
101
101
|
|
|
102
102
|
- attrs: >=23.1.0
|
|
103
|
-
- wexample-app: >=18.
|
|
104
|
-
- wexample-helpers: >=
|
|
105
|
-
- wexample-prompt: >=13.
|
|
103
|
+
- wexample-app: >=18.2.0
|
|
104
|
+
- wexample-helpers: >=17.0.0
|
|
105
|
+
- wexample-prompt: >=13.3.0
|
|
106
106
|
|
|
107
107
|
## Versioning & Compatibility Policy
|
|
108
108
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# cli
|
|
2
2
|
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
|
|
5
5
|
Reusable CLI primitives — command decorators, options, middlewares, and the enriched command runner — extracted from wex-core so any kernel built on wexample-app can opt in without depending on the full wex framework.
|
|
6
6
|
|
|
@@ -80,9 +80,9 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
|
|
|
80
80
|
## Dependencies
|
|
81
81
|
|
|
82
82
|
- attrs: >=23.1.0
|
|
83
|
-
- wexample-app: >=18.
|
|
84
|
-
- wexample-helpers: >=
|
|
85
|
-
- wexample-prompt: >=13.
|
|
83
|
+
- wexample-app: >=18.2.0
|
|
84
|
+
- wexample-helpers: >=17.0.0
|
|
85
|
+
- wexample-prompt: >=13.3.0
|
|
86
86
|
|
|
87
87
|
## Versioning & Compatibility Policy
|
|
88
88
|
|
|
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "wexample-cli"
|
|
9
|
-
version = "1.
|
|
9
|
+
version = "1.3.0"
|
|
10
10
|
description = "Reusable CLI primitives — command decorators, options, middlewares, and the enriched command runner — extracted from wex-core so any kernel built on wexample-app can opt in without depending on the full wex framework."
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "weeger", email = "contact@wexample.com" },
|
|
@@ -19,9 +19,9 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
21
|
"attrs>=23.1.0",
|
|
22
|
-
"wexample-app>=18.
|
|
23
|
-
"wexample-helpers>=
|
|
24
|
-
"wexample-prompt>=13.
|
|
22
|
+
"wexample-app>=18.2.0",
|
|
23
|
+
"wexample-helpers>=17.0.0",
|
|
24
|
+
"wexample-prompt>=13.3.0",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[project.readme]
|
|
@@ -38,9 +38,9 @@ class ExtendedCommand(Command):
|
|
|
38
38
|
middlewares_attributes = self.command_wrapper.middlewares_attributes
|
|
39
39
|
middlewares_registry = self.kernel.get_registry("middlewares")
|
|
40
40
|
|
|
41
|
-
for name in middlewares_attributes:
|
|
41
|
+
for name, attributes in middlewares_attributes.items():
|
|
42
42
|
middleware_class = middlewares_registry.get_class(name)
|
|
43
|
-
middleware = middleware_class(**
|
|
43
|
+
middleware = middleware_class(**attributes)
|
|
44
44
|
self.command_wrapper.set_middleware(middleware)
|
|
45
45
|
|
|
46
46
|
# Universal --help / -h handling — render the command's options instead
|
|
@@ -85,32 +85,33 @@ class ExtendedCommand(Command):
|
|
|
85
85
|
|
|
86
86
|
# Process all declared options
|
|
87
87
|
for option in self.command_wrapper.options:
|
|
88
|
+
name = option.name
|
|
88
89
|
value = None
|
|
89
90
|
|
|
90
91
|
# If the option is in parsed args, use that value
|
|
91
|
-
if
|
|
92
|
-
value = parsed_args[
|
|
92
|
+
if name in parsed_args:
|
|
93
|
+
value = parsed_args[name]
|
|
93
94
|
# Otherwise, use the default value if available
|
|
94
95
|
elif option.default is not None:
|
|
95
96
|
value = option.default
|
|
96
97
|
# If the option is required but not provided, raise an error
|
|
97
98
|
elif option.required:
|
|
98
|
-
raise CommandOptionMissingException(option_name=
|
|
99
|
+
raise CommandOptionMissingException(option_name=name)
|
|
99
100
|
|
|
100
101
|
# Validate the value if validators are defined and value is not None
|
|
101
102
|
if value is not None and option.validators:
|
|
103
|
+
from wexample_cli.exception.command_option_validation_exception import (
|
|
104
|
+
CommandOptionValidationException,
|
|
105
|
+
)
|
|
106
|
+
|
|
102
107
|
# For multiple values, validate each item individually
|
|
103
108
|
values_to_validate = value if isinstance(value, list) else [value]
|
|
104
109
|
|
|
105
110
|
for val in values_to_validate:
|
|
106
111
|
for validator in option.validators:
|
|
107
112
|
if not validator.validate(val):
|
|
108
|
-
from wexample_cli.exception.command_option_validation_exception import (
|
|
109
|
-
CommandOptionValidationException,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
113
|
raise CommandOptionValidationException(
|
|
113
|
-
option_name=
|
|
114
|
+
option_name=name,
|
|
114
115
|
value=val,
|
|
115
116
|
error_message=validator.get_error_message(val),
|
|
116
117
|
)
|
|
@@ -121,7 +122,7 @@ class ExtendedCommand(Command):
|
|
|
121
122
|
|
|
122
123
|
# Assign the validated value
|
|
123
124
|
if value is not None:
|
|
124
|
-
option.value = function_kwargs[
|
|
125
|
+
option.value = function_kwargs[name] = value
|
|
125
126
|
|
|
126
127
|
# `--` passthrough: if the user supplied extra args after `--`, the
|
|
127
128
|
# parser put them in parsed_args["__extra_args__"]. Forward them as
|
|
@@ -158,10 +159,11 @@ class ExtendedCommand(Command):
|
|
|
158
159
|
MIDDLEWARE_OPTION_VALUE_OPTIONAL,
|
|
159
160
|
)
|
|
160
161
|
|
|
161
|
-
|
|
162
|
+
middlewares = self.command_wrapper.middlewares
|
|
163
|
+
if middlewares:
|
|
162
164
|
output = MultipleResponse(kernel=self.kernel)
|
|
163
165
|
|
|
164
|
-
for middleware in
|
|
166
|
+
for middleware in middlewares:
|
|
165
167
|
show_progress = (
|
|
166
168
|
middleware.show_progress == MIDDLEWARE_OPTION_VALUE_ALLWAYS
|
|
167
169
|
or (
|
|
@@ -286,9 +288,6 @@ class ExtendedCommand(Command):
|
|
|
286
288
|
from wexample_app.helpers.response import response_normalize
|
|
287
289
|
from wexample_app.response.abstract_response import AbstractResponse
|
|
288
290
|
|
|
289
|
-
# Create a list to store all tasks
|
|
290
|
-
tasks = []
|
|
291
|
-
|
|
292
291
|
# Create an executor for running CPU-bound functions in a thread pool
|
|
293
292
|
executor = ThreadPoolExecutor(max_workers=min(32, len(execution_contexts)))
|
|
294
293
|
|
|
@@ -308,7 +307,7 @@ class ExtendedCommand(Command):
|
|
|
308
307
|
function_to_execute = execution_context.function or self.function
|
|
309
308
|
|
|
310
309
|
# Run the function in a thread pool to avoid blocking the event loop
|
|
311
|
-
loop = asyncio.
|
|
310
|
+
loop = asyncio.get_running_loop()
|
|
312
311
|
result = await loop.run_in_executor(
|
|
313
312
|
executor,
|
|
314
313
|
lambda: function_to_execute(**execution_context.function_kwargs),
|
|
@@ -320,9 +319,10 @@ class ExtendedCommand(Command):
|
|
|
320
319
|
return response_normalize(kernel=self.kernel, response=result)
|
|
321
320
|
|
|
322
321
|
# Create a task for each pass
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
322
|
+
tasks = [
|
|
323
|
+
asyncio.create_task(execute_single_pass(execution_context))
|
|
324
|
+
for execution_context in execution_contexts
|
|
325
|
+
]
|
|
326
326
|
|
|
327
327
|
# Wait for all tasks to complete
|
|
328
328
|
responses = await asyncio.gather(*tasks)
|
|
@@ -114,9 +114,9 @@ class CommandMethodWrapper(BaseClass):
|
|
|
114
114
|
|
|
115
115
|
def set_middleware(self, middleware: AbstractMiddleware) -> None:
|
|
116
116
|
self.middlewares.append(middleware)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
# extend is a single C-level call; avoids N Python method-dispatch
|
|
118
|
+
# round-trips that self.set_option() would incur per option.
|
|
119
|
+
self.options.extend(middleware.normalized_options)
|
|
120
120
|
|
|
121
121
|
def set_option(self, option: Option) -> None:
|
|
122
122
|
self.options.append(option)
|
|
@@ -64,15 +64,17 @@ class ExecutionContext(
|
|
|
64
64
|
return self._current_progress
|
|
65
65
|
|
|
66
66
|
def finish_progress(self, **kwargs) -> ProgressHandle:
|
|
67
|
-
self._current_progress
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
progress = self._current_progress
|
|
68
|
+
progress.finish()
|
|
69
|
+
parent = progress.parent
|
|
70
|
+
parent.update(**kwargs)
|
|
71
|
+
self._current_progress = parent
|
|
72
|
+
return parent
|
|
72
73
|
|
|
73
74
|
def get_or_create_progress(self, **kwargs) -> ProgressHandle:
|
|
74
75
|
if self._current_progress is None:
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
io = self.io
|
|
77
|
+
self._current_progress = io.progress(
|
|
78
|
+
**kwargs, context=io.create_context()
|
|
77
79
|
).get_handle()
|
|
78
80
|
return self._current_progress
|
|
@@ -7,8 +7,9 @@ if TYPE_CHECKING:
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def as_sudo() -> CommandMethodWrapper:
|
|
10
|
-
|
|
11
|
-
wrapper.sudo = True
|
|
12
|
-
return wrapper
|
|
10
|
+
return _sudo_decorator
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
|
|
13
|
+
def _sudo_decorator(wrapper: CommandMethodWrapper) -> CommandMethodWrapper:
|
|
14
|
+
wrapper.sudo = True
|
|
15
|
+
return wrapper
|
|
@@ -2,10 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
+
from wexample_cli.common.command_method_wrapper import CommandMethodWrapper
|
|
6
|
+
|
|
5
7
|
if TYPE_CHECKING:
|
|
6
8
|
from wexample_helpers.const.types import AnyCallable
|
|
7
9
|
|
|
8
|
-
from wexample_cli.common.command_method_wrapper import CommandMethodWrapper
|
|
9
10
|
from wexample_cli.middleware.abstract_middleware import AbstractMiddleware
|
|
10
11
|
|
|
11
12
|
|
|
@@ -16,8 +17,6 @@ def middleware(
|
|
|
16
17
|
) -> AnyCallable:
|
|
17
18
|
def decorator(command_wrapper: CommandMethodWrapper) -> CommandMethodWrapper:
|
|
18
19
|
# Type safety check
|
|
19
|
-
from wexample_cli.common.command_method_wrapper import CommandMethodWrapper
|
|
20
|
-
|
|
21
20
|
if not isinstance(command_wrapper, CommandMethodWrapper):
|
|
22
21
|
raise TypeError(
|
|
23
22
|
f"Invalid middleware usage: @middleware must decorate a {CommandMethodWrapper.__name__} "
|
{wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/decorator/option_stop_on_failure.py
RENAMED
|
@@ -11,9 +11,9 @@ OPTION_NAME_STOP_ON_FAILURE: str = "stop_on_failure"
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def option_stop_on_failure() -> AnyCallable:
|
|
14
|
-
|
|
15
|
-
from wexample_app.command.option import Option
|
|
14
|
+
from wexample_app.command.option import Option
|
|
16
15
|
|
|
16
|
+
def decorator(command_wrapper: CommandMethodWrapper) -> CommandMethodWrapper:
|
|
17
17
|
command_wrapper.set_option(
|
|
18
18
|
Option(
|
|
19
19
|
name=OPTION_NAME_STOP_ON_FAILURE,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import time
|
|
3
4
|
from typing import TYPE_CHECKING, Any
|
|
4
5
|
|
|
5
6
|
if TYPE_CHECKING:
|
|
@@ -55,7 +56,7 @@ def screenable(interval: float = 1.0, height: int = 30) -> AnyCallable:
|
|
|
55
56
|
)
|
|
56
57
|
|
|
57
58
|
command_wrapper.register_pipeline_wrapper(
|
|
58
|
-
_build_screen_wrapper(default_interval=
|
|
59
|
+
_build_screen_wrapper(default_interval=interval, height=height)
|
|
59
60
|
)
|
|
60
61
|
|
|
61
62
|
return command_wrapper
|
|
@@ -73,31 +74,26 @@ def _build_screen_wrapper(default_interval: float, height: int) -> AnyCallable:
|
|
|
73
74
|
) -> Any:
|
|
74
75
|
# Always strip screen kwargs so they cannot leak to the user function
|
|
75
76
|
# or to inner pipeline wrappers, whether or not --screen was passed.
|
|
76
|
-
enabled =
|
|
77
|
+
enabled = function_kwargs.pop(OPTION_NAME_SCREEN, False)
|
|
77
78
|
interval_override = function_kwargs.pop(OPTION_NAME_SCREEN_INTERVAL, None)
|
|
78
79
|
|
|
79
80
|
if not enabled:
|
|
80
81
|
return next_dispatch(function_kwargs)
|
|
81
82
|
|
|
82
|
-
import time
|
|
83
|
-
|
|
84
83
|
from wexample_app.response.null_response import NullResponse
|
|
85
84
|
|
|
86
|
-
interval =
|
|
87
|
-
float(interval_override)
|
|
88
|
-
if interval_override is not None
|
|
89
|
-
else default_interval
|
|
90
|
-
)
|
|
85
|
+
interval = interval_override if interval_override is not None else default_interval
|
|
91
86
|
kernel_io = request.kernel.io
|
|
92
87
|
# Skip the inter-frame sleep on the very first frame so the user sees
|
|
93
88
|
# output immediately instead of waiting `interval` seconds before the
|
|
94
89
|
# first render.
|
|
95
|
-
is_first_frame =
|
|
90
|
+
is_first_frame = True
|
|
96
91
|
|
|
97
92
|
def _frame(response) -> None:
|
|
98
|
-
|
|
93
|
+
nonlocal is_first_frame
|
|
94
|
+
if not is_first_frame:
|
|
99
95
|
time.sleep(interval)
|
|
100
|
-
is_first_frame
|
|
96
|
+
is_first_frame = False
|
|
101
97
|
|
|
102
98
|
response.clear()
|
|
103
99
|
saved_output = kernel_io.output
|
|
@@ -19,9 +19,9 @@ def webhook() -> type[CommandMethodWrapper]:
|
|
|
19
19
|
@command(type=COMMAND_TYPE_ADDON)
|
|
20
20
|
def my__group__command(context): ...
|
|
21
21
|
"""
|
|
22
|
+
return _webhook_decorator # type: ignore[return-value]
|
|
22
23
|
|
|
23
|
-
def decorator(wrapper: CommandMethodWrapper) -> CommandMethodWrapper:
|
|
24
|
-
wrapper.webhook = True
|
|
25
|
-
return wrapper
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
def _webhook_decorator(wrapper: CommandMethodWrapper) -> CommandMethodWrapper:
|
|
26
|
+
wrapper.webhook = True
|
|
27
|
+
return wrapper
|
|
@@ -27,11 +27,11 @@ def resolve_shell_command(
|
|
|
27
27
|
shell, which is the safer default. Use the legacy `--command "..."`
|
|
28
28
|
form if you intentionally want remote expansion.
|
|
29
29
|
"""
|
|
30
|
-
if extra_args and command:
|
|
31
|
-
context.io.warning(
|
|
32
|
-
"Both --command and `-- <args>` were given; using `-- <args>`."
|
|
33
|
-
)
|
|
34
30
|
if extra_args:
|
|
31
|
+
if command:
|
|
32
|
+
context.io.warning(
|
|
33
|
+
"Both --command and `-- <args>` were given; using `-- <args>`."
|
|
34
|
+
)
|
|
35
35
|
return shlex.join(extra_args)
|
|
36
36
|
if command:
|
|
37
37
|
return command
|
{wexample_cli-1.1.0 → wexample_cli-1.3.0}/src/wexample_cli/middleware/abstract_middleware.py
RENAMED
|
@@ -72,17 +72,17 @@ class AbstractMiddleware(
|
|
|
72
72
|
'Option "stop_on_failure" will be ignored due to parallelization'
|
|
73
73
|
)
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
if self.parallel == MIDDLEWARE_OPTION_VALUE_OPTIONAL:
|
|
76
|
+
command_wrapper.set_option(
|
|
77
|
+
Option(
|
|
78
|
+
name="parallel",
|
|
79
|
+
short_name="pll",
|
|
80
|
+
type=bool,
|
|
81
|
+
description="Execute async when possible",
|
|
82
|
+
default=False,
|
|
83
|
+
is_flag=True,
|
|
84
|
+
)
|
|
84
85
|
)
|
|
85
|
-
)
|
|
86
86
|
|
|
87
87
|
if self.show_progress == MIDDLEWARE_OPTION_VALUE_OPTIONAL:
|
|
88
88
|
command_wrapper.set_option(
|
|
@@ -133,10 +133,9 @@ class AbstractMiddleware(
|
|
|
133
133
|
|
|
134
134
|
def get_option_by_name(self, name: str) -> Option | None:
|
|
135
135
|
"""Get an option by its name from the normalized options."""
|
|
136
|
-
|
|
137
|
-
if
|
|
138
|
-
|
|
139
|
-
return None
|
|
136
|
+
return next(
|
|
137
|
+
(opt for opt in self.normalized_options if opt.name == name), None
|
|
138
|
+
)
|
|
140
139
|
|
|
141
140
|
def validate_options(
|
|
142
141
|
self,
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import types
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_resolve_shell_command_joins_extra_args_with_quoting() -> None:
|
|
7
|
+
from wexample_cli.helpers.extra_args import resolve_shell_command
|
|
8
|
+
|
|
9
|
+
result = resolve_shell_command(
|
|
10
|
+
context=None, command=None, extra_args=["echo", "hi there"]
|
|
11
|
+
)
|
|
12
|
+
assert result == "echo 'hi there'"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_resolve_shell_command_returns_none_when_nothing_given() -> None:
|
|
16
|
+
from wexample_cli.helpers.extra_args import resolve_shell_command
|
|
17
|
+
|
|
18
|
+
result = resolve_shell_command(context=None, command=None, extra_args=None)
|
|
19
|
+
assert result is None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_resolve_shell_command_uses_command_when_no_extra_args() -> None:
|
|
23
|
+
from wexample_cli.helpers.extra_args import resolve_shell_command
|
|
24
|
+
|
|
25
|
+
result = resolve_shell_command(context=None, command="ls -la", extra_args=None)
|
|
26
|
+
assert result == "ls -la"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_resolve_shell_command_warns_when_both_given() -> None:
|
|
30
|
+
from wexample_prompt.common.io_manager import IoManager
|
|
31
|
+
|
|
32
|
+
from wexample_cli.helpers.extra_args import resolve_shell_command
|
|
33
|
+
|
|
34
|
+
context = types.SimpleNamespace(io=IoManager())
|
|
35
|
+
result = resolve_shell_command(
|
|
36
|
+
context=context, command="legacy", extra_args=["run"]
|
|
37
|
+
)
|
|
38
|
+
assert result == "run"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|