usecli 0.1.59__tar.gz → 0.1.60__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.59 → usecli-0.1.60}/PKG-INFO +1 -1
- {usecli-0.1.59 → usecli-0.1.60}/pyproject.toml +1 -1
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/__init__.py +72 -11
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/about_command.py +67 -32
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/help_command.py +2 -1
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/inspire_command.py +4 -6
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/core/utils.py +16 -3
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/config/colors.py +69 -18
- usecli-0.1.60/src/usecli/cli/core/__init__.py +62 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/base_command.py +20 -7
- usecli-0.1.60/src/usecli/cli/core/exceptions/__init__.py +30 -0
- usecli-0.1.60/src/usecli/cli/core/ui/__init__.py +46 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/ui/title.py +1 -2
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/services/command_service.py +49 -9
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/shared/config/manager.py +37 -32
- usecli-0.1.59/src/usecli/cli/core/__init__.py +0 -66
- usecli-0.1.59/src/usecli/cli/core/exceptions/__init__.py +0 -16
- usecli-0.1.59/src/usecli/cli/core/ui/__init__.py +0 -31
- {usecli-0.1.59 → usecli-0.1.60}/LICENSE +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/README.md +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/commands/init_command.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/ui/list.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/ui/title.txt +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/menu.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/params.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/shared/config/globals.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/ui.py +0 -0
- {usecli-0.1.59 → usecli-0.1.60}/src/usecli/usecli.config.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "usecli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.60"
|
|
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" }]
|
|
@@ -2,16 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import shutil
|
|
6
5
|
import sys
|
|
7
6
|
from importlib import import_module
|
|
8
|
-
from typing import Any, Optional, Sequence
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Optional, Sequence
|
|
9
8
|
|
|
10
9
|
import click
|
|
11
10
|
import typer
|
|
12
11
|
from click.exceptions import BadParameter, ClickException, Exit, UsageError
|
|
13
12
|
from typer.core import TyperGroup
|
|
14
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
|
|
17
|
+
from usecli.menu import Menu
|
|
18
|
+
from usecli.params import Argument, Option
|
|
19
|
+
from usecli.ui import Confirm, Prompt
|
|
20
|
+
|
|
21
|
+
console: Console
|
|
22
|
+
|
|
15
23
|
try:
|
|
16
24
|
from typer._click.exceptions import BadParameter as TyperBadParameter # type: ignore[import-untyped]
|
|
17
25
|
from typer._click.exceptions import ClickException as TyperClickException # type: ignore[import-untyped]
|
|
@@ -23,13 +31,8 @@ except ImportError:
|
|
|
23
31
|
|
|
24
32
|
from usecli.cli.config.colors import COLOR
|
|
25
33
|
from usecli.cli.core.base_command import BaseCommand
|
|
26
|
-
from usecli.cli.core.exceptions import UsecliBadParameter, UsecliUsageError
|
|
27
|
-
from usecli.cli.core.ui.list import list_commands
|
|
28
34
|
from usecli.cli.services.command_service import CommandService
|
|
29
|
-
from usecli.menu import Menu
|
|
30
|
-
from usecli.params import Argument, Option
|
|
31
35
|
from usecli.shared.config.manager import get_config
|
|
32
|
-
from usecli.ui import Confirm, Console, Prompt, console
|
|
33
36
|
|
|
34
37
|
colors = import_module("usecli.cli.config.colors")
|
|
35
38
|
theme = COLOR
|
|
@@ -37,6 +40,27 @@ theme = COLOR
|
|
|
37
40
|
sys.modules.setdefault(__name__ + ".colors", colors)
|
|
38
41
|
sys.modules.setdefault("colors", colors)
|
|
39
42
|
|
|
43
|
+
_LAZY_EXPORTS = {
|
|
44
|
+
"Menu": ("usecli.menu", "Menu"),
|
|
45
|
+
"Argument": ("usecli.params", "Argument"),
|
|
46
|
+
"Option": ("usecli.params", "Option"),
|
|
47
|
+
"Prompt": ("usecli.ui", "Prompt"),
|
|
48
|
+
"Confirm": ("usecli.ui", "Confirm"),
|
|
49
|
+
"Console": ("usecli.ui", "Console"),
|
|
50
|
+
"console": ("usecli.ui", "console"),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def __getattr__(name: str) -> Any:
|
|
55
|
+
export = _LAZY_EXPORTS.get(name)
|
|
56
|
+
if export is None:
|
|
57
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
58
|
+
module_name, attr_name = export
|
|
59
|
+
value = getattr(import_module(module_name), attr_name)
|
|
60
|
+
globals()[name] = value
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
|
|
40
64
|
__all__ = [
|
|
41
65
|
"BaseCommand",
|
|
42
66
|
"console",
|
|
@@ -52,6 +76,10 @@ __all__ = [
|
|
|
52
76
|
]
|
|
53
77
|
|
|
54
78
|
|
|
79
|
+
def _console():
|
|
80
|
+
return __getattr__("console")
|
|
81
|
+
|
|
82
|
+
|
|
55
83
|
def _is_interactive_flag_present() -> bool:
|
|
56
84
|
"""Check if -i/--interactive flag is present in sys.argv.
|
|
57
85
|
|
|
@@ -206,10 +234,14 @@ class PrefixMatchingGroup(TyperGroup):
|
|
|
206
234
|
except Exit:
|
|
207
235
|
sys.exit(0)
|
|
208
236
|
except (BadParameter, TyperBadParameter) as e:
|
|
237
|
+
from usecli.cli.core.exceptions import UsecliBadParameter
|
|
238
|
+
|
|
209
239
|
styled_error = UsecliBadParameter(e.message, ctx=e.ctx, param=e.param)
|
|
210
240
|
styled_error.show()
|
|
211
241
|
sys.exit(styled_error.exit_code)
|
|
212
242
|
except (UsageError, TyperUsageError) as e:
|
|
243
|
+
from usecli.cli.core.exceptions import UsecliUsageError
|
|
244
|
+
|
|
213
245
|
styled_error = UsecliUsageError(e.message, ctx=e.ctx)
|
|
214
246
|
styled_error.show()
|
|
215
247
|
sys.exit(styled_error.exit_code)
|
|
@@ -241,18 +273,34 @@ class FilteredListCommand(click.Command):
|
|
|
241
273
|
Args:
|
|
242
274
|
ctx: The Click context.
|
|
243
275
|
"""
|
|
276
|
+
from usecli.cli.core.ui.list import list_commands
|
|
277
|
+
|
|
244
278
|
list_commands(app, prefix_filter=self.prefix_filter)
|
|
245
279
|
return None
|
|
246
280
|
|
|
247
281
|
|
|
282
|
+
def _get_default_help() -> str:
|
|
283
|
+
return "Usecli CLI - An elegant CLI framework for Python"
|
|
284
|
+
|
|
285
|
+
|
|
248
286
|
app = typer.Typer(
|
|
249
|
-
help=
|
|
287
|
+
help=_get_default_help(),
|
|
250
288
|
invoke_without_command=True,
|
|
251
289
|
no_args_is_help=False,
|
|
252
290
|
cls=PrefixMatchingGroup,
|
|
253
291
|
pretty_exceptions_enable=False, # Use custom error styling
|
|
254
292
|
)
|
|
255
293
|
|
|
294
|
+
_help_resolved = False
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _resolve_help():
|
|
298
|
+
global _help_resolved
|
|
299
|
+
if not _help_resolved:
|
|
300
|
+
app.info.help = _get_cli_help_text()
|
|
301
|
+
_help_resolved = True
|
|
302
|
+
|
|
303
|
+
|
|
256
304
|
service = CommandService(app)
|
|
257
305
|
service.load_commands()
|
|
258
306
|
|
|
@@ -278,14 +326,20 @@ def run_app(
|
|
|
278
326
|
version: Flag to show version and exit.
|
|
279
327
|
help: Flag to show help and exit.
|
|
280
328
|
"""
|
|
329
|
+
_resolve_help()
|
|
330
|
+
|
|
281
331
|
if help:
|
|
332
|
+
from usecli.cli.core.ui.list import list_commands
|
|
333
|
+
|
|
282
334
|
list_commands(app)
|
|
283
335
|
raise typer.Exit()
|
|
284
336
|
|
|
285
337
|
if version:
|
|
338
|
+
import shutil
|
|
339
|
+
|
|
286
340
|
config = get_config()
|
|
287
341
|
command_path = shutil.which(sys.argv[0]) or sys.argv[0]
|
|
288
|
-
|
|
342
|
+
_console().print(
|
|
289
343
|
f"[bold {theme.SECONDARY}]{config.get('title')} {service.version}[/bold {theme.SECONDARY}] [{theme.INFO}]({command_path})[/{theme.INFO}]"
|
|
290
344
|
)
|
|
291
345
|
raise typer.Exit()
|
|
@@ -305,18 +359,21 @@ def run_app(
|
|
|
305
359
|
prefix_filter: str | None = None
|
|
306
360
|
if ctx.obj and isinstance(ctx.obj, dict):
|
|
307
361
|
prefix_filter = ctx.obj.get("prefix_filter")
|
|
362
|
+
from usecli.cli.core.ui.list import list_commands
|
|
363
|
+
|
|
308
364
|
list_commands(app, prefix_filter=prefix_filter)
|
|
309
365
|
|
|
310
366
|
|
|
311
367
|
def main() -> None:
|
|
312
368
|
"""Run the CLI application with custom error handling."""
|
|
369
|
+
_resolve_help()
|
|
313
370
|
config = get_config()
|
|
314
371
|
command_name = config._get_command_name()
|
|
315
372
|
if command_name == "usecli" and not config.is_usecli_direct_dependency():
|
|
316
|
-
|
|
373
|
+
_console().print(
|
|
317
374
|
"[bold red]Error:[/bold red] usecli is not a direct dependency of this project."
|
|
318
375
|
)
|
|
319
|
-
|
|
376
|
+
_console().print(
|
|
320
377
|
"Add it to your [cyan]pyproject.toml[/cyan] dependencies or dependency-groups."
|
|
321
378
|
)
|
|
322
379
|
sys.exit(1)
|
|
@@ -326,10 +383,14 @@ def main() -> None:
|
|
|
326
383
|
except Exit:
|
|
327
384
|
sys.exit(0)
|
|
328
385
|
except (BadParameter, TyperBadParameter) as e:
|
|
386
|
+
from usecli.cli.core.exceptions import UsecliBadParameter
|
|
387
|
+
|
|
329
388
|
styled_error = UsecliBadParameter(e.message, ctx=e.ctx, param=e.param)
|
|
330
389
|
styled_error.show()
|
|
331
390
|
sys.exit(styled_error.exit_code)
|
|
332
391
|
except (UsageError, TyperUsageError) as e:
|
|
392
|
+
from usecli.cli.core.exceptions import UsecliUsageError
|
|
393
|
+
|
|
333
394
|
styled_error = UsecliUsageError(e.message, ctx=e.ctx)
|
|
334
395
|
styled_error.show()
|
|
335
396
|
sys.exit(styled_error.exit_code)
|
|
@@ -1,30 +1,53 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import importlib.metadata
|
|
4
3
|
import os
|
|
5
|
-
import platform
|
|
6
|
-
import re
|
|
7
4
|
import sys
|
|
8
|
-
from importlib.metadata import PackageNotFoundError
|
|
9
|
-
from importlib.metadata import version as get_version
|
|
10
5
|
from pathlib import Path
|
|
11
6
|
|
|
12
|
-
if sys.version_info >= (3, 11):
|
|
13
|
-
import tomllib
|
|
14
|
-
else:
|
|
15
|
-
import tomli as tomllib
|
|
16
|
-
|
|
17
|
-
from rich.console import Console
|
|
18
|
-
|
|
19
7
|
from usecli.cli.config.colors import COLOR
|
|
20
8
|
from usecli.cli.core.base_command import BaseCommand
|
|
21
|
-
from usecli.
|
|
22
|
-
|
|
9
|
+
from usecli.shared.config.manager import get_config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _LazyConsole:
|
|
13
|
+
_console = None
|
|
14
|
+
|
|
15
|
+
def _get_console(self):
|
|
16
|
+
if self._console is None:
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
|
|
19
|
+
self._console = Console()
|
|
20
|
+
return self._console
|
|
21
|
+
|
|
22
|
+
def __getattr__(self, name):
|
|
23
|
+
return getattr(self._get_console(), name)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
console = _LazyConsole()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _load_toml(text: str):
|
|
30
|
+
if sys.version_info >= (3, 11):
|
|
31
|
+
import tomllib
|
|
32
|
+
else:
|
|
33
|
+
import tomli as tomllib
|
|
34
|
+
|
|
35
|
+
return tomllib.loads(text)
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+
|
|
38
|
+
def _toml_decode_error():
|
|
39
|
+
if sys.version_info >= (3, 11):
|
|
40
|
+
pass
|
|
41
|
+
else:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
return _toml_decode_error()
|
|
25
45
|
|
|
26
46
|
|
|
27
47
|
def _get_version() -> str:
|
|
48
|
+
from importlib.metadata import PackageNotFoundError
|
|
49
|
+
from importlib.metadata import version as get_version
|
|
50
|
+
|
|
28
51
|
try:
|
|
29
52
|
return get_version("usecli")
|
|
30
53
|
except PackageNotFoundError:
|
|
@@ -36,6 +59,8 @@ def _parse_dependency_requirement(req: str) -> tuple[str, str | None]:
|
|
|
36
59
|
if not req_core:
|
|
37
60
|
return "", None
|
|
38
61
|
|
|
62
|
+
import re
|
|
63
|
+
|
|
39
64
|
match = re.match(r"^([A-Za-z0-9_.-]+)", req_core)
|
|
40
65
|
if not match:
|
|
41
66
|
return "", None
|
|
@@ -57,11 +82,11 @@ def _parse_dependency_requirement(req: str) -> tuple[str, str | None]:
|
|
|
57
82
|
return name, remainder
|
|
58
83
|
|
|
59
84
|
|
|
60
|
-
def _get_console_script_distribution(
|
|
61
|
-
command_name: str | None,
|
|
62
|
-
) -> importlib.metadata.Distribution | None:
|
|
85
|
+
def _get_console_script_distribution(command_name: str | None):
|
|
63
86
|
if not command_name:
|
|
64
87
|
return None
|
|
88
|
+
import importlib.metadata
|
|
89
|
+
|
|
65
90
|
try:
|
|
66
91
|
distributions = importlib.metadata.distributions()
|
|
67
92
|
except Exception:
|
|
@@ -79,9 +104,7 @@ def _get_console_script_distribution(
|
|
|
79
104
|
return None
|
|
80
105
|
|
|
81
106
|
|
|
82
|
-
def _get_package_dependencies_from_distribution(
|
|
83
|
-
dist: importlib.metadata.Distribution,
|
|
84
|
-
) -> list[tuple[str, str | None]]:
|
|
107
|
+
def _get_package_dependencies_from_distribution(dist) -> list[tuple[str, str | None]]:
|
|
85
108
|
requires = dist.requires or []
|
|
86
109
|
result: list[tuple[str, str | None]] = []
|
|
87
110
|
for req in requires:
|
|
@@ -93,10 +116,12 @@ def _get_package_dependencies_from_distribution(
|
|
|
93
116
|
return result
|
|
94
117
|
|
|
95
118
|
|
|
96
|
-
def _get_dependencies(config
|
|
119
|
+
def _get_dependencies(config) -> list[tuple[str, str | None]]:
|
|
97
120
|
command_name = os.path.basename(sys.argv[0]) if sys.argv else None
|
|
98
121
|
dist = _get_console_script_distribution(command_name)
|
|
99
122
|
if dist is None:
|
|
123
|
+
from usecli.cli.core.ui.title import get_script_command_name
|
|
124
|
+
|
|
100
125
|
primary_command = get_script_command_name(default=None)
|
|
101
126
|
dist = _get_console_script_distribution(primary_command)
|
|
102
127
|
if dist is not None:
|
|
@@ -107,8 +132,8 @@ def _get_dependencies(config: ConfigManager) -> list[tuple[str, str | None]]:
|
|
|
107
132
|
return []
|
|
108
133
|
|
|
109
134
|
try:
|
|
110
|
-
data =
|
|
111
|
-
except (
|
|
135
|
+
data = _load_toml(pyproject_path.read_text())
|
|
136
|
+
except (_toml_decode_error(), OSError):
|
|
112
137
|
return []
|
|
113
138
|
|
|
114
139
|
deps = data.get("project", {}).get("dependencies", [])
|
|
@@ -125,16 +150,18 @@ def _get_dependencies(config: ConfigManager) -> list[tuple[str, str | None]]:
|
|
|
125
150
|
return result
|
|
126
151
|
|
|
127
152
|
|
|
128
|
-
def _get_application_distribution()
|
|
153
|
+
def _get_application_distribution():
|
|
129
154
|
command_name = os.path.basename(sys.argv[0]) if sys.argv else None
|
|
130
155
|
dist = _get_console_script_distribution(command_name)
|
|
131
156
|
if dist is None:
|
|
157
|
+
from usecli.cli.core.ui.title import get_script_command_name
|
|
158
|
+
|
|
132
159
|
primary_command = get_script_command_name(default=None)
|
|
133
160
|
dist = _get_console_script_distribution(primary_command)
|
|
134
161
|
return dist
|
|
135
162
|
|
|
136
163
|
|
|
137
|
-
def _get_application_version(config
|
|
164
|
+
def _get_application_version(config) -> str:
|
|
138
165
|
dist = _get_application_distribution()
|
|
139
166
|
if dist is not None:
|
|
140
167
|
return dist.version
|
|
@@ -146,7 +173,7 @@ def _get_application_version(config: ConfigManager) -> str:
|
|
|
146
173
|
return _get_version()
|
|
147
174
|
|
|
148
175
|
|
|
149
|
-
def _get_application_description(config
|
|
176
|
+
def _get_application_description(config) -> str:
|
|
150
177
|
description = config.get("description")
|
|
151
178
|
if (
|
|
152
179
|
config.has_key("description")
|
|
@@ -165,14 +192,14 @@ def _get_application_description(config: ConfigManager) -> str:
|
|
|
165
192
|
)
|
|
166
193
|
|
|
167
194
|
|
|
168
|
-
def _get_project_description(config
|
|
195
|
+
def _get_project_description(config) -> str | None:
|
|
169
196
|
pyproject_path = config.pyproject_path
|
|
170
197
|
if not pyproject_path.exists():
|
|
171
198
|
return None
|
|
172
199
|
|
|
173
200
|
try:
|
|
174
|
-
data =
|
|
175
|
-
except (
|
|
201
|
+
data = _load_toml(pyproject_path.read_text())
|
|
202
|
+
except (_toml_decode_error(), OSError):
|
|
176
203
|
return None
|
|
177
204
|
|
|
178
205
|
description = data.get("project", {}).get("description")
|
|
@@ -202,6 +229,8 @@ def _get_installed_script_commands(command_name: str | None) -> list[str]:
|
|
|
202
229
|
|
|
203
230
|
|
|
204
231
|
def _get_script_commands() -> list[str]:
|
|
232
|
+
from usecli.cli.core.ui.title import get_script_command_name
|
|
233
|
+
|
|
205
234
|
primary_command = get_script_command_name(default=None)
|
|
206
235
|
command_name = os.path.basename(sys.argv[0]) if sys.argv else primary_command
|
|
207
236
|
installed_commands = _get_installed_script_commands(command_name)
|
|
@@ -217,8 +246,8 @@ def _get_script_commands() -> list[str]:
|
|
|
217
246
|
return []
|
|
218
247
|
|
|
219
248
|
try:
|
|
220
|
-
data =
|
|
221
|
-
except (
|
|
249
|
+
data = _load_toml(pyproject_path.read_text())
|
|
250
|
+
except (_toml_decode_error(), OSError):
|
|
222
251
|
return []
|
|
223
252
|
|
|
224
253
|
scripts = data.get("project", {}).get("scripts", {})
|
|
@@ -245,6 +274,8 @@ class AboutCommand(BaseCommand):
|
|
|
245
274
|
def handle(self) -> None:
|
|
246
275
|
config = get_config()
|
|
247
276
|
version = _get_application_version(config)
|
|
277
|
+
from usecli.cli.core.ui.title import get_project_name
|
|
278
|
+
|
|
248
279
|
app_name = get_project_name()
|
|
249
280
|
description = _get_application_description(config)
|
|
250
281
|
|
|
@@ -262,6 +293,8 @@ class AboutCommand(BaseCommand):
|
|
|
262
293
|
version_label = "Cli Version" if dist is not None else "Application Version"
|
|
263
294
|
self._print_row(name_label, app_name)
|
|
264
295
|
self._print_row(version_label, version)
|
|
296
|
+
import platform
|
|
297
|
+
|
|
265
298
|
self._print_row("Python Version", platform.python_version())
|
|
266
299
|
self._print_row("Platform", f"[{COLOR.FOREGROUND_MUTED}]{platform.platform()}")
|
|
267
300
|
|
|
@@ -282,6 +315,8 @@ class AboutCommand(BaseCommand):
|
|
|
282
315
|
if deps:
|
|
283
316
|
for dep_name, spec in deps:
|
|
284
317
|
try:
|
|
318
|
+
from importlib.metadata import version as get_version
|
|
319
|
+
|
|
285
320
|
installed_version = get_version(dep_name)
|
|
286
321
|
self._print_row(dep_name, installed_version)
|
|
287
322
|
except Exception:
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from usecli.cli.core.base_command import BaseCommand
|
|
6
|
-
from usecli.cli.core.ui.list import list_commands
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
class HelpCommand(BaseCommand):
|
|
@@ -19,4 +18,6 @@ class HelpCommand(BaseCommand):
|
|
|
19
18
|
|
|
20
19
|
def handle(self) -> None:
|
|
21
20
|
"""Handle the command execution."""
|
|
21
|
+
from usecli.cli.core.ui.list import list_commands
|
|
22
|
+
|
|
22
23
|
list_commands(self.app)
|
|
@@ -4,15 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import random
|
|
6
6
|
|
|
7
|
-
from rich.console import Console
|
|
8
|
-
from rich.panel import Panel
|
|
9
|
-
|
|
10
7
|
from usecli.cli.config.colors import COLOR
|
|
11
8
|
from usecli.cli.core.base_command import BaseCommand
|
|
12
9
|
from usecli.shared.config.manager import get_config
|
|
13
10
|
|
|
14
|
-
console = Console()
|
|
15
|
-
|
|
16
11
|
|
|
17
12
|
class InspireCommand(BaseCommand):
|
|
18
13
|
"""Command for displaying random inspirational quotes."""
|
|
@@ -78,7 +73,10 @@ class InspireCommand(BaseCommand):
|
|
|
78
73
|
selected = random.choice(quotes)
|
|
79
74
|
quote, author = selected.rsplit(" - ", 1)
|
|
80
75
|
|
|
81
|
-
console
|
|
76
|
+
from rich.console import Console
|
|
77
|
+
from rich.panel import Panel
|
|
78
|
+
|
|
79
|
+
Console().print(
|
|
82
80
|
Panel(
|
|
83
81
|
f"{quote}\n\n[{COLOR.FOREGROUND_MUTED}]— {author}[/{COLOR.FOREGROUND_MUTED}]",
|
|
84
82
|
border_style=COLOR.PANEL_PRIMARY,
|
|
@@ -8,12 +8,25 @@ from datetime import datetime, timedelta
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
|
-
from rich.console import Console
|
|
12
|
-
|
|
13
11
|
if TYPE_CHECKING:
|
|
14
12
|
pass
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
|
|
15
|
+
class _LazyConsole:
|
|
16
|
+
_console = None
|
|
17
|
+
|
|
18
|
+
def _get_console(self):
|
|
19
|
+
if self._console is None:
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
|
|
22
|
+
self._console = Console()
|
|
23
|
+
return self._console
|
|
24
|
+
|
|
25
|
+
def __getattr__(self, name):
|
|
26
|
+
return getattr(self._get_console(), name)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
console = _LazyConsole()
|
|
17
30
|
|
|
18
31
|
|
|
19
32
|
def is_interactive() -> bool:
|
|
@@ -11,18 +11,19 @@ Usage:
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
-
import importlib.metadata
|
|
15
|
-
import importlib.util
|
|
16
14
|
import os
|
|
17
15
|
import sys
|
|
18
16
|
import time
|
|
19
17
|
from pathlib import Path
|
|
20
18
|
from typing import Any, Callable, Final, Protocol, cast, final
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
|
|
21
|
+
def _import_tomllib():
|
|
22
|
+
if sys.version_info >= (3, 11):
|
|
23
|
+
import tomllib
|
|
24
|
+
else:
|
|
25
|
+
import tomli as tomllib
|
|
26
|
+
return tomllib
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
PYPROJECT_TOML = "pyproject.toml"
|
|
@@ -114,6 +115,8 @@ def _get_console_script_aliases(command_name: str | None) -> set[str]:
|
|
|
114
115
|
return set()
|
|
115
116
|
aliases: set[str] = {command_name}
|
|
116
117
|
try:
|
|
118
|
+
import importlib.metadata
|
|
119
|
+
|
|
117
120
|
distributions = importlib.metadata.distributions()
|
|
118
121
|
except Exception:
|
|
119
122
|
return aliases
|
|
@@ -139,7 +142,7 @@ def _config_matches_command(path: Path, command_name: str | None) -> bool:
|
|
|
139
142
|
return True
|
|
140
143
|
try:
|
|
141
144
|
data = _load_usecli_config_file(path)
|
|
142
|
-
except
|
|
145
|
+
except OSError:
|
|
143
146
|
return True
|
|
144
147
|
config_command = data.get("command_name")
|
|
145
148
|
if not isinstance(config_command, str):
|
|
@@ -205,6 +208,8 @@ def _find_usecli_config_path_for_command(
|
|
|
205
208
|
|
|
206
209
|
|
|
207
210
|
def _find_usecli_config_in_package() -> Path | None:
|
|
211
|
+
import importlib.util
|
|
212
|
+
|
|
208
213
|
spec = importlib.util.find_spec(_get_package_name())
|
|
209
214
|
if spec is None or not spec.submodule_search_locations:
|
|
210
215
|
return None
|
|
@@ -222,6 +227,8 @@ def _find_usecli_config_in_package() -> Path | None:
|
|
|
222
227
|
|
|
223
228
|
|
|
224
229
|
def _find_usecli_config_in_named_package(package_name: str) -> Path | None:
|
|
230
|
+
import importlib.util
|
|
231
|
+
|
|
225
232
|
if not package_name:
|
|
226
233
|
return None
|
|
227
234
|
spec = importlib.util.find_spec(package_name)
|
|
@@ -241,6 +248,8 @@ def _find_usecli_config_in_named_package(package_name: str) -> Path | None:
|
|
|
241
248
|
|
|
242
249
|
|
|
243
250
|
def _find_usecli_config_for_console_script() -> Path | None:
|
|
251
|
+
import importlib.metadata
|
|
252
|
+
|
|
244
253
|
command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
|
|
245
254
|
if not command_name:
|
|
246
255
|
return None
|
|
@@ -282,6 +291,8 @@ def _is_preferred_package_path(path: Path) -> bool:
|
|
|
282
291
|
|
|
283
292
|
|
|
284
293
|
def _is_within_usecli_package(start_dir: Path) -> bool:
|
|
294
|
+
import importlib.util
|
|
295
|
+
|
|
285
296
|
spec = importlib.util.find_spec(_get_package_name())
|
|
286
297
|
if spec is None or not spec.submodule_search_locations:
|
|
287
298
|
return False
|
|
@@ -365,6 +376,7 @@ def _load_usecli_config(
|
|
|
365
376
|
|
|
366
377
|
|
|
367
378
|
def _load_usecli_config_file(config_path: Path) -> dict[str, Any]:
|
|
379
|
+
tomllib = _import_tomllib()
|
|
368
380
|
try:
|
|
369
381
|
data = tomllib.loads(config_path.read_text())
|
|
370
382
|
except (tomllib.TOMLDecodeError, OSError):
|
|
@@ -556,6 +568,7 @@ def _resolve_theme_path(
|
|
|
556
568
|
|
|
557
569
|
|
|
558
570
|
def _load_theme_file(theme_path: Path) -> dict[str, Any]:
|
|
571
|
+
tomllib = _import_tomllib()
|
|
559
572
|
try:
|
|
560
573
|
with open(theme_path, "rb") as theme_file:
|
|
561
574
|
data = tomllib.load(theme_file)
|
|
@@ -578,7 +591,15 @@ def _load_theme() -> tuple[dict[str, str], dict[str, str], str, Path | None]:
|
|
|
578
591
|
theme_name = config_theme.strip()
|
|
579
592
|
|
|
580
593
|
theme_path = _resolve_theme_path(theme_name, project_root, config_values)
|
|
581
|
-
|
|
594
|
+
package_default_theme = (THEMES_DIR / f"{DEFAULT_THEME_NAME}.toml").resolve()
|
|
595
|
+
if (
|
|
596
|
+
theme_name == DEFAULT_THEME_NAME
|
|
597
|
+
and theme_path
|
|
598
|
+
and theme_path.resolve() == package_default_theme
|
|
599
|
+
):
|
|
600
|
+
theme_data = {}
|
|
601
|
+
else:
|
|
602
|
+
theme_data = _load_theme_file(theme_path) if theme_path else {}
|
|
582
603
|
|
|
583
604
|
colors = _merge_theme_values(
|
|
584
605
|
DEFAULT_THEME_COLORS,
|
|
@@ -711,19 +732,49 @@ def _apply_theme(
|
|
|
711
732
|
|
|
712
733
|
|
|
713
734
|
def _ensure_theme_loaded(color_class: type[Any]) -> None:
|
|
714
|
-
global _THEME_CONTEXT
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
735
|
+
global _THEME_CONTEXT, _THEME_LOADED
|
|
736
|
+
if _THEME_LOADED:
|
|
737
|
+
context = _theme_context()
|
|
738
|
+
if context == _THEME_CONTEXT:
|
|
739
|
+
return
|
|
740
|
+
colors, ansi, _, _ = _load_theme()
|
|
741
|
+
_THEME_CONTEXT = context
|
|
742
|
+
_THEME_COLORS.update(colors)
|
|
743
|
+
_THEME_ANSI.update(ansi)
|
|
744
|
+
else:
|
|
745
|
+
colors, ansi, _, _ = _load_theme()
|
|
746
|
+
_THEME_CONTEXT = _theme_context()
|
|
747
|
+
_THEME_COLORS.update(colors)
|
|
748
|
+
_THEME_ANSI.update(ansi)
|
|
749
|
+
_THEME_LOADED = True
|
|
722
750
|
_apply_theme(cast(_ColorNamespace, color_class), _THEME_COLORS, _THEME_ANSI)
|
|
723
751
|
|
|
724
752
|
|
|
725
|
-
|
|
726
|
-
|
|
753
|
+
# Deferred: _load_theme() and _theme_context() are called on first COLOR attribute access
|
|
754
|
+
# via _ColorMeta.__getattribute__ -> _ensure_theme_loaded, instead of at import time.
|
|
755
|
+
# This avoids ~50ms of filesystem walking during startup when colors aren't needed yet.
|
|
756
|
+
_THEME_LOADED: bool = False
|
|
757
|
+
_THEME_COLORS: dict[str, str] = DEFAULT_THEME_COLORS.copy()
|
|
758
|
+
_THEME_ANSI: dict[str, str] = {
|
|
759
|
+
"reset": "\033[0m",
|
|
760
|
+
"primary": "",
|
|
761
|
+
"secondary": "",
|
|
762
|
+
"accent": "",
|
|
763
|
+
"foreground": "",
|
|
764
|
+
"foreground_muted": "",
|
|
765
|
+
"red": "",
|
|
766
|
+
"green": "",
|
|
767
|
+
"yellow": "",
|
|
768
|
+
"blue": "",
|
|
769
|
+
}
|
|
770
|
+
_THEME_NAME: str = DEFAULT_THEME_NAME
|
|
771
|
+
_THEME_PATH: Path | None = None
|
|
772
|
+
_THEME_CONTEXT: tuple[Path | None, Path | None, str, Path | None] = (
|
|
773
|
+
None,
|
|
774
|
+
None,
|
|
775
|
+
DEFAULT_THEME_NAME,
|
|
776
|
+
None,
|
|
777
|
+
)
|
|
727
778
|
|
|
728
779
|
|
|
729
780
|
class _ColorMeta(type):
|