cmdbox-cli 1.0.0__py3-none-any.whl
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.
- cmdbox/__init__.py +0 -0
- cmdbox/cli/__init__.py +0 -0
- cmdbox/cli/app.py +125 -0
- cmdbox/cli/commands/__init__.py +0 -0
- cmdbox/cli/commands/alias_fallback.py +102 -0
- cmdbox/cli/commands/command_crud.py +429 -0
- cmdbox/cli/commands/command_run.py +255 -0
- cmdbox/cli/commands/history.py +109 -0
- cmdbox/cli/commands/init.py +54 -0
- cmdbox/cli/commands/settings.py +62 -0
- cmdbox/cli/commands/tag_crud.py +277 -0
- cmdbox/cli/commands/variable_crud.py +349 -0
- cmdbox/cli/common/__init__.py +0 -0
- cmdbox/cli/common/errors.py +58 -0
- cmdbox/cli/common/update_fields.py +88 -0
- cmdbox/cli/completions/__init__.py +0 -0
- cmdbox/cli/completions/commands.py +26 -0
- cmdbox/cli/completions/fields.py +31 -0
- cmdbox/cli/completions/tags.py +24 -0
- cmdbox/cli/completions/variables.py +26 -0
- cmdbox/cli/handlers/__init__.py +0 -0
- cmdbox/cli/handlers/command_handlers.py +357 -0
- cmdbox/cli/handlers/common_handlers.py +15 -0
- cmdbox/cli/handlers/history_handlers.py +94 -0
- cmdbox/cli/handlers/init_handler.py +127 -0
- cmdbox/cli/handlers/run_handler.py +178 -0
- cmdbox/cli/handlers/settings_handler.py +59 -0
- cmdbox/cli/handlers/tag_handlers.py +220 -0
- cmdbox/cli/handlers/variable_handlers.py +272 -0
- cmdbox/cli/prompts/__init__.py +0 -0
- cmdbox/cli/prompts/completers.py +161 -0
- cmdbox/cli/prompts/prompts.py +108 -0
- cmdbox/cli/prompts/validators.py +46 -0
- cmdbox/cli/ui/__init__.py +0 -0
- cmdbox/cli/ui/console.py +31 -0
- cmdbox/cli/ui/editor.py +141 -0
- cmdbox/cli/ui/presenters/__init__.py +0 -0
- cmdbox/cli/ui/presenters/app_presenter.py +8 -0
- cmdbox/cli/ui/presenters/command_presenter.py +168 -0
- cmdbox/cli/ui/presenters/history_presenter.py +83 -0
- cmdbox/cli/ui/presenters/init_instructions.py +52 -0
- cmdbox/cli/ui/presenters/init_presenter.py +57 -0
- cmdbox/cli/ui/presenters/result_presenter.py +144 -0
- cmdbox/cli/ui/presenters/settings_presenter.py +130 -0
- cmdbox/cli/ui/presenters/tag_presenter.py +97 -0
- cmdbox/cli/ui/presenters/variable_presenter.py +103 -0
- cmdbox/cli/ui/primitives.py +410 -0
- cmdbox/cli/ui/theme.py +43 -0
- cmdbox/cli/ui/theme_builder.py +49 -0
- cmdbox/common/__init__.py +0 -0
- cmdbox/common/io.py +34 -0
- cmdbox/container.py +156 -0
- cmdbox/core/__init__.py +0 -0
- cmdbox/core/fields.py +48 -0
- cmdbox/core/paths.py +52 -0
- cmdbox/database.py +65 -0
- cmdbox/exceptions.py +10 -0
- cmdbox/init/__init__.py +0 -0
- cmdbox/init/detect.py +82 -0
- cmdbox/init/integrations/bash.sh +10 -0
- cmdbox/init/integrations/cmd.bat +14 -0
- cmdbox/init/integrations/fish.fish +11 -0
- cmdbox/init/integrations/powershell.ps1 +14 -0
- cmdbox/init/integrations/zsh.sh +10 -0
- cmdbox/init/io.py +68 -0
- cmdbox/init/specs.py +54 -0
- cmdbox/logging_setup/__init__.py +0 -0
- cmdbox/logging_setup/log_config.py +123 -0
- cmdbox/logging_setup/log_decorators.py +40 -0
- cmdbox/logging_setup/log_handlers.py +94 -0
- cmdbox/migrations/__init__.py +1 -0
- cmdbox/migrations/errors.py +10 -0
- cmdbox/migrations/runner.py +127 -0
- cmdbox/migrations/versions/__init__.py +0 -0
- cmdbox/models.py +165 -0
- cmdbox/repositories/__init__.py +0 -0
- cmdbox/repositories/base_repository.py +181 -0
- cmdbox/repositories/command_repository.py +391 -0
- cmdbox/repositories/errors.py +120 -0
- cmdbox/repositories/history_repository.py +155 -0
- cmdbox/repositories/results.py +37 -0
- cmdbox/repositories/tag_repository.py +91 -0
- cmdbox/repositories/validators.py +256 -0
- cmdbox/repositories/variable_repository.py +324 -0
- cmdbox/resolve/__init__.py +0 -0
- cmdbox/resolve/errors.py +65 -0
- cmdbox/resolve/lookup.py +137 -0
- cmdbox/resolve/resolver.py +402 -0
- cmdbox/resolve/type_defs.py +96 -0
- cmdbox/runtime/__init__.py +0 -0
- cmdbox/runtime/executor.py +454 -0
- cmdbox/runtime/results.py +25 -0
- cmdbox/runtime/shell.py +90 -0
- cmdbox/services/__init__.py +0 -0
- cmdbox/services/command_services.py +261 -0
- cmdbox/services/errors.py +37 -0
- cmdbox/services/field_selection.py +162 -0
- cmdbox/services/history_service.py +68 -0
- cmdbox/services/run_service.py +204 -0
- cmdbox/services/tag_services.py +134 -0
- cmdbox/services/variable_services.py +224 -0
- cmdbox/settings/__init__.py +0 -0
- cmdbox/settings/models.py +129 -0
- cmdbox/settings/settings_repository.py +36 -0
- cmdbox/settings/settings_service.py +144 -0
- cmdbox/version.py +1 -0
- cmdbox_cli-1.0.0.dist-info/METADATA +125 -0
- cmdbox_cli-1.0.0.dist-info/RECORD +112 -0
- cmdbox_cli-1.0.0.dist-info/WHEEL +5 -0
- cmdbox_cli-1.0.0.dist-info/entry_points.txt +2 -0
- cmdbox_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
- cmdbox_cli-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
from cmdbox.models import Command
|
|
5
|
+
from cmdbox.repositories.command_repository import CommandRepository
|
|
6
|
+
from cmdbox.repositories.history_repository import HistoryRepository
|
|
7
|
+
from cmdbox.resolve.resolver import Resolver
|
|
8
|
+
from cmdbox.resolve.type_defs import ResolveResult
|
|
9
|
+
from cmdbox.runtime.executor import Executor, RunContext
|
|
10
|
+
from cmdbox.runtime.results import ExecutionResult
|
|
11
|
+
from cmdbox.settings.models import Settings
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RunService:
|
|
15
|
+
"""
|
|
16
|
+
Provides services for executing and previewing commands.
|
|
17
|
+
|
|
18
|
+
This class manages the execution of commands and their previews. It interacts with
|
|
19
|
+
a command repository, a resolver for processing commands, and an executor for
|
|
20
|
+
executing commands. The `run` method is used to execute a command, while the `preview`
|
|
21
|
+
method enables retrieving a resolved result of a command without executing it.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
repo (CommandRepository): The repository that stores and provides access to
|
|
25
|
+
command definitions based on their alias.
|
|
26
|
+
resolver (Resolver): Encapsulates the logic required to resolve a command
|
|
27
|
+
template into its executable form.
|
|
28
|
+
executor (Executor): Responsible for executing resolved commands and
|
|
29
|
+
returning the results.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
repo: CommandRepository,
|
|
35
|
+
resolver: Resolver,
|
|
36
|
+
executor: Executor,
|
|
37
|
+
history_repo: HistoryRepository | None = None,
|
|
38
|
+
get_settings: Callable[[], Settings] | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
self._repo = repo
|
|
41
|
+
self._resolver = resolver
|
|
42
|
+
self._executor = executor
|
|
43
|
+
self._history_repo = history_repo
|
|
44
|
+
self._get_settings = get_settings
|
|
45
|
+
|
|
46
|
+
def run(
|
|
47
|
+
self,
|
|
48
|
+
command_alias: str,
|
|
49
|
+
ctx: RunContext | None = None,
|
|
50
|
+
runtime_vars: dict[str, str] | None = None,
|
|
51
|
+
) -> ExecutionResult:
|
|
52
|
+
"""
|
|
53
|
+
Executes a command based on the given alias.
|
|
54
|
+
|
|
55
|
+
This method retrieves a command associated with the provided alias, resolves its
|
|
56
|
+
template using a resolver, and then executes the resolved command text. Execution
|
|
57
|
+
context stored on the command (cwd, shell, env, timeout) is merged with any
|
|
58
|
+
runtime-supplied context, with runtime values taking precedence.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
command_alias (str): The alias for the command to be executed.
|
|
62
|
+
ctx (RunContext | None): The context for running the command.
|
|
63
|
+
runtime_vars (dict[str, str] | None): Runtime variables to be used during command execution.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
ExecutionResult: The result of executing the command.
|
|
67
|
+
"""
|
|
68
|
+
cmd = self._repo.get_by_alias(command_alias)
|
|
69
|
+
resolved_cmd = self._resolver.resolve(cmd.template, runtime_vars=runtime_vars)
|
|
70
|
+
ctx = self.build_context(cmd, ctx)
|
|
71
|
+
result = self._executor.run(resolved_cmd.text, ctx=ctx)
|
|
72
|
+
self._repo.record_use(cmd.id)
|
|
73
|
+
self.record_history(
|
|
74
|
+
alias=command_alias,
|
|
75
|
+
template=cmd.template,
|
|
76
|
+
resolved=resolved_cmd.text,
|
|
77
|
+
runtime_vars=runtime_vars,
|
|
78
|
+
exit_code=result.exit_code,
|
|
79
|
+
)
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
def preview(
|
|
83
|
+
self,
|
|
84
|
+
command_alias: str,
|
|
85
|
+
runtime_vars: dict[str, str] | None = None,
|
|
86
|
+
ctx: RunContext | None = None,
|
|
87
|
+
) -> tuple[ResolveResult, RunContext | None]:
|
|
88
|
+
"""
|
|
89
|
+
Retrieves and resolves a command template based on its alias.
|
|
90
|
+
|
|
91
|
+
This method takes a command alias, retrieves the associated command template,
|
|
92
|
+
and resolves it to produce a complete command configuration or data structure.
|
|
93
|
+
This resolved template is then returned as a `ResolveResult`.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
command_alias (str): The alias of the command to be resolved.
|
|
97
|
+
runtime_vars (dict[str, str] | None): Runtime variables to be used during command resolution.
|
|
98
|
+
ctx (RunContext | None): The context in which the command is being executed.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
tuple[ResolveResult, RunContext | None]: The resolved result of the command template and the effective context.
|
|
102
|
+
"""
|
|
103
|
+
cmd = self._repo.get_by_alias(command_alias)
|
|
104
|
+
resolved = self._resolver.resolve(cmd.template, runtime_vars=runtime_vars)
|
|
105
|
+
effective_ctx = self.build_context(cmd, ctx)
|
|
106
|
+
return resolved, effective_ctx
|
|
107
|
+
|
|
108
|
+
def record_history(
|
|
109
|
+
self,
|
|
110
|
+
*,
|
|
111
|
+
alias: str,
|
|
112
|
+
template: str,
|
|
113
|
+
resolved: str,
|
|
114
|
+
runtime_vars: dict[str, str] | None = None,
|
|
115
|
+
exit_code: int | None = None,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Records command execution history if the history feature is enabled and
|
|
119
|
+
properly configured. This function interacts with an underlying
|
|
120
|
+
repository to store details about the executed command including alias,
|
|
121
|
+
template, resolved string, runtime variables, and exit code.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
alias (str): The alias or the name of the command executed.
|
|
125
|
+
template (str): The template string representing the command.
|
|
126
|
+
resolved (str): The fully resolved string of the executed command.
|
|
127
|
+
runtime_vars (dict[str, str] | None): Runtime variables used during
|
|
128
|
+
the execution of the command. Defaults to None.
|
|
129
|
+
exit_code (int | None): Exit code that indicates the result of the
|
|
130
|
+
command execution. Defaults to None.
|
|
131
|
+
"""
|
|
132
|
+
if not self._history_repo or not self._get_settings:
|
|
133
|
+
return
|
|
134
|
+
settings = self._get_settings()
|
|
135
|
+
if not settings.history.enabled:
|
|
136
|
+
return
|
|
137
|
+
self._history_repo.record(
|
|
138
|
+
alias=alias,
|
|
139
|
+
template=template,
|
|
140
|
+
resolved=resolved,
|
|
141
|
+
variables_used=runtime_vars or None,
|
|
142
|
+
exit_code=exit_code,
|
|
143
|
+
limit=settings.history.limit_per_command,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def collect_missing_vars(
|
|
147
|
+
self, command_alias: str, runtime_vars: dict[str, str] | None = None
|
|
148
|
+
) -> list[str]:
|
|
149
|
+
cmd = self._repo.get_by_alias(command_alias)
|
|
150
|
+
missing = self._resolver.collect_missing_vars(
|
|
151
|
+
cmd.template, runtime_vars=runtime_vars
|
|
152
|
+
)
|
|
153
|
+
return missing
|
|
154
|
+
|
|
155
|
+
def build_context(
|
|
156
|
+
self, cmd: Command, runtime_ctx: RunContext | None
|
|
157
|
+
) -> RunContext | None:
|
|
158
|
+
"""
|
|
159
|
+
Constructs a runtime context by merging command-level settings with runtime-level
|
|
160
|
+
settings. Resolves priority based on input from `runtime_ctx` when available, or
|
|
161
|
+
falls back to `cmd` for default values. If no significant configuration changes
|
|
162
|
+
are detected, returns `None`.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
cmd (Command): The command object containing default execution settings, including
|
|
166
|
+
environment variables, working directory, shell, timeout, and more.
|
|
167
|
+
runtime_ctx (RunContext | None): An optional runtime context object that may override
|
|
168
|
+
specific command-level settings, such as environment variables and capture
|
|
169
|
+
preferences.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
RunContext | None: Returns a `RunContext` object with the resulting configuration
|
|
173
|
+
or `None` if no significant changes are produced by the merge.
|
|
174
|
+
"""
|
|
175
|
+
stored_env = json.loads(cmd.env) if cmd.env else {}
|
|
176
|
+
runtime_env = getattr(runtime_ctx, "env", None) or {}
|
|
177
|
+
merged_env = {**stored_env, **runtime_env} or None
|
|
178
|
+
|
|
179
|
+
cwd = (runtime_ctx.cwd if runtime_ctx and runtime_ctx.cwd else None) or cmd.cwd
|
|
180
|
+
shell = (
|
|
181
|
+
runtime_ctx.shell if runtime_ctx and runtime_ctx.shell else None
|
|
182
|
+
) or cmd.shell
|
|
183
|
+
timeout = (
|
|
184
|
+
runtime_ctx.timeout
|
|
185
|
+
if (runtime_ctx and runtime_ctx.timeout is not None)
|
|
186
|
+
else cmd.timeout
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
capture = runtime_ctx.capture if runtime_ctx else False
|
|
190
|
+
emit = runtime_ctx.emit if runtime_ctx else False
|
|
191
|
+
verbose = runtime_ctx.verbose if runtime_ctx else False
|
|
192
|
+
|
|
193
|
+
if not any([cwd, shell, merged_env, timeout, capture, emit, verbose]):
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
return RunContext(
|
|
197
|
+
cwd=cwd,
|
|
198
|
+
shell=shell,
|
|
199
|
+
env=merged_env,
|
|
200
|
+
timeout=timeout,
|
|
201
|
+
capture=capture,
|
|
202
|
+
emit=emit,
|
|
203
|
+
verbose=verbose,
|
|
204
|
+
)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from typing import Sequence
|
|
2
|
+
|
|
3
|
+
from cmdbox.models import Tag
|
|
4
|
+
from cmdbox.repositories.tag_repository import TagRepository
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TagServices:
|
|
8
|
+
"""
|
|
9
|
+
Provides services for managing tags.
|
|
10
|
+
|
|
11
|
+
The `TagServices` class encapsulates the logic for working with tags,
|
|
12
|
+
offering methods to create, update, delete, and retrieve tag records.
|
|
13
|
+
It also supports additional functionality such as searching and listing tags
|
|
14
|
+
with sorting and limit options.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
tag_repository (TagRepository): Repository for managing tag records.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, tag_repository: TagRepository):
|
|
21
|
+
self._repo = tag_repository
|
|
22
|
+
|
|
23
|
+
def create_tag(self, name: str, description: str | None = None) -> Tag:
|
|
24
|
+
"""
|
|
25
|
+
Creates a new tag by storing the provided name and optional description
|
|
26
|
+
into the database.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
name (str): The name of the tag to be created.
|
|
30
|
+
description (str | None): An optional description for the tag. Defaults to None.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Tag: The created tag record.
|
|
34
|
+
"""
|
|
35
|
+
return self._repo.create(name=name, description=description)
|
|
36
|
+
|
|
37
|
+
def update_tag(self, name_: str, **fields) -> Tag:
|
|
38
|
+
"""
|
|
39
|
+
Updates an existing tag by its name with new field values.
|
|
40
|
+
|
|
41
|
+
Retrieves the tag corresponding to the given name and updates it
|
|
42
|
+
with the provided fields. The update is performed using the repository.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
name_ (str): The name of the tag to update.
|
|
46
|
+
**fields: Arbitrary field values to update on the tag.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Tag: The updated tag object.
|
|
50
|
+
"""
|
|
51
|
+
tag = self._repo.get_by_name(name_)
|
|
52
|
+
return self._repo.update(tag, **fields)
|
|
53
|
+
|
|
54
|
+
def delete_tag(self, name: str) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Deletes a tag by its name.
|
|
57
|
+
|
|
58
|
+
This method removes a tag record from the repository that matches the
|
|
59
|
+
provided name.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
name: The name of the tag to delete.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
bool: True if the tag was deleted successfully, False otherwise.
|
|
66
|
+
"""
|
|
67
|
+
tag = self._repo.get_by_name(name)
|
|
68
|
+
return self._repo.delete(tag)
|
|
69
|
+
|
|
70
|
+
def get_tag(self, name: str) -> Tag:
|
|
71
|
+
"""
|
|
72
|
+
Retrieves a tag by its name.
|
|
73
|
+
|
|
74
|
+
This method fetches a tag object associated with the given name from the
|
|
75
|
+
repository.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
name (str): The name of the tag to retrieve.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Tag: The tag object associated with the given name.
|
|
82
|
+
"""
|
|
83
|
+
return self._repo.get_by_name(name)
|
|
84
|
+
|
|
85
|
+
def get_tag_by_id(self, tag_id: int) -> Tag:
|
|
86
|
+
tag = self._repo.get_by_id(tag_id)
|
|
87
|
+
return tag
|
|
88
|
+
|
|
89
|
+
def list_tags(
|
|
90
|
+
self,
|
|
91
|
+
order_by: str | Sequence[str] = "name",
|
|
92
|
+
limit: int = 25,
|
|
93
|
+
) -> list[Tag]:
|
|
94
|
+
"""
|
|
95
|
+
Lists tags with sorting and limit options.
|
|
96
|
+
|
|
97
|
+
This function fetches a list of tags from the repository. The results can
|
|
98
|
+
be sorted and limited based on the provided arguments.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
order_by (str | Sequence[str]): Specifies the field(s) to sort the results by.
|
|
102
|
+
Default is "name".
|
|
103
|
+
limit (int, optional): The maximum number of tags to return. Default is 25.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
list[Tag]: A list of tags sorted according to the specified criteria.
|
|
107
|
+
"""
|
|
108
|
+
return self._repo.list_all(order_by, limit)
|
|
109
|
+
|
|
110
|
+
def search(
|
|
111
|
+
self,
|
|
112
|
+
query: str,
|
|
113
|
+
fields: str | Sequence[str] | None = None,
|
|
114
|
+
limit: int = 25,
|
|
115
|
+
) -> list[Tag]:
|
|
116
|
+
"""
|
|
117
|
+
Searches for tags that match the given query across specified fields.
|
|
118
|
+
|
|
119
|
+
This method allows you to perform a search within the data repository for tags
|
|
120
|
+
that match the provided query string in the specified fields. It returns a list of
|
|
121
|
+
tags that satisfy the search criteria.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
query (str): The search term used for matching against the repository.
|
|
125
|
+
fields (str | Sequence[str] | None): The fields to perform the search within. If
|
|
126
|
+
None, defaults to ("name", "description").
|
|
127
|
+
limit (int, optional): The maximum number of tags to return. Default is 25.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
list[Tag]: A list of Tag objects that match the search query.
|
|
131
|
+
"""
|
|
132
|
+
if not fields:
|
|
133
|
+
fields = ("name", "description")
|
|
134
|
+
return self._repo.search(query, fields, limit)
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from typing import Sequence
|
|
2
|
+
|
|
3
|
+
from cmdbox.models import Tag, Variable
|
|
4
|
+
from cmdbox.repositories.variable_repository import VariableRepository
|
|
5
|
+
from cmdbox.database import db
|
|
6
|
+
from cmdbox.repositories.results import TagAttachResult, TagDetachResult
|
|
7
|
+
from cmdbox.repositories.tag_repository import TagRepository
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class VariableServices:
|
|
11
|
+
"""
|
|
12
|
+
Provides services for managing variables and their associated tags.
|
|
13
|
+
|
|
14
|
+
The `VariableServices` class encapsulates the logic for working with variables and
|
|
15
|
+
tags, offering methods to create, update, delete, and retrieve variable records
|
|
16
|
+
and their tag associations. It also supports additional functionality such as
|
|
17
|
+
searching, tagging, and listing variables with filtering and sorting options.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
variable_repository (VariableRepository): Repository for managing variable records.
|
|
21
|
+
tag_repository (TagRepository): Repository for managing tag-related operations.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self, variable_repository: VariableRepository, tag_repository: TagRepository
|
|
26
|
+
):
|
|
27
|
+
self._repo = variable_repository
|
|
28
|
+
self._tag_repo = tag_repository
|
|
29
|
+
|
|
30
|
+
def create_variable(
|
|
31
|
+
self,
|
|
32
|
+
name: str,
|
|
33
|
+
value: str,
|
|
34
|
+
tags: list[str] | None = None,
|
|
35
|
+
) -> Variable:
|
|
36
|
+
"""
|
|
37
|
+
Creates a new variable by storing the provided name, value, and optional tags
|
|
38
|
+
into the database. If tags are provided, they are associated with the created
|
|
39
|
+
variable.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
name (str): The name of the variable to be created.
|
|
43
|
+
value (str): The value associated with the variable.
|
|
44
|
+
tags (list[str] | None): An optional list of tags to associate with the variable.
|
|
45
|
+
Defaults to None.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Variable: The created variable record.
|
|
49
|
+
"""
|
|
50
|
+
with db.atomic():
|
|
51
|
+
tags = self._get_tags(tags)
|
|
52
|
+
var = self._repo.create(name=name, value=value)
|
|
53
|
+
if tags:
|
|
54
|
+
self._repo.add_tags(var, tags)
|
|
55
|
+
return var
|
|
56
|
+
|
|
57
|
+
def update_variable(self, name_: str, **fields) -> Variable:
|
|
58
|
+
"""
|
|
59
|
+
Updates an existing variable by its name with new field values.
|
|
60
|
+
|
|
61
|
+
Retrieves the variable corresponding to the given name and updates it
|
|
62
|
+
with the provided fields. The update is performed using the repository.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
name_ (str): The name of the variable to update.
|
|
66
|
+
**fields: Arbitrary field values to update on the variable.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Variable: The updated variable object.
|
|
70
|
+
"""
|
|
71
|
+
var = self._repo.get_by_name(name_)
|
|
72
|
+
return self._repo.update(var, **fields)
|
|
73
|
+
|
|
74
|
+
def delete_variable(self, name: str) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
Deletes a variable by its name.
|
|
77
|
+
|
|
78
|
+
This method removes a variable record from the repository that matches the
|
|
79
|
+
provided name.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
name: The name of the variable to delete.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
bool: True if the variable was deleted successfully, False otherwise.
|
|
86
|
+
"""
|
|
87
|
+
var = self._repo.get_by_name(name)
|
|
88
|
+
return self._repo.delete(var)
|
|
89
|
+
|
|
90
|
+
def add_tags(self, name: str, tags: list[str]) -> TagAttachResult:
|
|
91
|
+
"""
|
|
92
|
+
Adds specified tags to an existing variable name.
|
|
93
|
+
|
|
94
|
+
The method retrieves a variable object associated with the given name and
|
|
95
|
+
applies the provided tags to the variable.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
name (str): The name identifying the variable to which tags are to be
|
|
99
|
+
attached.
|
|
100
|
+
tags (list[str]): A list of tags to attach to the variable.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
TagAttachResult: The result of the tag attachment operation, indicating
|
|
104
|
+
whether the tags were successfully attached to the variable.
|
|
105
|
+
"""
|
|
106
|
+
var = self._repo.get_by_name(name)
|
|
107
|
+
tags = self._get_tags(tags)
|
|
108
|
+
return self._repo.add_tags(var, tags)
|
|
109
|
+
|
|
110
|
+
def remove_tags(self, name: str, tags: list[str]) -> TagDetachResult:
|
|
111
|
+
"""
|
|
112
|
+
Removes specific tags associated with a variable name.
|
|
113
|
+
|
|
114
|
+
This method retrieves a variable by its name and detaches the specified tags
|
|
115
|
+
from it. The updated tag associations will be handled by the repository.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
name: The unique identifier for the variable to update.
|
|
119
|
+
tags: A list of tags to be removed from the specified variable.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
TagDetachResult: An object representing the result of tag detachment.
|
|
123
|
+
"""
|
|
124
|
+
var = self._repo.get_by_name(name)
|
|
125
|
+
tags = self._get_tags(tags)
|
|
126
|
+
return self._repo.remove_tags(var, tags)
|
|
127
|
+
|
|
128
|
+
def get_variable(self, name: str) -> Variable:
|
|
129
|
+
"""
|
|
130
|
+
Retrieves a variable by its name.
|
|
131
|
+
|
|
132
|
+
This method fetches a variable object associated with the given name from the
|
|
133
|
+
repository.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
name (str): The name of the variable to retrieve.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Variable: The variable object associated with the given name.
|
|
140
|
+
"""
|
|
141
|
+
var = self._repo.get_by_name(name)
|
|
142
|
+
return var
|
|
143
|
+
|
|
144
|
+
def get_variable_by_id(self, var_id: int) -> Variable:
|
|
145
|
+
var = self._repo.get_by_id(var_id)
|
|
146
|
+
return var
|
|
147
|
+
|
|
148
|
+
def list_variables(
|
|
149
|
+
self,
|
|
150
|
+
order_by: str | Sequence[str] = "name",
|
|
151
|
+
tags: Sequence[str] | None = None,
|
|
152
|
+
limit: int = 25,
|
|
153
|
+
) -> list[Variable]:
|
|
154
|
+
"""
|
|
155
|
+
Lists variables, optionally filtered by tags, with sorting and limit options.
|
|
156
|
+
|
|
157
|
+
This function fetches a list of variables, either filtered by specific tags
|
|
158
|
+
or returning all available variables. The results can be sorted and limited
|
|
159
|
+
based on the provided arguments.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
order_by (str | Sequence[str]): Specifies the field(s) to sort the results by.
|
|
163
|
+
Default is "name".
|
|
164
|
+
tags (Sequence[str], optional): A list of tags to filter the variables. If
|
|
165
|
+
provided, only variables matching the tags will be included.
|
|
166
|
+
limit (int, optional): The maximum number of commands to return. Default is 25.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
list[Variable]: A list of variables matching the provided filters and sorted
|
|
170
|
+
according to the specified criteria.
|
|
171
|
+
"""
|
|
172
|
+
if tags:
|
|
173
|
+
tags = self._get_tags(tags)
|
|
174
|
+
return self._repo.list_by_tag(tags, order_by, limit)
|
|
175
|
+
return self._repo.list_all(order_by, limit)
|
|
176
|
+
|
|
177
|
+
def search(
|
|
178
|
+
self,
|
|
179
|
+
query: str,
|
|
180
|
+
fields: str | Sequence[str] | None = None,
|
|
181
|
+
limit: int = 25,
|
|
182
|
+
) -> list[Variable]:
|
|
183
|
+
"""
|
|
184
|
+
Searches for variables that match the given query across specified fields.
|
|
185
|
+
|
|
186
|
+
This method allows you to perform a search within the data repository for variables
|
|
187
|
+
that match the provided query string in the specified fields. It returns a list of
|
|
188
|
+
variables that satisfy the search criteria.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
query (str): The search term used for matching against the repository.
|
|
192
|
+
fields (str | Sequence[str] | None): The fields to perform the search within. Defaults
|
|
193
|
+
to ("name", "value"). If None, no specific fields are targeted.
|
|
194
|
+
limit (int): The maximum number of results to return. Defaults to 25.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
list[Variable]: A list of Variable objects that match the search query.
|
|
198
|
+
"""
|
|
199
|
+
if not fields:
|
|
200
|
+
fields = ("name", "value")
|
|
201
|
+
return self._repo.search(query, fields=fields, limit=limit)
|
|
202
|
+
|
|
203
|
+
def _get_tags(self, tags: Sequence[str] | None) -> list[Tag]:
|
|
204
|
+
"""
|
|
205
|
+
Fetches a list of Tag objects based on the provided tag names.
|
|
206
|
+
|
|
207
|
+
This method takes a list of tag names as input and retrieves the corresponding
|
|
208
|
+
Tag objects from the tag repository. The resulting list of Tag objects is then
|
|
209
|
+
returned.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
tags (list[str] | None): A list of string representing the names of the tags to
|
|
213
|
+
retrieve.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
list[Tag]: A list of Tag objects corresponding to the specified tag names.
|
|
217
|
+
"""
|
|
218
|
+
if tags is None:
|
|
219
|
+
return []
|
|
220
|
+
ret_tags: list[Tag] = []
|
|
221
|
+
for name in tags:
|
|
222
|
+
tag = self._tag_repo.get_by_name(name)
|
|
223
|
+
ret_tags.append(tag)
|
|
224
|
+
return ret_tags
|
|
File without changes
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass(frozen=True)
|
|
5
|
+
class UIStyle:
|
|
6
|
+
# Core
|
|
7
|
+
title: str = "bold"
|
|
8
|
+
subtitle: str = "dim"
|
|
9
|
+
muted: str = "dim"
|
|
10
|
+
border: str = "dim"
|
|
11
|
+
panel_title: str = "bold"
|
|
12
|
+
|
|
13
|
+
# Tables
|
|
14
|
+
table_header: str = "bold"
|
|
15
|
+
caption: str = "dim"
|
|
16
|
+
|
|
17
|
+
# Key/value panels
|
|
18
|
+
kv_key: str = "dim"
|
|
19
|
+
kv_value: str = ""
|
|
20
|
+
|
|
21
|
+
# Status
|
|
22
|
+
success: str = "green"
|
|
23
|
+
info: str = "cyan"
|
|
24
|
+
warning: str = "yellow"
|
|
25
|
+
error: str = "red"
|
|
26
|
+
debug: str = "dim blue"
|
|
27
|
+
|
|
28
|
+
# Code
|
|
29
|
+
code: str = "cyan"
|
|
30
|
+
code_inline: str = "cyan"
|
|
31
|
+
code_block: str = "dim cyan"
|
|
32
|
+
|
|
33
|
+
# Entity accents
|
|
34
|
+
entity_name: str = "bold"
|
|
35
|
+
entity_id: str = "magenta"
|
|
36
|
+
entity_count: str = "bold"
|
|
37
|
+
entity_time: str = "dim"
|
|
38
|
+
|
|
39
|
+
# Tags
|
|
40
|
+
tag_pill: str = "bold white on dark_green"
|
|
41
|
+
tag_pill_muted: str = "white on grey23"
|
|
42
|
+
|
|
43
|
+
# Execution and previews
|
|
44
|
+
run_command: str = "cyan"
|
|
45
|
+
run_stdout: str = ""
|
|
46
|
+
run_stderr: str = "bold red"
|
|
47
|
+
trace_kind: str = "dim"
|
|
48
|
+
trace_key: str = "magenta"
|
|
49
|
+
trace_value: str = "cyan"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class DefaultFields:
|
|
54
|
+
command_output: list[str] = field(
|
|
55
|
+
default_factory=lambda: ["alias", "template", "description"]
|
|
56
|
+
)
|
|
57
|
+
command_search: list[str] = field(
|
|
58
|
+
default_factory=lambda: ["alias", "template", "description"]
|
|
59
|
+
)
|
|
60
|
+
variable_output: list[str] = field(default_factory=lambda: ["name", "value"])
|
|
61
|
+
variable_search: list[str] = field(default_factory=lambda: ["name", "value"])
|
|
62
|
+
tag_output: list[str] = field(default_factory=lambda: ["name", "description"])
|
|
63
|
+
tag_search: list[str] = field(default_factory=lambda: ["name", "description"])
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class FieldAliases:
|
|
68
|
+
alias_mapping: dict[str, list[str]] = field(
|
|
69
|
+
default_factory=lambda: {
|
|
70
|
+
"alias": ["a", "al"],
|
|
71
|
+
"template": ["t", "temp"],
|
|
72
|
+
"description": ["d", "desc"],
|
|
73
|
+
"date_created": ["dc", "created"],
|
|
74
|
+
"last_updated": ["lu", "updated"],
|
|
75
|
+
"used": ["u"],
|
|
76
|
+
"last_used": ["lu"],
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def alias_map(self) -> dict[str, str]:
|
|
82
|
+
out: dict[str, str] = {}
|
|
83
|
+
for field_name, aliases in self.alias_mapping.items():
|
|
84
|
+
for alias in aliases:
|
|
85
|
+
out[alias.lower()] = field_name
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True)
|
|
90
|
+
class UiSettings:
|
|
91
|
+
use_color: bool = True
|
|
92
|
+
colors: UIStyle = UIStyle()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True)
|
|
96
|
+
class ExecutionSettings:
|
|
97
|
+
default_shell: str = "auto" # auto | bash | zsh | pwsh | cmd
|
|
98
|
+
capture_output: bool = False
|
|
99
|
+
default_verbose: bool = False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class LoggingFileSettings:
|
|
104
|
+
enabled: bool = False
|
|
105
|
+
level: str = "INFO"
|
|
106
|
+
max_bytes: int = 1_000_000
|
|
107
|
+
backups: int = 3
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True)
|
|
111
|
+
class LoggingSettings:
|
|
112
|
+
console_level: str = "WARNING"
|
|
113
|
+
file: LoggingFileSettings = LoggingFileSettings()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class HistorySettings:
|
|
118
|
+
enabled: bool = True
|
|
119
|
+
limit_per_command: int = 100 # 0 = unlimited
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass(frozen=True)
|
|
123
|
+
class Settings:
|
|
124
|
+
ui: UiSettings = UiSettings()
|
|
125
|
+
execution_settings: ExecutionSettings = ExecutionSettings()
|
|
126
|
+
default_fields: DefaultFields = DefaultFields()
|
|
127
|
+
field_aliases: FieldAliases = FieldAliases()
|
|
128
|
+
logging: LoggingSettings = LoggingSettings()
|
|
129
|
+
history: HistorySettings = HistorySettings()
|