usecli 0.1.74__tar.gz → 0.1.75__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.
- {usecli-0.1.74 → usecli-0.1.75}/PKG-INFO +61 -3
- {usecli-0.1.74 → usecli-0.1.75}/README.md +60 -2
- {usecli-0.1.74 → usecli-0.1.75}/pyproject.toml +1 -1
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/__init__.py +136 -12
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/about_command.py +37 -20
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/inspire_command.py +10 -1
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +2 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/init_command.py +203 -6
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/base_command.py +43 -2
- usecli-0.1.75/src/usecli/cli/core/runtime.py +97 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/ui/list.py +72 -25
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/ui/title.py +4 -1
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/templates/command.py.j2 +16 -1
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/utils/interactive/terminal_menu.py +4 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/menu.py +5 -0
- usecli-0.1.75/src/usecli/progress.py +169 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/ui.py +7 -0
- {usecli-0.1.74 → usecli-0.1.75}/LICENSE +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/config/colors.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/exceptions/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/ui/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/ui/title.txt +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/services/command_service.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/params.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/shared/config/globals.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/shared/config/manager.py +0 -0
- {usecli-0.1.74 → usecli-0.1.75}/src/usecli/usecli.config.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usecli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.75
|
|
4
4
|
Summary: A powerful Python CLI framework for building beautiful, developer-friendly command-line tools.
|
|
5
5
|
Author: Edward Boswell
|
|
6
6
|
Author-email: Edward Boswell <thememium@gmail.com>
|
|
@@ -166,17 +166,75 @@ def signature(self) -> str:
|
|
|
166
166
|
return "spec show" # usecli spec show
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
+
### JSON Output
|
|
170
|
+
|
|
171
|
+
Every command inherits `--json`; commands do not declare the option themselves. The flag works before or after a command name:
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
usecli --json about
|
|
175
|
+
usecli about --json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
A successful invocation writes exactly one JSON document to stdout:
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{"ok":true,"data":null}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
A command can return any JSON-serializable value to populate `data`:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
class StatusCommand(BaseCommand):
|
|
188
|
+
def signature(self) -> str:
|
|
189
|
+
return "status"
|
|
190
|
+
|
|
191
|
+
def description(self) -> str:
|
|
192
|
+
return "Show service status"
|
|
193
|
+
|
|
194
|
+
def handle(self):
|
|
195
|
+
return {"status": "ready", "workers": 3}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Failures preserve a non-zero exit status and use a stable error envelope:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{"ok":false,"error":{"type":"UsageError","message":"No such command","code":2}}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
In JSON mode, stdout is reserved for that document. Human-facing output, including `console.print()` and ordinary `print()`, is routed to stderr. Prompts and confirmations resolve their declared defaults without reading stdin. A prompt without a default, menus, and `--interactive` fail immediately with a structured `NonInteractiveError` instead of blocking.
|
|
205
|
+
|
|
206
|
+
The existing fields `ok`, `data`, `error`, `error.type`, `error.message`, and `error.code` keep their names and types across compatible releases. New fields may be added.
|
|
207
|
+
|
|
169
208
|
### UI Components
|
|
170
209
|
|
|
171
210
|
```python
|
|
172
|
-
from usecli import
|
|
211
|
+
from usecli import Confirm, Menu, Prompt, console
|
|
173
212
|
|
|
174
213
|
console.print("[green]Done![/green]")
|
|
175
214
|
name = Prompt.ask("Enter name")
|
|
176
215
|
ok = Confirm.ask("Continue?")
|
|
177
|
-
choice = Menu(["A", "B", "C"])
|
|
216
|
+
choice = Menu.select(["A", "B", "C"])
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Progress Indicators
|
|
220
|
+
|
|
221
|
+
Use `Spinner` when the amount of work is unknown and `ProgressBar` when a total is known:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from usecli import ProgressBar, Spinner
|
|
225
|
+
|
|
226
|
+
with Spinner("Loading records") as spinner:
|
|
227
|
+
load_records()
|
|
228
|
+
spinner.update("Indexing records")
|
|
229
|
+
|
|
230
|
+
with ProgressBar(total=len(items), description="Processing") as progress:
|
|
231
|
+
for item in items:
|
|
232
|
+
process(item)
|
|
233
|
+
progress.advance()
|
|
178
234
|
```
|
|
179
235
|
|
|
236
|
+
Progress always renders to stderr, uses the active usecli theme, and is suppressed automatically in JSON mode, with `quiet=True`, or when stderr is not attached to a terminal. Calling commands do not need to detect these conditions.
|
|
237
|
+
|
|
180
238
|
### Available Commands
|
|
181
239
|
|
|
182
240
|
```
|
|
@@ -138,17 +138,75 @@ def signature(self) -> str:
|
|
|
138
138
|
return "spec show" # usecli spec show
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
+
### JSON Output
|
|
142
|
+
|
|
143
|
+
Every command inherits `--json`; commands do not declare the option themselves. The flag works before or after a command name:
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
usecli --json about
|
|
147
|
+
usecli about --json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
A successful invocation writes exactly one JSON document to stdout:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{"ok":true,"data":null}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
A command can return any JSON-serializable value to populate `data`:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
class StatusCommand(BaseCommand):
|
|
160
|
+
def signature(self) -> str:
|
|
161
|
+
return "status"
|
|
162
|
+
|
|
163
|
+
def description(self) -> str:
|
|
164
|
+
return "Show service status"
|
|
165
|
+
|
|
166
|
+
def handle(self):
|
|
167
|
+
return {"status": "ready", "workers": 3}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Failures preserve a non-zero exit status and use a stable error envelope:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{"ok":false,"error":{"type":"UsageError","message":"No such command","code":2}}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
In JSON mode, stdout is reserved for that document. Human-facing output, including `console.print()` and ordinary `print()`, is routed to stderr. Prompts and confirmations resolve their declared defaults without reading stdin. A prompt without a default, menus, and `--interactive` fail immediately with a structured `NonInteractiveError` instead of blocking.
|
|
177
|
+
|
|
178
|
+
The existing fields `ok`, `data`, `error`, `error.type`, `error.message`, and `error.code` keep their names and types across compatible releases. New fields may be added.
|
|
179
|
+
|
|
141
180
|
### UI Components
|
|
142
181
|
|
|
143
182
|
```python
|
|
144
|
-
from usecli import
|
|
183
|
+
from usecli import Confirm, Menu, Prompt, console
|
|
145
184
|
|
|
146
185
|
console.print("[green]Done![/green]")
|
|
147
186
|
name = Prompt.ask("Enter name")
|
|
148
187
|
ok = Confirm.ask("Continue?")
|
|
149
|
-
choice = Menu(["A", "B", "C"])
|
|
188
|
+
choice = Menu.select(["A", "B", "C"])
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Progress Indicators
|
|
192
|
+
|
|
193
|
+
Use `Spinner` when the amount of work is unknown and `ProgressBar` when a total is known:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from usecli import ProgressBar, Spinner
|
|
197
|
+
|
|
198
|
+
with Spinner("Loading records") as spinner:
|
|
199
|
+
load_records()
|
|
200
|
+
spinner.update("Indexing records")
|
|
201
|
+
|
|
202
|
+
with ProgressBar(total=len(items), description="Processing") as progress:
|
|
203
|
+
for item in items:
|
|
204
|
+
process(item)
|
|
205
|
+
progress.advance()
|
|
150
206
|
```
|
|
151
207
|
|
|
208
|
+
Progress always renders to stderr, uses the active usecli theme, and is suppressed automatically in JSON mode, with `quiet=True`, or when stderr is not attached to a terminal. Calling commands do not need to detect these conditions.
|
|
209
|
+
|
|
152
210
|
### Available Commands
|
|
153
211
|
|
|
154
212
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "usecli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.75"
|
|
4
4
|
description = "A powerful Python CLI framework for building beautiful, developer-friendly command-line tools."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Edward Boswell", email = "thememium@gmail.com" }]
|
|
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
|
|
|
20
20
|
from usecli.cli.core.base_command import BaseCommand
|
|
21
21
|
from usecli.menu import Menu
|
|
22
22
|
from usecli.params import Argument, Option
|
|
23
|
+
from usecli.progress import ProgressBar, Spinner
|
|
23
24
|
from usecli.ui import Confirm, Prompt
|
|
24
25
|
|
|
25
26
|
console: Console
|
|
@@ -37,6 +38,8 @@ _LAZY_EXPORTS = {
|
|
|
37
38
|
"Confirm": ("usecli.ui", "Confirm"),
|
|
38
39
|
"Console": ("usecli.ui", "Console"),
|
|
39
40
|
"console": ("usecli.ui", "console"),
|
|
41
|
+
"Spinner": ("usecli.progress", "Spinner"),
|
|
42
|
+
"ProgressBar": ("usecli.progress", "ProgressBar"),
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
|
|
@@ -155,18 +158,120 @@ def _ensure_cli_initialized() -> None:
|
|
|
155
158
|
windows_expand_args=True,
|
|
156
159
|
**extra,
|
|
157
160
|
):
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
complete_var=complete_var,
|
|
162
|
-
standalone_mode=False,
|
|
163
|
-
windows_expand_args=windows_expand_args,
|
|
164
|
-
**extra,
|
|
161
|
+
cli_args = list(args) if args is not None else sys.argv[1:]
|
|
162
|
+
option_args = (
|
|
163
|
+
cli_args[: cli_args.index("--")] if "--" in cli_args else cli_args
|
|
165
164
|
)
|
|
165
|
+
if "--json" not in option_args:
|
|
166
|
+
return super().main(
|
|
167
|
+
args=args,
|
|
168
|
+
prog_name=prog_name,
|
|
169
|
+
complete_var=complete_var,
|
|
170
|
+
standalone_mode=False,
|
|
171
|
+
windows_expand_args=windows_expand_args,
|
|
172
|
+
**extra,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
from contextlib import redirect_stdout
|
|
176
|
+
|
|
177
|
+
from usecli.cli.core.runtime import (
|
|
178
|
+
InvocationExit,
|
|
179
|
+
JSONSerializationError,
|
|
180
|
+
NonInteractiveError,
|
|
181
|
+
error_document,
|
|
182
|
+
execution_context,
|
|
183
|
+
serialize_document,
|
|
184
|
+
success_document,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
original_stdout = sys.stdout
|
|
188
|
+
_TyperBadParameter = globals()["_TyperBadParameter"]
|
|
189
|
+
_TyperUsageError = globals()["_TyperUsageError"]
|
|
190
|
+
_TyperClickException = globals()["_TyperClickException"]
|
|
191
|
+
|
|
192
|
+
def write_document(document):
|
|
193
|
+
original_stdout.write(serialize_document(document))
|
|
194
|
+
original_stdout.flush()
|
|
195
|
+
|
|
196
|
+
def fail(error_type: str, message: str, code: int):
|
|
197
|
+
write_document(
|
|
198
|
+
error_document(
|
|
199
|
+
error_type=error_type,
|
|
200
|
+
message=message,
|
|
201
|
+
code=code,
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
raise SystemExit(code)
|
|
205
|
+
|
|
206
|
+
with execution_context(json_mode=True):
|
|
207
|
+
try:
|
|
208
|
+
with redirect_stdout(sys.stderr):
|
|
209
|
+
result = super().main(
|
|
210
|
+
args=args,
|
|
211
|
+
prog_name=prog_name,
|
|
212
|
+
complete_var=complete_var,
|
|
213
|
+
standalone_mode=False,
|
|
214
|
+
windows_expand_args=windows_expand_args,
|
|
215
|
+
**extra,
|
|
216
|
+
)
|
|
217
|
+
write_document(success_document(result))
|
|
218
|
+
return result
|
|
219
|
+
except InvocationExit as error:
|
|
220
|
+
if error.code == 0:
|
|
221
|
+
write_document(success_document())
|
|
222
|
+
return None
|
|
223
|
+
fail("Exit", str(error), error.code)
|
|
224
|
+
except Exit as error:
|
|
225
|
+
code = error.exit_code
|
|
226
|
+
if code == 0:
|
|
227
|
+
write_document(success_document())
|
|
228
|
+
return None
|
|
229
|
+
fail("Exit", f"Command exited with status {code}", code)
|
|
230
|
+
except JSONSerializationError as error:
|
|
231
|
+
print(f"JSONSerializationError: {error}", file=sys.stderr)
|
|
232
|
+
fail("JSONSerializationError", str(error), 1)
|
|
233
|
+
except NonInteractiveError as error:
|
|
234
|
+
print(f"NonInteractiveError: {error}", file=sys.stderr)
|
|
235
|
+
fail("NonInteractiveError", str(error), error.code)
|
|
236
|
+
except (_TyperBadParameter, BadParameter) as error:
|
|
237
|
+
error.show(file=sys.stderr)
|
|
238
|
+
fail("BadParameter", error.format_message(), error.exit_code)
|
|
239
|
+
except (_TyperUsageError, UsageError) as error:
|
|
240
|
+
error.show(file=sys.stderr)
|
|
241
|
+
fail("UsageError", error.format_message(), error.exit_code)
|
|
242
|
+
except (_TyperClickException, ClickException) as error:
|
|
243
|
+
error.show(file=sys.stderr)
|
|
244
|
+
fail(
|
|
245
|
+
error.__class__.__name__,
|
|
246
|
+
error.format_message(),
|
|
247
|
+
error.exit_code,
|
|
248
|
+
)
|
|
249
|
+
except SystemExit as error:
|
|
250
|
+
code = error.code if isinstance(error.code, int) else 1
|
|
251
|
+
if code == 0:
|
|
252
|
+
write_document(success_document())
|
|
253
|
+
return None
|
|
254
|
+
fail("Exit", f"Command exited with status {code}", code)
|
|
255
|
+
except Exception as error:
|
|
256
|
+
print(
|
|
257
|
+
f"{error.__class__.__name__}: {error}",
|
|
258
|
+
file=sys.stderr,
|
|
259
|
+
)
|
|
260
|
+
fail(error.__class__.__name__, str(error), 1)
|
|
166
261
|
|
|
167
262
|
def invoke(self, ctx):
|
|
168
263
|
from click.exceptions import Exit
|
|
169
264
|
|
|
265
|
+
from usecli.cli.core.runtime import is_json_mode
|
|
266
|
+
|
|
267
|
+
if is_json_mode():
|
|
268
|
+
from usecli.cli.core.runtime import InvocationExit
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
return super().invoke(ctx)
|
|
272
|
+
except Exit as error:
|
|
273
|
+
raise InvocationExit(error.exit_code) from error
|
|
274
|
+
|
|
170
275
|
_TyperBadParameter = globals()["_TyperBadParameter"]
|
|
171
276
|
_TyperUsageError = globals()["_TyperUsageError"]
|
|
172
277
|
_TyperClickException = globals()["_TyperClickException"]
|
|
@@ -245,6 +350,8 @@ __all__ = [
|
|
|
245
350
|
"Option",
|
|
246
351
|
"Prompt",
|
|
247
352
|
"Confirm",
|
|
353
|
+
"Spinner",
|
|
354
|
+
"ProgressBar",
|
|
248
355
|
"colors",
|
|
249
356
|
"theme",
|
|
250
357
|
]
|
|
@@ -356,12 +463,12 @@ class _FilteredListCommand:
|
|
|
356
463
|
def invoke(self, ctx):
|
|
357
464
|
from usecli.cli.core.ui.list import list_commands
|
|
358
465
|
|
|
359
|
-
list_commands(_get_app(), prefix_filter=self.prefix_filter)
|
|
466
|
+
return list_commands(_get_app(), prefix_filter=self.prefix_filter)
|
|
360
467
|
|
|
361
468
|
def __call__(self, *args, **kwargs):
|
|
362
469
|
from usecli.cli.core.ui.list import list_commands
|
|
363
470
|
|
|
364
|
-
list_commands(_get_app(), prefix_filter=self.prefix_filter)
|
|
471
|
+
return list_commands(_get_app(), prefix_filter=self.prefix_filter)
|
|
365
472
|
|
|
366
473
|
|
|
367
474
|
def _get_default_help() -> str:
|
|
@@ -397,13 +504,23 @@ def _get_run_app_callback():
|
|
|
397
504
|
interactive: bool = typer.Option(
|
|
398
505
|
False, "--interactive", "-i", help="Run in interactive mode.", is_eager=True
|
|
399
506
|
),
|
|
400
|
-
|
|
507
|
+
json_output: bool = typer.Option(
|
|
508
|
+
False,
|
|
509
|
+
"--json",
|
|
510
|
+
help="Emit one machine-readable JSON document.",
|
|
511
|
+
is_eager=True,
|
|
512
|
+
),
|
|
513
|
+
) -> object:
|
|
401
514
|
_resolve_help()
|
|
402
515
|
|
|
403
516
|
if help:
|
|
404
517
|
from usecli.cli.core.ui.list import list_commands
|
|
405
518
|
|
|
406
|
-
list_commands(_get_app())
|
|
519
|
+
data = list_commands(_get_app())
|
|
520
|
+
from usecli.cli.core.runtime import is_json_mode
|
|
521
|
+
|
|
522
|
+
if is_json_mode():
|
|
523
|
+
return data
|
|
407
524
|
raise typer.Exit()
|
|
408
525
|
|
|
409
526
|
if version:
|
|
@@ -421,6 +538,13 @@ def _get_run_app_callback():
|
|
|
421
538
|
|
|
422
539
|
interactive_requested = interactive or _is_interactive_flag_present()
|
|
423
540
|
|
|
541
|
+
from usecli.cli.core.runtime import is_json_mode
|
|
542
|
+
|
|
543
|
+
if interactive_requested and is_json_mode():
|
|
544
|
+
from usecli.cli.core.runtime import NonInteractiveError
|
|
545
|
+
|
|
546
|
+
raise NonInteractiveError("Interactive mode is unavailable in JSON mode")
|
|
547
|
+
|
|
424
548
|
if interactive_requested:
|
|
425
549
|
from usecli.cli.commands.defaults.base.internal.fzf_command import (
|
|
426
550
|
run_interactive,
|
|
@@ -436,7 +560,7 @@ def _get_run_app_callback():
|
|
|
436
560
|
prefix_filter = ctx.obj.get("prefix_filter")
|
|
437
561
|
from usecli.cli.core.ui.list import list_commands
|
|
438
562
|
|
|
439
|
-
list_commands(_get_app(), prefix_filter=prefix_filter)
|
|
563
|
+
return list_commands(_get_app(), prefix_filter=prefix_filter)
|
|
440
564
|
|
|
441
565
|
globals()["run_app"] = run_app
|
|
442
566
|
return run_app
|
|
@@ -257,7 +257,9 @@ class AboutCommand(BaseCommand):
|
|
|
257
257
|
def description(self) -> str:
|
|
258
258
|
return "Display detailed information about the application"
|
|
259
259
|
|
|
260
|
-
def handle(self) ->
|
|
260
|
+
def handle(self) -> dict[str, object]:
|
|
261
|
+
import platform
|
|
262
|
+
|
|
261
263
|
config = get_config()
|
|
262
264
|
version = _get_application_version(config)
|
|
263
265
|
from usecli.cli.core.ui.title import get_project_name
|
|
@@ -265,6 +267,38 @@ class AboutCommand(BaseCommand):
|
|
|
265
267
|
app_name = get_project_name()
|
|
266
268
|
description = _get_application_description(config)
|
|
267
269
|
|
|
270
|
+
dist = _get_application_distribution()
|
|
271
|
+
name_label = "Cli Name" if dist is not None else "Application Name"
|
|
272
|
+
version_label = "Cli Version" if dist is not None else "Application Version"
|
|
273
|
+
|
|
274
|
+
script_commands = _get_script_commands() or ["usecli"]
|
|
275
|
+
|
|
276
|
+
deps = _get_dependencies(config)
|
|
277
|
+
dependencies: list[dict[str, str | None]] = []
|
|
278
|
+
for dep_name, spec in deps:
|
|
279
|
+
try:
|
|
280
|
+
from importlib.metadata import version as get_version
|
|
281
|
+
|
|
282
|
+
installed_version = get_version(dep_name)
|
|
283
|
+
dependencies.append({"name": dep_name, "version": installed_version})
|
|
284
|
+
except Exception:
|
|
285
|
+
dependencies.append({"name": dep_name, "version": spec})
|
|
286
|
+
|
|
287
|
+
data: dict[str, object] = {
|
|
288
|
+
"name": app_name,
|
|
289
|
+
"version": version,
|
|
290
|
+
"description": description,
|
|
291
|
+
"python_version": platform.python_version(),
|
|
292
|
+
"platform": platform.platform(),
|
|
293
|
+
"entry_points": script_commands,
|
|
294
|
+
"dependencies": dependencies,
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
from usecli.cli.core.runtime import is_json_mode
|
|
298
|
+
|
|
299
|
+
if is_json_mode():
|
|
300
|
+
return data
|
|
301
|
+
|
|
268
302
|
console.print()
|
|
269
303
|
console.print(f"[bold {COLOR.PRIMARY}]Description[/bold {COLOR.PRIMARY}]")
|
|
270
304
|
console.print(f"[{COLOR.PRIMARY}]─" * 78)
|
|
@@ -273,22 +307,14 @@ class AboutCommand(BaseCommand):
|
|
|
273
307
|
console.print()
|
|
274
308
|
console.print(f"[bold {COLOR.PRIMARY}]Environment[/bold {COLOR.PRIMARY}]")
|
|
275
309
|
console.print(f"[{COLOR.PRIMARY}]─" * 78)
|
|
276
|
-
|
|
277
|
-
dist = _get_application_distribution()
|
|
278
|
-
name_label = "Cli Name" if dist is not None else "Application Name"
|
|
279
|
-
version_label = "Cli Version" if dist is not None else "Application Version"
|
|
280
310
|
self._print_row(name_label, app_name)
|
|
281
311
|
self._print_row(version_label, version)
|
|
282
|
-
import platform
|
|
283
|
-
|
|
284
312
|
self._print_row("Python Version", platform.python_version())
|
|
285
313
|
self._print_row("Platform", f"[{COLOR.FOREGROUND_MUTED}]{platform.platform()}")
|
|
286
314
|
|
|
287
315
|
console.print()
|
|
288
316
|
console.print(f"[bold {COLOR.PRIMARY}]Entry Points[/bold {COLOR.PRIMARY}]")
|
|
289
317
|
console.print(f"[{COLOR.PRIMARY}]─" * 78)
|
|
290
|
-
|
|
291
|
-
script_commands = _get_script_commands() or ["usecli"]
|
|
292
318
|
for index, command in enumerate(script_commands):
|
|
293
319
|
label = "Primary command" if index == 0 else "Command"
|
|
294
320
|
self._print_row(command, label)
|
|
@@ -296,8 +322,6 @@ class AboutCommand(BaseCommand):
|
|
|
296
322
|
console.print()
|
|
297
323
|
console.print(f"[bold {COLOR.PRIMARY}]Dependencies[/bold {COLOR.PRIMARY}]")
|
|
298
324
|
console.print(f"[{COLOR.PRIMARY}]─" * 78)
|
|
299
|
-
|
|
300
|
-
deps = _get_dependencies(config)
|
|
301
325
|
if deps:
|
|
302
326
|
for dep_name, spec in deps:
|
|
303
327
|
try:
|
|
@@ -313,17 +337,10 @@ class AboutCommand(BaseCommand):
|
|
|
313
337
|
else:
|
|
314
338
|
self._print_row("Dependencies", "unable to load")
|
|
315
339
|
|
|
316
|
-
# console.print()
|
|
317
|
-
# console.print(f"[bold {COLOR.PRIMARY}]Features[/bold {COLOR.PRIMARY}]")
|
|
318
|
-
# console.print(f"[{COLOR.PRIMARY}]─" * 78)
|
|
319
|
-
#
|
|
320
|
-
# self._print_row("Prefix Matching", f"[{COLOR.SECONDARY}]ENABLED")
|
|
321
|
-
# self._print_row("Rich UI", f"[{COLOR.SECONDARY}]ENABLED")
|
|
322
|
-
# self._print_row("Command Scaffolding", f"[{COLOR.SECONDARY}]ENABLED")
|
|
323
|
-
# self._print_row("Interactive Menus", f"[{COLOR.SECONDARY}]ENABLED")
|
|
324
|
-
# self._print_row("Fuzzy Finder", f"[{COLOR.SECONDARY}]ENABLED")
|
|
325
340
|
console.print()
|
|
326
341
|
|
|
342
|
+
return data
|
|
343
|
+
|
|
327
344
|
def _print_row(self, label: str, value: str) -> None:
|
|
328
345
|
visible_value = console.render_str(value).plain
|
|
329
346
|
right_align_column = 76
|
|
@@ -24,7 +24,7 @@ class InspireCommand(BaseCommand):
|
|
|
24
24
|
"""Return the command description."""
|
|
25
25
|
return "Show a random inspirational quote"
|
|
26
26
|
|
|
27
|
-
def handle(self) ->
|
|
27
|
+
def handle(self) -> dict[str, str]:
|
|
28
28
|
"""Handle the command execution."""
|
|
29
29
|
|
|
30
30
|
quotes = [
|
|
@@ -73,6 +73,13 @@ class InspireCommand(BaseCommand):
|
|
|
73
73
|
selected = random.choice(quotes)
|
|
74
74
|
quote, author = selected.rsplit(" - ", 1)
|
|
75
75
|
|
|
76
|
+
data = {"quote": quote, "author": author}
|
|
77
|
+
|
|
78
|
+
from usecli.cli.core.runtime import is_json_mode
|
|
79
|
+
|
|
80
|
+
if is_json_mode():
|
|
81
|
+
return data
|
|
82
|
+
|
|
76
83
|
from rich.console import Console
|
|
77
84
|
from rich.panel import Panel
|
|
78
85
|
|
|
@@ -84,3 +91,5 @@ class InspireCommand(BaseCommand):
|
|
|
84
91
|
title_align="left",
|
|
85
92
|
)
|
|
86
93
|
)
|
|
94
|
+
|
|
95
|
+
return data
|