usecli 0.1.58__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.58 → usecli-0.1.60}/PKG-INFO +4 -13
- {usecli-0.1.58 → usecli-0.1.60}/README.md +3 -12
- {usecli-0.1.58 → usecli-0.1.60}/pyproject.toml +1 -1
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/__init__.py +72 -11
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/about_command.py +67 -32
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/help_command.py +2 -1
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/inspire_command.py +4 -6
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/core/utils.py +16 -3
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/make/make_command.py +8 -4
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/make/make_theme_command.py +7 -3
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/init_command.py +31 -6
- {usecli-0.1.58 → 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.58 → 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.58 → usecli-0.1.60}/src/usecli/cli/core/ui/title.py +1 -2
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/services/command_service.py +49 -9
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -3
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/shared/config/manager.py +83 -37
- usecli-0.1.58/src/usecli/cli/core/__init__.py +0 -66
- usecli-0.1.58/src/usecli/cli/core/exceptions/__init__.py +0 -16
- usecli-0.1.58/src/usecli/cli/core/ui/__init__.py +0 -31
- {usecli-0.1.58 → usecli-0.1.60}/LICENSE +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/ui/list.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/ui/title.txt +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/menu.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/params.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/shared/config/globals.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/src/usecli/ui.py +0 -0
- {usecli-0.1.58 → usecli-0.1.60}/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.60
|
|
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>
|
|
@@ -182,19 +182,10 @@ choice = Menu(["A", "B", "C"]).show()
|
|
|
182
182
|
```
|
|
183
183
|
about Show app info
|
|
184
184
|
help Show help
|
|
185
|
-
init Initialize usecli
|
|
185
|
+
init Initialize usecli (usecli only)
|
|
186
186
|
inspire Random quote
|
|
187
|
-
make:command Create new command
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
### Hiding Built-in Commands
|
|
191
|
-
|
|
192
|
-
Add this to `usecli.config.toml`:
|
|
193
|
-
|
|
194
|
-
```toml
|
|
195
|
-
[usecli]
|
|
196
|
-
hide_init = true
|
|
197
|
-
hide_inspire = true
|
|
187
|
+
make:command Create new command (usecli only)
|
|
188
|
+
make:theme Create new theme (usecli only)
|
|
198
189
|
```
|
|
199
190
|
|
|
200
191
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
@@ -154,19 +154,10 @@ choice = Menu(["A", "B", "C"]).show()
|
|
|
154
154
|
```
|
|
155
155
|
about Show app info
|
|
156
156
|
help Show help
|
|
157
|
-
init Initialize usecli
|
|
157
|
+
init Initialize usecli (usecli only)
|
|
158
158
|
inspire Random quote
|
|
159
|
-
make:command Create new command
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
### Hiding Built-in Commands
|
|
163
|
-
|
|
164
|
-
Add this to `usecli.config.toml`:
|
|
165
|
-
|
|
166
|
-
```toml
|
|
167
|
-
[usecli]
|
|
168
|
-
hide_init = true
|
|
169
|
-
hide_inspire = true
|
|
159
|
+
make:command Create new command (usecli only)
|
|
160
|
+
make:theme Create new theme (usecli only)
|
|
170
161
|
```
|
|
171
162
|
|
|
172
163
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
@@ -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:
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
|
|
7
9
|
import typer
|
|
@@ -21,8 +23,8 @@ class MakeCommand(BaseCommand):
|
|
|
21
23
|
"""Command for generating new CLI command files."""
|
|
22
24
|
|
|
23
25
|
def visible(self) -> bool:
|
|
24
|
-
|
|
25
|
-
return
|
|
26
|
+
command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
|
|
27
|
+
return command_name == "usecli"
|
|
26
28
|
|
|
27
29
|
def signature(self) -> str:
|
|
28
30
|
"""Return the command signature."""
|
|
@@ -50,7 +52,8 @@ class MakeCommand(BaseCommand):
|
|
|
50
52
|
if config.get_project_root().resolve() != current_root:
|
|
51
53
|
reset_config()
|
|
52
54
|
config = get_config()
|
|
53
|
-
|
|
55
|
+
project_paths = config.get_project_paths()
|
|
56
|
+
commands_dir = project_paths["commands_dir"]
|
|
54
57
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
|
55
58
|
target_file = commands_dir / file_name
|
|
56
59
|
|
|
@@ -59,7 +62,8 @@ class MakeCommand(BaseCommand):
|
|
|
59
62
|
f"[{COLOR.ERROR}]Error: Command file {target_file} already exists.[/{COLOR.ERROR}]"
|
|
60
63
|
)
|
|
61
64
|
return
|
|
62
|
-
|
|
65
|
+
templates_dir = project_paths["templates_dir"]
|
|
66
|
+
project_template_path = templates_dir / "command.py.j2"
|
|
63
67
|
if project_template_path.exists():
|
|
64
68
|
template_path = project_template_path
|
|
65
69
|
else:
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
|
|
7
9
|
import typer
|
|
@@ -23,8 +25,8 @@ console = Console()
|
|
|
23
25
|
|
|
24
26
|
class MakeThemeCommand(BaseCommand):
|
|
25
27
|
def visible(self) -> bool:
|
|
26
|
-
|
|
27
|
-
return
|
|
28
|
+
command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
|
|
29
|
+
return command_name == "usecli"
|
|
28
30
|
|
|
29
31
|
def signature(self) -> str:
|
|
30
32
|
return "make:theme"
|
|
@@ -46,6 +48,7 @@ class MakeThemeCommand(BaseCommand):
|
|
|
46
48
|
reset_config()
|
|
47
49
|
config = get_config()
|
|
48
50
|
|
|
51
|
+
project_paths = config.get_project_paths()
|
|
49
52
|
themes_entries = self._normalize_theme_entries(config.get("themes_dir", []))
|
|
50
53
|
if not themes_entries:
|
|
51
54
|
console.print(
|
|
@@ -86,7 +89,8 @@ class MakeThemeCommand(BaseCommand):
|
|
|
86
89
|
break
|
|
87
90
|
counter += 1
|
|
88
91
|
|
|
89
|
-
|
|
92
|
+
templates_dir = project_paths["templates_dir"]
|
|
93
|
+
project_template_path = templates_dir / "theme.toml.j2"
|
|
90
94
|
if project_template_path.exists():
|
|
91
95
|
template_path = project_template_path
|
|
92
96
|
else:
|
|
@@ -29,15 +29,15 @@ from usecli.cli.core.exceptions import UsecliBadParameter
|
|
|
29
29
|
from usecli.cli.core.validators import validate_command_name
|
|
30
30
|
from usecli.cli.utils.interactive.terminal_menu import terminal_menu
|
|
31
31
|
from usecli.shared.config.globals import TEMPLATES_DIR, THEMES_DIR, USECLI_CONFIG_TOML
|
|
32
|
-
from usecli.shared.config.manager import ConfigManager
|
|
32
|
+
from usecli.shared.config.manager import ConfigManager
|
|
33
33
|
|
|
34
34
|
console = Console()
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class InitCommand(BaseCommand):
|
|
38
38
|
def visible(self) -> bool:
|
|
39
|
-
|
|
40
|
-
return
|
|
39
|
+
command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
|
|
40
|
+
return command_name == "usecli"
|
|
41
41
|
|
|
42
42
|
def signature(self) -> str:
|
|
43
43
|
return "init"
|
|
@@ -244,6 +244,30 @@ class InitCommand(BaseCommand):
|
|
|
244
244
|
return "themes"
|
|
245
245
|
return str(parent / "themes")
|
|
246
246
|
|
|
247
|
+
def _infer_commands_dir(self, project_root: Path) -> str:
|
|
248
|
+
src_dir = project_root / "src"
|
|
249
|
+
if src_dir.exists() and src_dir.is_dir():
|
|
250
|
+
packages = [
|
|
251
|
+
d
|
|
252
|
+
for d in src_dir.iterdir()
|
|
253
|
+
if d.is_dir() and not d.name.startswith((".", "_"))
|
|
254
|
+
]
|
|
255
|
+
if len(packages) == 1:
|
|
256
|
+
package_name = packages[0].name
|
|
257
|
+
return f"src/{package_name}/cli/commands"
|
|
258
|
+
pyproject_path = project_root / "pyproject.toml"
|
|
259
|
+
if pyproject_path.exists():
|
|
260
|
+
try:
|
|
261
|
+
data = tomllib.loads(pyproject_path.read_text())
|
|
262
|
+
project_name = data.get("project", {}).get("name")
|
|
263
|
+
if project_name:
|
|
264
|
+
package_name = project_name.replace("-", "_").replace(" ", "_")
|
|
265
|
+
if (project_root / package_name).is_dir():
|
|
266
|
+
return f"{package_name}/cli/commands"
|
|
267
|
+
except (tomllib.TOMLDecodeError, OSError):
|
|
268
|
+
pass
|
|
269
|
+
return "cli/commands"
|
|
270
|
+
|
|
247
271
|
def _get_existing_usecli_script_name(self, pyproject_path: Path) -> str | None:
|
|
248
272
|
if not pyproject_path.exists():
|
|
249
273
|
return None
|
|
@@ -532,9 +556,7 @@ include = ["{root_package}*"]
|
|
|
532
556
|
description: str = typer.Option(
|
|
533
557
|
"A custom CLI tool", help="Description for your CLI"
|
|
534
558
|
),
|
|
535
|
-
commands_dir: str = typer.Option(
|
|
536
|
-
"cli/commands", help="Directory for custom commands"
|
|
537
|
-
),
|
|
559
|
+
commands_dir: str = typer.Option(None, help="Directory for custom commands"),
|
|
538
560
|
command_name: Annotated[
|
|
539
561
|
str,
|
|
540
562
|
typer.Option(
|
|
@@ -554,6 +576,9 @@ include = ["{root_package}*"]
|
|
|
554
576
|
project_root / "pyproject.toml"
|
|
555
577
|
)
|
|
556
578
|
|
|
579
|
+
if commands_dir is None:
|
|
580
|
+
commands_dir = self._infer_commands_dir(project_root)
|
|
581
|
+
|
|
557
582
|
console.print()
|
|
558
583
|
existing_command_name = self._get_existing_usecli_script_name(pyproject_path)
|
|
559
584
|
if existing_command_name and command_name == "usecli":
|