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.
Files changed (112) hide show
  1. cmdbox/__init__.py +0 -0
  2. cmdbox/cli/__init__.py +0 -0
  3. cmdbox/cli/app.py +125 -0
  4. cmdbox/cli/commands/__init__.py +0 -0
  5. cmdbox/cli/commands/alias_fallback.py +102 -0
  6. cmdbox/cli/commands/command_crud.py +429 -0
  7. cmdbox/cli/commands/command_run.py +255 -0
  8. cmdbox/cli/commands/history.py +109 -0
  9. cmdbox/cli/commands/init.py +54 -0
  10. cmdbox/cli/commands/settings.py +62 -0
  11. cmdbox/cli/commands/tag_crud.py +277 -0
  12. cmdbox/cli/commands/variable_crud.py +349 -0
  13. cmdbox/cli/common/__init__.py +0 -0
  14. cmdbox/cli/common/errors.py +58 -0
  15. cmdbox/cli/common/update_fields.py +88 -0
  16. cmdbox/cli/completions/__init__.py +0 -0
  17. cmdbox/cli/completions/commands.py +26 -0
  18. cmdbox/cli/completions/fields.py +31 -0
  19. cmdbox/cli/completions/tags.py +24 -0
  20. cmdbox/cli/completions/variables.py +26 -0
  21. cmdbox/cli/handlers/__init__.py +0 -0
  22. cmdbox/cli/handlers/command_handlers.py +357 -0
  23. cmdbox/cli/handlers/common_handlers.py +15 -0
  24. cmdbox/cli/handlers/history_handlers.py +94 -0
  25. cmdbox/cli/handlers/init_handler.py +127 -0
  26. cmdbox/cli/handlers/run_handler.py +178 -0
  27. cmdbox/cli/handlers/settings_handler.py +59 -0
  28. cmdbox/cli/handlers/tag_handlers.py +220 -0
  29. cmdbox/cli/handlers/variable_handlers.py +272 -0
  30. cmdbox/cli/prompts/__init__.py +0 -0
  31. cmdbox/cli/prompts/completers.py +161 -0
  32. cmdbox/cli/prompts/prompts.py +108 -0
  33. cmdbox/cli/prompts/validators.py +46 -0
  34. cmdbox/cli/ui/__init__.py +0 -0
  35. cmdbox/cli/ui/console.py +31 -0
  36. cmdbox/cli/ui/editor.py +141 -0
  37. cmdbox/cli/ui/presenters/__init__.py +0 -0
  38. cmdbox/cli/ui/presenters/app_presenter.py +8 -0
  39. cmdbox/cli/ui/presenters/command_presenter.py +168 -0
  40. cmdbox/cli/ui/presenters/history_presenter.py +83 -0
  41. cmdbox/cli/ui/presenters/init_instructions.py +52 -0
  42. cmdbox/cli/ui/presenters/init_presenter.py +57 -0
  43. cmdbox/cli/ui/presenters/result_presenter.py +144 -0
  44. cmdbox/cli/ui/presenters/settings_presenter.py +130 -0
  45. cmdbox/cli/ui/presenters/tag_presenter.py +97 -0
  46. cmdbox/cli/ui/presenters/variable_presenter.py +103 -0
  47. cmdbox/cli/ui/primitives.py +410 -0
  48. cmdbox/cli/ui/theme.py +43 -0
  49. cmdbox/cli/ui/theme_builder.py +49 -0
  50. cmdbox/common/__init__.py +0 -0
  51. cmdbox/common/io.py +34 -0
  52. cmdbox/container.py +156 -0
  53. cmdbox/core/__init__.py +0 -0
  54. cmdbox/core/fields.py +48 -0
  55. cmdbox/core/paths.py +52 -0
  56. cmdbox/database.py +65 -0
  57. cmdbox/exceptions.py +10 -0
  58. cmdbox/init/__init__.py +0 -0
  59. cmdbox/init/detect.py +82 -0
  60. cmdbox/init/integrations/bash.sh +10 -0
  61. cmdbox/init/integrations/cmd.bat +14 -0
  62. cmdbox/init/integrations/fish.fish +11 -0
  63. cmdbox/init/integrations/powershell.ps1 +14 -0
  64. cmdbox/init/integrations/zsh.sh +10 -0
  65. cmdbox/init/io.py +68 -0
  66. cmdbox/init/specs.py +54 -0
  67. cmdbox/logging_setup/__init__.py +0 -0
  68. cmdbox/logging_setup/log_config.py +123 -0
  69. cmdbox/logging_setup/log_decorators.py +40 -0
  70. cmdbox/logging_setup/log_handlers.py +94 -0
  71. cmdbox/migrations/__init__.py +1 -0
  72. cmdbox/migrations/errors.py +10 -0
  73. cmdbox/migrations/runner.py +127 -0
  74. cmdbox/migrations/versions/__init__.py +0 -0
  75. cmdbox/models.py +165 -0
  76. cmdbox/repositories/__init__.py +0 -0
  77. cmdbox/repositories/base_repository.py +181 -0
  78. cmdbox/repositories/command_repository.py +391 -0
  79. cmdbox/repositories/errors.py +120 -0
  80. cmdbox/repositories/history_repository.py +155 -0
  81. cmdbox/repositories/results.py +37 -0
  82. cmdbox/repositories/tag_repository.py +91 -0
  83. cmdbox/repositories/validators.py +256 -0
  84. cmdbox/repositories/variable_repository.py +324 -0
  85. cmdbox/resolve/__init__.py +0 -0
  86. cmdbox/resolve/errors.py +65 -0
  87. cmdbox/resolve/lookup.py +137 -0
  88. cmdbox/resolve/resolver.py +402 -0
  89. cmdbox/resolve/type_defs.py +96 -0
  90. cmdbox/runtime/__init__.py +0 -0
  91. cmdbox/runtime/executor.py +454 -0
  92. cmdbox/runtime/results.py +25 -0
  93. cmdbox/runtime/shell.py +90 -0
  94. cmdbox/services/__init__.py +0 -0
  95. cmdbox/services/command_services.py +261 -0
  96. cmdbox/services/errors.py +37 -0
  97. cmdbox/services/field_selection.py +162 -0
  98. cmdbox/services/history_service.py +68 -0
  99. cmdbox/services/run_service.py +204 -0
  100. cmdbox/services/tag_services.py +134 -0
  101. cmdbox/services/variable_services.py +224 -0
  102. cmdbox/settings/__init__.py +0 -0
  103. cmdbox/settings/models.py +129 -0
  104. cmdbox/settings/settings_repository.py +36 -0
  105. cmdbox/settings/settings_service.py +144 -0
  106. cmdbox/version.py +1 -0
  107. cmdbox_cli-1.0.0.dist-info/METADATA +125 -0
  108. cmdbox_cli-1.0.0.dist-info/RECORD +112 -0
  109. cmdbox_cli-1.0.0.dist-info/WHEEL +5 -0
  110. cmdbox_cli-1.0.0.dist-info/entry_points.txt +2 -0
  111. cmdbox_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
  112. cmdbox_cli-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,410 @@
