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,277 @@
1
+ import logging
2
+ from typing import Annotated
3
+
4
+ import typer
5
+
6
+ from cmdbox import container
7
+ from cmdbox.cli.common.errors import make_cli_guard
8
+ from cmdbox.cli.completions.fields import tag_editable_field_options, tag_field_options
9
+ from cmdbox.cli.completions.tags import complete_tag_names
10
+ from cmdbox.cli.handlers import tag_handlers
11
+
12
+ app = typer.Typer(no_args_is_help=True)
13
+
14
+ cli_guard = make_cli_guard(container.get_console)
15
+
16
+ log = logging.getLogger(__name__)
17
+
18
+
19
+ @app.command("add")
20
+ @cli_guard
21
+ def add(
22
+ name: Annotated[
23
+ str,
24
+ typer.Argument(help="The name of the tag.", autocompletion=complete_tag_names),
25
+ ] = None,
26
+ description: Annotated[
27
+ str, typer.Argument(help="A description of the tag.")
28
+ ] = None,
29
+ interactive: Annotated[
30
+ bool,
31
+ typer.Option(
32
+ "--interactive",
33
+ "-i",
34
+ is_flag=True,
35
+ help="Prompt for tag details interactively.",
36
+ ),
37
+ ] = False,
38
+ ) -> None:
39
+ """
40
+ Adds a new tag with a name and description. The tag can be created in
41
+ interactive if no options are provided or the `--interactive` flag is
42
+ used.
43
+ """
44
+ log.debug(
45
+ "tag.add called. name=%s, description=%s, interactive=%s",
46
+ name,
47
+ description,
48
+ interactive,
49
+ )
50
+ add_tag_args = tag_handlers.AddTagArgs(
51
+ name=name, description=description, interactive=interactive
52
+ )
53
+ tag_handlers.run_add_tag(
54
+ args=add_tag_args,
55
+ get_tag_services=container.get_tag_services,
56
+ get_console=container.get_console,
57
+ )
58
+
59
+
60
+ @app.command("get")
61
+ @cli_guard
62
+ def get(
63
+ name: Annotated[
64
+ str,
65
+ typer.Argument(
66
+ help="The name of the tag to retrieve.", autocompletion=complete_tag_names
67
+ ),
68
+ ],
69
+ ) -> None:
70
+ """
71
+ Gets and displays a saved tag under the provided name.
72
+ """
73
+ log.debug("tag.get called. name=%s", name)
74
+ tag_handlers.run_get_tag(
75
+ name=name,
76
+ get_tag_services=container.get_tag_services,
77
+ get_console=container.get_console,
78
+ )
79
+
80
+
81
+ @app.command("update")
82
+ @cli_guard
83
+ def update(
84
+ name: Annotated[
85
+ str,
86
+ typer.Argument(
87
+ help="The name of the tag to update.", autocompletion=complete_tag_names
88
+ ),
89
+ ],
90
+ description: Annotated[
91
+ str,
92
+ typer.Option("--description", "-d", help="The new description of the tag."),
93
+ ] = None,
94
+ new_name: Annotated[
95
+ str,
96
+ typer.Option("--name", "-n", help="The new name of the tag."),
97
+ ] = None,
98
+ set_: Annotated[
99
+ list[str],
100
+ typer.Option(
101
+ "--set",
102
+ "-s",
103
+ help="A list of key=value pairs to update.",
104
+ autocompletion=tag_editable_field_options,
105
+ ),
106
+ ] = None,
107
+ edit_mode: Annotated[
108
+ bool,
109
+ typer.Option("--edit", "-e", help="Edit mode."),
110
+ ] = False,
111
+ edit_fields: Annotated[
112
+ str,
113
+ typer.Option(
114
+ "--edit-fields",
115
+ "-ef",
116
+ help="A list of fields to be edited in edit mode, separated by commas. Defaults to all fields.",
117
+ ),
118
+ ] = None,
119
+ ) -> None:
120
+ """
121
+ Updates an existing tag with the provided options. Each field can be updated
122
+ individually or in bulk using the `--set` option. Using the `--edit` option enables
123
+ editing the already stored values in an interactive mode.
124
+ """
125
+ log.debug(
126
+ "tag.update called. name=%s, description=%s, new_name=%s, set_pairs=%s, edit_mode=%s, edit_fields=%s",
127
+ name,
128
+ description,
129
+ new_name,
130
+ set_,
131
+ edit_mode,
132
+ edit_fields,
133
+ )
134
+ tag_handlers.run_update_tag(
135
+ name=name,
136
+ description=description,
137
+ new_name=new_name,
138
+ set_pairs=set_,
139
+ edit_mode=edit_mode,
140
+ edit_fields=edit_fields,
141
+ get_tag_services=container.get_tag_services,
142
+ get_settings=container.get_settings,
143
+ get_console=container.get_console,
144
+ )
145
+
146
+
147
+ app.command("edit", hidden=True)(update)
148
+
149
+
150
+ @app.command("list")
151
+ @cli_guard
152
+ def list_tags(
153
+ order: Annotated[
154
+ str,
155
+ typer.Option(
156
+ "--order",
157
+ "-o",
158
+ help="The field to order the results by.",
159
+ autocompletion=tag_field_options,
160
+ ),
161
+ ] = "name",
162
+ limit: Annotated[
163
+ int,
164
+ typer.Option("--limit", "-l", help="The maximum number of results to return."),
165
+ ] = 10,
166
+ fields: Annotated[
167
+ list[str] | None,
168
+ typer.Option(
169
+ "--field",
170
+ "-f",
171
+ help="The fields to display in the results list.",
172
+ autocompletion=tag_field_options,
173
+ ),
174
+ ] = None,
175
+ ) -> None:
176
+ """
177
+ Displays all stored tags in a list format. The number of results can be limited
178
+ with the `--limit` option. The output fields can be customized with the `--field` option.
179
+ """
180
+ log.debug("tag.list called. order=%s, limit=%s, fields=%s", order, limit, fields)
181
+ tag_handlers.run_list_tags(
182
+ limit=limit,
183
+ fields=fields,
184
+ order_by=order,
185
+ get_tag_services=container.get_tag_services,
186
+ get_settings=container.get_settings,
187
+ get_console=container.get_console,
188
+ get_display_field_resolver=container.get_tag_display_field_resolver,
189
+ )
190
+
191
+
192
+ app.command("ls", hidden=True)(list_tags)
193
+
194
+
195
+ @app.command("search")
196
+ @cli_guard
197
+ def search(
198
+ term: Annotated[
199
+ str,
200
+ typer.Argument(
201
+ help="The search term to use.", autocompletion=complete_tag_names
202
+ ),
203
+ ],
204
+ limit: Annotated[
205
+ int, typer.Option(help="The maximum number of results to return.")
206
+ ] = 10,
207
+ search_fields: Annotated[
208
+ list[str],
209
+ typer.Option(
210
+ "--in",
211
+ "-i",
212
+ help="The fields to search within.",
213
+ autocompletion=tag_field_options,
214
+ ),
215
+ ] = None,
216
+ fields: Annotated[
217
+ list[str] | None,
218
+ typer.Option(
219
+ "--field",
220
+ "-f",
221
+ help="The field(s) to display in the results list. Defaults to all fields.",
222
+ autocompletion=tag_field_options,
223
+ ),
224
+ ] = None,
225
+ ) -> None:
226
+ """
227
+ Searches the database for tags matching the provided search term. The search fields
228
+ can be customized with the `--in` option. The output fields can be customized with the
229
+ `--field` option.
230
+ """
231
+ log.debug(
232
+ "tag.search called. term=%s, limit=%s, search_fields=%s, fields=%s",
233
+ term,
234
+ limit,
235
+ search_fields,
236
+ fields,
237
+ )
238
+ tag_handlers.run_search_tags(
239
+ term=term,
240
+ limit=limit,
241
+ search_fields=search_fields,
242
+ fields=fields,
243
+ get_tag_services=container.get_tag_services,
244
+ get_settings=container.get_settings,
245
+ get_console=container.get_console,
246
+ get_display_field_resolver=container.get_tag_display_field_resolver,
247
+ get_search_field_resolver=container.get_tag_search_field_resolver,
248
+ )
249
+
250
+
251
+ app.command("find", hidden=True)(search)
252
+
253
+
254
+ @app.command("delete")
255
+ @cli_guard
256
+ def delete(
257
+ name: Annotated[
258
+ str,
259
+ typer.Argument(
260
+ help="The name of the tag to delete.", autocompletion=complete_tag_names
261
+ ),
262
+ ],
263
+ ) -> None:
264
+ """
265
+ Deletes the tag stored under the provided name.
266
+ """
267
+ log.debug("tag.delete called. name=%s", name)
268
+ tag_handlers.run_delete_tag(
269
+ name=name,
270
+ get_tag_services=container.get_tag_services,
271
+ get_console=container.get_console,
272
+ )
273
+
274
+
275
+ app.command("del", hidden=True)(delete)
276
+ app.command("rm", hidden=True)(delete)
277
+ app.command("remove", hidden=True)(delete)
@@ -0,0 +1,349 @@
1
+ import logging
2
+ from typing import Annotated
3
+
4
+ import typer
5
+
6
+ from cmdbox import container
7
+ from cmdbox.cli.common.errors import make_cli_guard
8
+ from cmdbox.cli.completions.fields import variable_field_options
9
+ from cmdbox.cli.completions.variables import complete_variable_names
10
+ from cmdbox.cli.completions.tags import complete_tag_names
11
+ from cmdbox.cli.handlers import variable_handlers
12
+
13
+ app = typer.Typer(no_args_is_help=True)
14
+
15
+ cli_guard = make_cli_guard(container.get_console)
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+
20
+ @app.command("add")
21
+ @cli_guard
22
+ def add(
23
+ name: Annotated[str, typer.Argument(help="The name of the variable.")] = None,
24
+ value: Annotated[str, typer.Argument(help="The value of the variable.")] = None,
25
+ tags: Annotated[
26
+ list[str],
27
+ typer.Option(
28
+ "--tags",
29
+ "-t",
30
+ help="A list of tags to associate with the variable, separated by commas.",
31
+ autocompletion=complete_tag_names,
32
+ ),
33
+ ] = None,
34
+ interactive: Annotated[
35
+ bool,
36
+ typer.Option("--interactive", "-i", is_flag=True, help="Interactive mode."),
37
+ ] = False,
38
+ ) -> None:
39
+ """
40
+ Adds a new variable with the specified name, value, and tags. The variable
41
+ can be created in interactive mode if no options are provided or the `--interactive`
42
+ flag is used.
43
+ """
44
+ log.debug(
45
+ "var.add called. name=%s, value=%s, tags=%s, interactive=%s",
46
+ name,
47
+ value,
48
+ tags,
49
+ interactive,
50
+ )
51
+ add_var_args = variable_handlers.AddVariableArgs(
52
+ name=name, value=value, tags=tags, interactive=interactive
53
+ )
54
+ variable_handlers.run_add_variable(
55
+ args=add_var_args,
56
+ get_var_services=container.get_variable_services,
57
+ get_tag_services=container.get_tag_services,
58
+ get_console=container.get_console,
59
+ )
60
+
61
+
62
+ @app.command("get")
63
+ @cli_guard
64
+ def get(
65
+ name: Annotated[
66
+ str,
67
+ typer.Argument(
68
+ help="The name of the variable to retrieve.",
69
+ autocompletion=complete_variable_names,
70
+ ),
71
+ ],
72
+ ) -> None:
73
+ """
74
+ Retrieves and displays the variable stored under the provided name.
75
+ """
76
+ log.debug("var.get called. name=%s", name)
77
+ variable_handlers.run_get_variable(
78
+ name=name,
79
+ get_var_services=container.get_variable_services,
80
+ get_console=container.get_console,
81
+ )
82
+
83
+
84
+ @app.command("update")
85
+ @cli_guard
86
+ def update(
87
+ name: Annotated[
88
+ str,
89
+ typer.Argument(
90
+ help="The name of the variable to update.",
91
+ autocompletion=complete_variable_names,
92
+ ),
93
+ ],
94
+ value: Annotated[str, typer.Option("--value", "-v", help="The new value.")] = None,
95
+ new_name: Annotated[str, typer.Option("--name", "-n", help="The new name.")] = None,
96
+ set_: Annotated[
97
+ list[str],
98
+ typer.Option(
99
+ "--set",
100
+ "-s",
101
+ help="A list of key=value pairs to update.",
102
+ autocompletion=variable_field_options,
103
+ ),
104
+ ] = None,
105
+ edit_mode: Annotated[
106
+ bool,
107
+ typer.Option("--edit", "-e", help="Edit mode."),
108
+ ] = False,
109
+ edit_fields: Annotated[
110
+ str,
111
+ typer.Option(
112
+ "--edit-fields",
113
+ "-ef",
114
+ help="A list of fields to be edited in edit mode, separated by commas. Defaults to all fields.",
115
+ ),
116
+ ] = None,
117
+ ) -> None:
118
+ """
119
+ Updates an existing variable with the provided options. Each field can be updated
120
+ individually or in bulk using the `--set` option. Using the `--edit` option enables
121
+ editing the already stored values in an interactive mode.
122
+ """
123
+ log.debug(
124
+ "var.update called. name=%s, value=%s, new_name=%s, set_pairs=%s, edit_mode=%s, edit_fields=%s",
125
+ name,
126
+ value,
127
+ new_name,
128
+ set_,
129
+ edit_mode,
130
+ edit_fields,
131
+ )
132
+ variable_handlers.run_update_variable(
133
+ name=name,
134
+ value=value,
135
+ new_name=new_name,
136
+ set_pairs=set_,
137
+ edit_mode=edit_mode,
138
+ edit_fields=edit_fields,
139
+ get_var_services=container.get_variable_services,
140
+ get_settings=container.get_settings,
141
+ get_console=container.get_console,
142
+ )
143
+
144
+
145
+ app.command("edit", hidden=True)(update)
146
+
147
+
148
+ @app.command("list")
149
+ @cli_guard
150
+ def list_vars(
151
+ order: Annotated[
152
+ str,
153
+ typer.Option(
154
+ "--order",
155
+ "-o",
156
+ help="The field to order the results by.",
157
+ autocompletion=variable_field_options,
158
+ ),
159
+ ] = "name",
160
+ tags: Annotated[
161
+ list[str],
162
+ typer.Option(
163
+ "--tag",
164
+ "-t",
165
+ help="The tags to filter by.",
166
+ autocompletion=complete_tag_names,
167
+ ),
168
+ ] = None,
169
+ limit: Annotated[
170
+ int,
171
+ typer.Option("--limit", "-l", help="The maximum number of results to return."),
172
+ ] = 10,
173
+ fields: Annotated[
174
+ list[str] | None,
175
+ typer.Option(
176
+ "--field",
177
+ "-f",
178
+ help="The fields to display in the results list.",
179
+ autocompletion=variable_field_options,
180
+ ),
181
+ ] = None,
182
+ ) -> None:
183
+ """
184
+ Displays all stored variables in a list format. The number of results can be limited
185
+ with the `--limit` option. The output fields can be customized with the `--field` option.
186
+ """
187
+ log.debug(
188
+ "var.list called. order=%s, tags=%s, limit=%s, fields=%s",
189
+ order,
190
+ tags,
191
+ limit,
192
+ fields,
193
+ )
194
+ variable_handlers.run_list_variables(
195
+ order_by=order,
196
+ tags=tags,
197
+ limit=limit,
198
+ fields=fields,
199
+ get_var_services=container.get_variable_services,
200
+ get_settings=container.get_settings,
201
+ get_console=container.get_console,
202
+ get_display_field_resolver=container.get_variable_display_field_resolver,
203
+ )
204
+
205
+
206
+ app.command("ls", hidden=True)(list_vars)
207
+
208
+
209
+ @app.command("search")
210
+ @cli_guard
211
+ def search(
212
+ term: Annotated[str, typer.Argument(help="The search term to use.")],
213
+ limit: Annotated[
214
+ int, typer.Option(help="The maximum number of results to return.")
215
+ ] = 10,
216
+ search_fields: Annotated[
217
+ list[str],
218
+ typer.Option(
219
+ "--in",
220
+ "-i",
221
+ help="The fields to search within.",
222
+ autocompletion=variable_field_options,
223
+ ),
224
+ ] = None,
225
+ fields: Annotated[
226
+ list[str] | None,
227
+ typer.Option(
228
+ "--field",
229
+ "-f",
230
+ help="The field(s) to display in the results list. Defaults to all fields.",
231
+ autocompletion=variable_field_options,
232
+ ),
233
+ ] = None,
234
+ ) -> None:
235
+ """
236
+ Searches the database for variables matching the provided search term. The search fields
237
+ can be customized with the `--in` option. The output fields can be customized with the
238
+ `--field` option.
239
+ """
240
+ log.debug(
241
+ "var.search called. term=%s, limit=%s, search_fields=%s, fields=%s",
242
+ term,
243
+ limit,
244
+ search_fields,
245
+ fields,
246
+ )
247
+ variable_handlers.run_search_variables(
248
+ term=term,
249
+ limit=limit,
250
+ search_fields=search_fields,
251
+ fields=fields,
252
+ get_var_services=container.get_variable_services,
253
+ get_settings=container.get_settings,
254
+ get_console=container.get_console,
255
+ get_display_field_resolver=container.get_variable_display_field_resolver,
256
+ get_search_field_resolver=container.get_variable_search_field_resolver,
257
+ )
258
+
259
+
260
+ app.command("find", hidden=True)(search)
261
+
262
+
263
+ @app.command("delete")
264
+ @cli_guard
265
+ def delete(
266
+ name: Annotated[
267
+ str,
268
+ typer.Argument(
269
+ help="The name of the variable to delete.",
270
+ autocompletion=complete_variable_names,
271
+ ),
272
+ ],
273
+ ) -> None:
274
+ """
275
+ Deletes the variable stored under the provided name.
276
+ """
277
+ log.debug("var.delete called. name=%s", name)
278
+ variable_handlers.run_delete_variable(
279
+ name=name,
280
+ get_var_services=container.get_variable_services,
281
+ get_console=container.get_console,
282
+ )
283
+
284
+
285
+ app.command("del", hidden=True)(delete)
286
+ app.command("rm", hidden=True)(delete)
287
+ app.command("remove", hidden=True)(delete)
288
+
289
+
290
+ @app.command("tag")
291
+ @cli_guard
292
+ def add_tags(
293
+ name: Annotated[
294
+ str,
295
+ typer.Argument(
296
+ help="The name of the command to tag.",
297
+ autocompletion=complete_variable_names,
298
+ ),
299
+ ],
300
+ tags: Annotated[
301
+ list[str],
302
+ typer.Argument(
303
+ help="The tags to add to the command.", autocompletion=complete_tag_names
304
+ ),
305
+ ] = None,
306
+ ) -> None:
307
+ """
308
+ Adds the provided tags to the command stored under the provided alias. Tags must
309
+ be existing.
310
+ """
311
+ log.debug("var.tag.add called. name=%s, tags=%s", name, tags)
312
+ variable_handlers.run_attach_tags(
313
+ name=name,
314
+ tag_names=tags,
315
+ get_var_services=container.get_variable_services,
316
+ get_tag_services=container.get_tag_services,
317
+ get_console=container.get_console,
318
+ )
319
+
320
+
321
+ @app.command("untag")
322
+ @cli_guard
323
+ def remove_tags(
324
+ name: Annotated[
325
+ str,
326
+ typer.Argument(
327
+ help="The name of the command to untag.",
328
+ autocompletion=complete_variable_names,
329
+ ),
330
+ ],
331
+ tags: Annotated[
332
+ list[str],
333
+ typer.Argument(
334
+ help="The tags to remove from the command.",
335
+ autocompletion=complete_tag_names,
336
+ ),
337
+ ] = None,
338
+ ) -> None:
339
+ """
340
+ Removes the provided tags from the command stored under the provided alias.
341
+ """
342
+ log.debug("var.tag.remove called. name=%s, tags=%s", name, tags)
343
+ variable_handlers.run_detach_tags(
344
+ name=name,
345
+ tag_names=tags,
346
+ get_var_services=container.get_variable_services,
347
+ get_tag_services=container.get_tag_services,
348
+ get_console=container.get_console,
349
+ )
File without changes
@@ -0,0 +1,58 @@
1
+ import logging
2
+ from functools import wraps
3
+ from typing import Callable
4
+
5
+ import typer
6
+
7
+ from cmdbox.cli.ui.console import ConsoleUI
8
+ from cmdbox.exceptions import CmdboxError
9
+
10
+ log = logging.getLogger(__name__)
11
+
12
+
13
+ def make_cli_guard(
14
+ get_console: Callable[[], ConsoleUI],
15
+ ) -> Callable[[Callable], Callable]:
16
+ """
17
+ Creates a decorator to handle exceptions in CLI commands and provide uniform error
18
+ handling and messaging via a ConsoleUI instance.
19
+
20
+ The decorator wraps any given function, intercepting exceptions raised during its execution.
21
+ If an unexpected exception occurs, it is logged using the ConsoleUI's error handler,
22
+ and the program exits with a specific error code.
23
+
24
+ Args:
25
+ get_console: Callable that returns a `ConsoleUI` instance. Used to handle error
26
+ messages and output them in a consistent manner during CLI execution.
27
+
28
+ Returns:
29
+ Callable: A decorator function that processes the given function, wrapping it with
30
+ exception handling and error reporting logic.
31
+ """
32
+
33
+ def cli_guard(fn):
34
+ @wraps(fn)
35
+ def wrapper(*args, **kwargs):
36
+ try:
37
+ return fn(*args, **kwargs)
38
+ except typer.BadParameter:
39
+ log.error("Invalid parameter(s) provided.")
40
+ raise
41
+ except typer.Exit:
42
+ log.debug("Exiting CLI. Typer exit raised.")
43
+ raise
44
+ except CmdboxError as exc:
45
+ log.error(exc)
46
+ console = get_console()
47
+ console.error(str(exc))
48
+ raise typer.Exit(code=1)
49
+ except Exception as exc:
50
+ message = f"An unexpected error occurred: {exc}"
51
+ log.critical(message, exc_info=True)
52
+ console = get_console()
53
+ console.error(message)
54
+ raise typer.Exit(code=1)
55
+
56
+ return wrapper
57
+
58
+ return cli_guard