1
+ from dataclasses import dataclass
2
+ from datetime import date, datetime
3
+ from typing import Any, Mapping, Sequence
4
+
5
+ from rich import box
6
+ from rich.align import Align
7
+ from rich.console import Group, RenderableType
8
+ from rich.padding import Padding
9
+ from rich.panel import Panel
10
+ from rich.rule import Rule
11
+ from rich.table import Table
12
+ from rich.text import Text
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class UiStyles:
17
+ # General
18
+ title: str = "ui.title"
19
+ subtitle: str = "ui.subtitle"
20
+ muted: str = "ui.muted"
21
+ dim: str = "ui.dim"
22
+
23
+ # Status
24
+ success: str = "status.success"
25
+ info: str = "status.info"
26
+ warning: str = "status.warning"
27
+ error: str = "status.error"
28
+
29
+ # Panels / borders
30
+ panel_border: str = "ui.border"
31
+ panel_title: str = "ui.panel_title"
32
+
33
+ # Tables
34
+ table_header: str = "ui.table_header"
35
+ table_caption: str = "ui.caption"
36
+ kv_key: str = "ui.kv.key"
37
+ kv_value: str = "ui.kv.value"
38
+
39
+ # Code-ish output (semantic)
40
+ code: str = "code"
41
+ code_inline: str = "code.inline"
42
+ code_block: str = "code.block"
43
+
44
+
45
+ def to_text(value: Any, *, style: str | None = None) -> Text:
46
+ """
47
+ Converts a given value into a formatted textual representation, optionally applying
48
+ a text style.
49
+
50
+ The function takes various types of input (e.g., None, datetime, date, or other types)
51
+ and returns a corresponding textual representation as a `Text` object. If a style
52
+ is specified, it will stylize the output text.
53
+
54
+ Args:
55
+ value: The input value to convert. Can be of type None, datetime, date,
56
+ or any type that can be converted into a string representation.
57
+ style: An optional string specifying the style to apply to the output text.
58
+
59
+ Returns:
60
+ Text: A formatted textual representation of the input value.
61
+ """
62
+ if value is None:
63
+ t = Text("null")
64
+ elif isinstance(value, Text):
65
+ if style:
66
+ value.stylize(style)
67
+ return value
68
+ elif isinstance(value, datetime):
69
+ t = Text(value.isoformat(sep=" ", timespec="seconds"))
70
+ elif isinstance(value, date):
71
+ t = Text(value.isoformat())
72
+ else:
73
+ t = Text(str(value))
74
+
75
+ if style:
76
+ t.stylize(style)
77
+ return t
78
+
79
+
80
+ def spacer(lines: int = 1) -> RenderableType:
81
+ return Text("\n" * max(0, lines))
82
+
83
+
84
+ def divider(title: str | None = None, *, style: str = "ui.dim") -> Rule:
85
+ if title:
86
+ return Rule(Text(title), style=style)
87
+ return Rule(style=style)
88
+
89
+
90
+ def col(name: str, *, style: str = "", **kwargs) -> tuple[str, dict[str, Any]]:
91
+ """
92
+ A helper function that generates a table column.
93
+
94
+ This function facilitates the creation of a tuple that consists of a string name and a
95
+ dictionary. The dictionary contains a "style" key with a specified value and may also include
96
+ other key-value pairs provided as additional arguments.
97
+
98
+ Args:
99
+ name (str): The name or identifier for the column.
100
+ style (str, optional): A string specifying the style attribute. Defaults to an empty string.
101
+ **kwargs: Additional key-value pairs to include in the dictionary.
102
+
103
+ Returns:
104
+ tuple[str, dict[str, Any]]: A tuple where the first element is the provided name, and the
105
+ second element is a dictionary containing the "style" attribute along with any additional
106
+ key-value pairs.
107
+ """
108
+ data = {"style": style}
109
+ data.update(kwargs)
110
+ return name, data
111
+
112
+
113
+ def banner(
114
+ title: str,
115
+ subtitle: str | None = None,
116
+ *,
117
+ status: str | None = None,
118
+ styles: UiStyles = UiStyles(),
119
+ ) -> RenderableType:
120
+ """
121
+ A consistent header block.
122
+
123
+ status: one of "success" | "info" | "warning" | "error" or a Theme style name
124
+ """
125
+ if status:
126
+ # Allow semantic names or full style names
127
+ status_style = getattr(styles, status, status)
128
+ border_style = status_style
129
+ title_style = status_style
130
+ else:
131
+ border_style = styles.panel_border
132
+ title_style = styles.title
133
+
134
+ title_text = Text(title, style=title_style)
135
+
136
+ parts: list[RenderableType] = [title_text]
137
+
138
+ if subtitle:
139
+ parts.append(Text(subtitle, style=styles.subtitle))
140
+
141
+ return Panel(
142
+ Group(*parts),
143
+ border_style=border_style,
144
+ padding=(0, 1),
145
+ )
146
+
147
+
148
+ def tag_pill(
149
+ name: str,
150
+ *,
151
+ style: str = "tag.pill",
152
+ padding: tuple[int, int] = (0, 1),
153
+ ) -> RenderableType:
154
+ text = Text(name, style=style)
155
+ return Padding(text, padding)
156
+
157
+
158
+ def tag_block(
159
+ tags: Sequence[str],
160
+ *,
161
+ style: str = "tag.pill",
162
+ separator: str = " ",
163
+ ) -> RenderableType:
164
+ text = Text()
165
+
166
+ for i, tag in enumerate(tags):
167
+ if i > 0:
168
+ text.append(separator)
169
+ text.append(f" {tag} ", style=style)
170
+
171
+ return text
172
+
173
+
174
+ def status_line(
175
+ message: str,
176
+ *,
177
+ status: str = "info",
178
+ styles: UiStyles = UiStyles(),
179
+ ) -> Text:
180
+ """
181
+ One line status message with semantic styling.
182
+ """
183
+ status_style = getattr(styles, status, status)
184
+ return Text(message, style=status_style)
185
+
186
+
187
+ def bullet_list(
188
+ items: Sequence[Any],
189
+ *,
190
+ bullet: str = "•",
191
+ item_style: str = "ui.kv.value",
192
+ bullet_style: str = "ui.muted",
193
+ styles: UiStyles = UiStyles(),
194
+ empty_message: str | None = None,
195
+ ) -> RenderableType:
196
+ """
197
+ Simple bullet list that is safe against markup.
198
+ """
199
+ if not items:
200
+ if empty_message is None:
201
+ return Text("")
202
+ return Text(empty_message, style=styles.muted)
203
+
204
+ lines: list[RenderableType] = []
205
+ for item in items:
206
+ b = Text(f"{bullet} ", style=bullet_style if bullet_style else styles.muted)
207
+ v = to_text(item, style=item_style if item_style else styles.kv_value)
208
+ lines.append(Text.assemble(b, v))
209
+
210
+ return Group(*lines)
211
+
212
+
213
+ def kv_table(
214
+ rows: Sequence[tuple[str, Any]],
215
+ *,
216
+ key_style: str = "ui.kv.key",
217
+ value_style: str = "ui.kv.value",
218
+ show_header: bool = False,
219
+ styles: UiStyles = UiStyles(),
220
+ ) -> Table:
221
+ """
222
+ Two-column key/value table for detail views.
223
+ """
224
+ table = Table(
225
+ show_header=show_header,
226
+ box=None,
227
+ pad_edge=False,
228
+ padding=(0, 1),
229
+ collapse_padding=True,
230
+ )
231
+
232
+ if show_header:
233
+ table.add_column("Field", style=styles.table_header)
234
+ table.add_column("Value", style=styles.table_header)
235
+ else:
236
+ table.add_column(
237
+ justify="right", style=key_style or styles.kv_key, no_wrap=True
238
+ )
239
+ table.add_column(style=value_style or styles.kv_value, overflow="fold")
240
+
241
+ for k, v in rows:
242
+ table.add_row(
243
+ to_text(k, style=key_style or styles.kv_key),
244
+ to_text(v, style=value_style or styles.kv_value),
245
+ )
246
+
247
+ return table
248
+
249
+
250
+ def kv_panel(
251
+ title: str,
252
+ rows: Sequence[tuple[str, Any]],
253
+ *,
254
+ border_style: str = "ui.border",
255
+ styles: UiStyles = UiStyles(),
256
+ ) -> Panel:
257
+ """
258
+ Panel containing a key/value table.
259
+ """
260
+ return Panel(
261
+ kv_table(rows, styles=styles),
262
+ title=Text(title, style=styles.panel_title),
263
+ border_style=border_style,
264
+ padding=(0, 1),
265
+ )
266
+
267
+
268
+ def table_panel(
269
+ title: str,
270
+ columns: Sequence[tuple[str, dict[str, Any]]],
271
+ rows: Sequence[Sequence[Any]],
272
+ *,
273
+ caption: str | None = None,
274
+ border_style: str = "ui.border",
275
+ styles: UiStyles = UiStyles(),
276
+ ) -> Panel:
277
+ """
278
+ Build a standard table inside a panel.
279
+
280
+ columns: [("Alias", {"style": "...", "no_wrap": True}), ...]
281
+ rows: list of row sequences. Each cell is converted with to_text.
282
+ """
283
+ table = Table(
284
+ show_header=True,
285
+ header_style=styles.table_header,
286
+ pad_edge=False,
287
+ collapse_padding=True,
288
+ box=box.MINIMAL,
289
+ )
290
+
291
+ for name, kwargs in columns:
292
+ table.add_column(name, **kwargs)
293
+
294
+ for row in rows:
295
+ table.add_row(*[to_text(cell) for cell in row])
296
+
297
+ if caption:
298
+ table.caption = caption
299
+ table.caption_style = styles.table_caption
300
+
301
+ return Panel(
302
+ table,
303
+ title=Text(title, style=styles.panel_title),
304
+ border_style=border_style,
305
+ padding=(0, 1),
306
+ )
307
+
308
+
309
+ def code_inline(
310
+ text: Any,
311
+ *,
312
+ style: str = "code.inline",
313
+ styles: UiStyles = UiStyles(),
314
+ ) -> Text:
315
+ """
316
+ Semantic inline code styling. Does not change terminal font, but gives a consistent look.
317
+ """
318
+ # If you do not define code.inline, fall back to code, then to a generic style.
319
+ resolved = style
320
+ if style == "code.inline":
321
+ resolved = styles.code_inline or styles.code or "bold"
322
+ return to_text(text, style=resolved)
323
+
324
+
325
+ def code_block(
326
+ text: Any,
327
+ *,
328
+ title: str | None = None,
329
+ border_style: str = "ui.border",
330
+ style: str = "code.block",
331
+ styles: UiStyles = UiStyles(),
332
+ fit: bool = False,
333
+ ) -> Panel:
334
+ """
335
+ Panel for code-ish blocks (templates, resolved commands, previews).
336
+ """
337
+ resolved = style
338
+ if style == "code.block":
339
+ resolved = styles.code_block or styles.code or "bold"
340
+
341
+ body = to_text(text, style=resolved)
342
+
343
+ panel_title = Text(title, style=styles.panel_title) if title else None
344
+ panel_cls = Panel.fit if fit else Panel
345
+
346
+ return panel_cls(
347
+ body,
348
+ title=panel_title,
349
+ border_style=border_style,
350
+ padding=(1, 1),
351
+ )
352
+
353
+
354
+ def centered(renderable: RenderableType) -> RenderableType:
355
+ return Align.center(renderable)
356
+
357
+
358
+ def stack(*renderables: RenderableType) -> RenderableType:
359
+ """
360
+ Group renderables vertically.
361
+ """
362
+ return Group(*renderables)
363
+
364
+
365
+ def section(
366
+ title: str,
367
+ body: RenderableType,
368
+ caption: str | None = None,
369
+ *,
370
+ border_style: str = "ui.border",
371
+ styles: UiStyles = UiStyles(),
372
+ ) -> Panel:
373
+ """
374
+ Generic titled panel wrapper.
375
+ """
376
+ return Panel(
377
+ body,
378
+ title=Text(title, style=styles.panel_title),
379
+ subtitle=Text(caption, style="ui.caption") if caption else None,
380
+ border_style=border_style,
381
+ padding=(0, 1),
382
+ )
383
+
384
+
385
+ def summary_counts(
386
+ counts: Mapping[str, int],
387
+ *,
388
+ styles: UiStyles = UiStyles(),
389
+ ) -> RenderableType:
390
+ """
391
+ Small helper for displaying a few counts, like:
392
+ Commands: 12 Variables: 4 Tags: 9
393
+ """
394
+ parts: list[Text] = []
395
+ first = True
396
+ for label, count in counts.items():
397
+ if not first:
398
+ parts.append(Text(" ", style=styles.muted))
399
+ first = False
400
+ parts.append(Text(f"{label}:", style=styles.muted))
401
+ parts.append(Text(" ", style=styles.muted))
402
+ parts.append(Text(str(count), style=styles.title))
403
+
404
+ return Text.assemble(*parts)
405
+
406
+
407
+ def pluralize(count: int, singular: str, plural: str | None = None) -> str:
408
+ if count == 1:
409
+ return f"1 {singular}"
410
+ return f"{count} {plural or (singular + 's')}"
cmdbox/cli/ui/theme.py ADDED
@@ -0,0 +1,43 @@
1
+ from rich.theme import Theme
2
+
3
+ CMDBOX_THEME = Theme(
4
+ {
5
+ # Core text
6
+ "ui.title": "bold",
7
+ "ui.subtitle": "dim",
8
+ "ui.muted": "dim",
9
+ "ui.dim": "dim",
10
+ "ui.border": "dim",
11
+ "ui.panel_title": "bold",
12
+ # Tables
13
+ "ui.table_header": "bold",
14
+ "ui.caption": "dim",
15
+ # Key/value panels
16
+ "ui.kv.key": "dim",
17
+ "ui.kv.value": "",
18
+ # Status
19
+ "status.success": "bold green",
20
+ "status.info": "bold cyan",
21
+ "status.warning": "bold yellow",
22
+ "status.error": "bold red",
23
+ # Code semantics (not a font switch, just a consistent code look)
24
+ "code": "cyan",
25
+ "code.inline": "cyan",
26
+ "code.block": "cyan",
27
+ # Common entity accents (optional, keep minimal)
28
+ "entity.name": "bold",
29
+ "entity.id": "magenta",
30
+ "entity.count": "bold",
31
+ "entity.time": "dim",
32
+ # Tags
33
+ "tag.pill": "bold white on dark_green",
34
+ "tag.pill_muted": "white on grey23",
35
+ # Execution and previews
36
+ "run.command": "cyan",
37
+ "run.stdout": "",
38
+ "run.stderr": "bold red",
39
+ "run.trace.kind": "dim",
40
+ "run.trace.key": "magenta",
41
+ "run.trace.value": "cyan",
42
+ }
43
+ )
@@ -0,0 +1,49 @@
1
+ from rich.theme import Theme as RichTheme
2
+
3
+ from cmdbox.settings.models import Settings
4
+
5
+
6
+ def build_theme(settings: Settings) -> RichTheme:
7
+ c = settings.ui.colors
8
+
9
+ style_map = {
10
+ "ui.title": c.title,
11
+ "ui.subtitle": c.subtitle,
12
+ "ui.muted": c.muted,
13
+ "ui.dim": c.muted,
14
+ "ui.border": c.border,
15
+ "ui.panel_title": c.panel_title,
16
+ # Tables
17
+ "ui.table_header": c.table_header,
18
+ "ui.caption": c.caption,
19
+ # Key/value panels
20
+ "ui.kv.key": c.kv_key,
21
+ "ui.kv.value": c.kv_value,
22
+ # Status
23
+ "status.success": c.success,
24
+ "status.info": c.info,
25
+ "status.warning": c.warning,
26
+ "status.error": c.error,
27
+ "status.debug": c.debug,
28
+ # Code semantics (not a font switch, just a consistent code look)
29
+ "code": c.code,
30
+ "code.inline": c.code_inline,
31
+ "code.block": c.code_block,
32
+ # Common entity accents (optional, keep minimal)
33
+ "entity.name": c.entity_name,
34
+ "entity.id": c.entity_id,
35
+ "entity.count": c.entity_count,
36
+ "entity.time": c.entity_time,
37
+ # Tags
38
+ "tag.pill": c.tag_pill,
39
+ "tag.pill_muted": c.tag_pill_muted,
40
+ # Execution and previews
41
+ "run.command": c.run_command,
42
+ "run.stdout": c.run_stdout,
43
+ "run.stderr": c.run_stderr,
44
+ "run.trace.kind": c.trace_kind,
45
+ "run.trace.key": c.trace_key,
46
+ "run.trace.value": c.trace_value,
47
+ }
48
+
49
+ return RichTheme(style_map)
File without changes
cmdbox/common/io.py ADDED
@@ -0,0 +1,34 @@
1
+ import os
2
+ import tempfile
3
+ from pathlib import Path
4
+
5
+ from cmdbox.logging_setup.log_decorators import log_action
6
+
7
+
8
+ @log_action(__name__, "atomic_write_text")
9
+ def atomic_write_text(dest: Path, text: str, encoding: str = "utf-8") -> None:
10
+ """
11
+ Performs an atomic write operation to a text file. This ensures that the file content is written
12
+ to a temporary file first and then atomically replaces the original file to prevent partial writes
13
+ or data corruption.
14
+
15
+ Args:
16
+ dest (Path): The destination path for the final output file.
17
+ text (str): The text content to write to the file.
18
+
19
+ """
20
+ dest.parent.mkdir(parents=True, exist_ok=True)
21
+ with tempfile.NamedTemporaryFile(
22
+ mode="w",
23
+ encoding=encoding,
24
+ dir=str(dest.parent),
25
+ delete=False,
26
+ prefix=dest.name + ".",
27
+ suffix=".tmp",
28
+ newline="\n",
29
+ ) as tf:
30
+ tmp_path = Path(tf.name)
31
+ tf.write(text)
32
+ tf.flush()
33
+ os.fsync(tf.fileno())
34
+ tmp_path.replace(dest)
cmdbox/container.py ADDED
@@ -0,0 +1,156 @@
1
+ from functools import lru_cache
2
+
3
+ from cmdbox.cli.ui.console import ConsoleUI
4
+ from cmdbox.cli.ui.theme_builder import build_theme
5
+ from cmdbox.core.fields import (
6
+ COMMAND_DISPLAY_FIELDS,
7
+ COMMAND_SEARCH_FIELDS,
8
+ VARIABLE_DISPLAY_FIELDS,
9
+ VARIABLE_SEARCH_FIELDS,
10
+ TAG_DISPLAY_FIELDS,
11
+ TAG_SEARCH_FIELDS,
12
+ )
13
+ from cmdbox.repositories.history_repository import HistoryRepository
14
+ from cmdbox.services.field_selection import FieldSelectionResolver
15
+ from cmdbox.services.history_service import HistoryService
16
+ from cmdbox.services.variable_services import VariableServices
17
+ from cmdbox.settings.models import Settings
18
+ from cmdbox.settings.settings_repository import SettingsRepository
19
+ from cmdbox.settings.settings_service import SettingsService
20
+ from cmdbox.core.paths import get_app_data_dir
21
+ from cmdbox.database import get_db
22
+ from cmdbox.repositories.command_repository import CommandRepository
23
+ from cmdbox.repositories.tag_repository import TagRepository
24
+ from cmdbox.repositories.variable_repository import VariableRepository
25
+ from cmdbox.resolve.lookup import MemoizedLookup, RepoLookup
26
+ from cmdbox.resolve.resolver import Resolver
27
+ from cmdbox.runtime.executor import Executor
28
+ from cmdbox.services.command_services import CommandServices
29
+ from cmdbox.services.run_service import RunService
30
+ from cmdbox.services.tag_services import TagServices
31
+
32
+
33
+ @lru_cache(maxsize=1)
34
+ def get_settings_service() -> SettingsService:
35
+ config_path = get_app_data_dir() / "config.toml"
36
+ repo = SettingsRepository(config_path)
37
+ settings = SettingsService(repo)
38
+ return settings
39
+
40
+
41
+ def get_settings() -> Settings:
42
+ return get_settings_service().get()
43
+
44
+
45
+ @lru_cache(maxsize=1)
46
+ def get_console() -> ConsoleUI:
47
+ _settings = get_settings()
48
+ theme = build_theme(_settings)
49
+ return ConsoleUI(theme)
50
+
51
+
52
+ @lru_cache(maxsize=1)
53
+ def get_command_repo() -> CommandRepository:
54
+ return CommandRepository()
55
+
56
+
57
+ @lru_cache(maxsize=1)
58
+ def get_variable_repo() -> VariableRepository:
59
+ return VariableRepository()
60
+
61
+
62
+ @lru_cache(maxsize=1)
63
+ def get_tag_repo() -> TagRepository:
64
+ return TagRepository()
65
+
66
+
67
+ @lru_cache(maxsize=1)
68
+ def get_history_repo() -> HistoryRepository:
69
+ get_db()
70
+ return HistoryRepository()
71
+
72
+
73
+ @lru_cache(maxsize=1)
74
+ def get_resolver(strict: bool = False) -> Resolver:
75
+ get_db()
76
+ command_repo = get_command_repo()
77
+ variable_repo = get_variable_repo()
78
+ repo_lookup = RepoLookup(command_repo, variable_repo)
79
+ lookup = MemoizedLookup(repo_lookup)
80
+ return Resolver(lookup, strict=strict)
81
+
82
+
83
+ @lru_cache(maxsize=1)
84
+ def get_run_service() -> RunService:
85
+ get_db()
86
+ cmd_repo = get_command_repo()
87
+ resolver = get_resolver()
88
+ executor = Executor()
89
+ return RunService(
90
+ cmd_repo,
91
+ resolver,
92
+ executor,
93
+ history_repo=get_history_repo(),
94
+ get_settings=get_settings,
95
+ )
96
+
97
+
98
+ @lru_cache(maxsize=1)
99
+ def get_command_services() -> CommandServices:
100
+ get_db()
101
+ cmd_repo = get_command_repo()
102
+ tag_repo = get_tag_repo()
103
+ return CommandServices(command_repository=cmd_repo, tag_repository=tag_repo)
104
+
105
+
106
+ @lru_cache(maxsize=1)
107
+ def get_variable_services() -> VariableServices:
108
+ get_db()
109
+ var_repo = get_variable_repo()
110
+ tag_repo = get_tag_repo()
111
+ return VariableServices(variable_repository=var_repo, tag_repository=tag_repo)
112
+
113
+
114
+ @lru_cache(maxsize=1)
115
+ def get_tag_services() -> TagServices:
116
+ get_db()
117
+ tag_repo = get_tag_repo()
118
+ return TagServices(tag_repository=tag_repo)
119
+
120
+
121
+ @lru_cache(maxsize=1)
122
+ def get_history_service() -> HistoryService:
123
+ return HistoryService(
124
+ repo=get_history_repo(),
125
+ get_settings=get_settings,
126
+ )
127
+
128
+
129
+ @lru_cache(maxsize=1)
130
+ def get_command_display_field_resolver() -> FieldSelectionResolver:
131
+ return FieldSelectionResolver(allowed_fields=COMMAND_DISPLAY_FIELDS)
132
+
133
+
134
+ @lru_cache(maxsize=1)
135
+ def get_command_search_field_resolver() -> FieldSelectionResolver:
136
+ return FieldSelectionResolver(allowed_fields=COMMAND_SEARCH_FIELDS)
137
+
138
+
139
+ @lru_cache(maxsize=1)
140
+ def get_variable_display_field_resolver() -> FieldSelectionResolver:
141
+ return FieldSelectionResolver(allowed_fields=VARIABLE_DISPLAY_FIELDS)
142
+
143
+
144
+ @lru_cache(maxsize=1)
145
+ def get_variable_search_field_resolver() -> FieldSelectionResolver:
146
+ return FieldSelectionResolver(allowed_fields=VARIABLE_SEARCH_FIELDS)
147
+
148
+
149
+ @lru_cache(maxsize=1)
150
+ def get_tag_display_field_resolver() -> FieldSelectionResolver:
151
+ return FieldSelectionResolver(allowed_fields=TAG_DISPLAY_FIELDS)
152
+
153
+
154
+ @lru_cache(maxsize=1)
155
+ def get_tag_search_field_resolver() -> FieldSelectionResolver:
156
+ return FieldSelectionResolver(allowed_fields=TAG_SEARCH_FIELDS)
File without changes