usecli 0.1.33__tar.gz → 0.1.34__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.33 → usecli-0.1.34}/PKG-INFO +4 -2
- {usecli-0.1.33 → usecli-0.1.34}/README.md +3 -1
- {usecli-0.1.33 → usecli-0.1.34}/pyproject.toml +4 -9
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/init_command.py +29 -75
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/config/colors.py +55 -13
- usecli-0.1.33/src/usecli/cli/templates/usecli.toml.j2 → usecli-0.1.34/src/usecli/cli/templates/usecli.config.toml.j2 +1 -1
- usecli-0.1.34/src/usecli/cli/usecli.config.toml +13 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/shared/config/globals.py +1 -1
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/shared/config/manager.py +68 -71
- {usecli-0.1.33 → usecli-0.1.34}/LICENSE +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/base_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/exceptions/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/skill_generator.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/ui/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/ui/list.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/ui/title.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/services/command_service.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/menu.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/params.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.34}/src/usecli/ui.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usecli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.34
|
|
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>
|
|
@@ -190,8 +190,10 @@ make:command Create new command
|
|
|
190
190
|
|
|
191
191
|
### Hiding Built-in Commands
|
|
192
192
|
|
|
193
|
+
Add this to `usecli.config.toml`:
|
|
194
|
+
|
|
193
195
|
```toml
|
|
194
|
-
[
|
|
196
|
+
[usecli]
|
|
195
197
|
hide_init = true
|
|
196
198
|
hide_inspire = true
|
|
197
199
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "usecli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.34"
|
|
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" }]
|
|
@@ -81,11 +81,6 @@ exclude = ["**/__pycache__", "**/.venv"]
|
|
|
81
81
|
# Ty uses rule = "level" format (error, warn, ignore)
|
|
82
82
|
# See all rules at: https://docs.astral.sh/ty/reference/rules/
|
|
83
83
|
|
|
84
|
-
[tool.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
templates_dir = "src/usecli/cli/templates"
|
|
88
|
-
themes_dir = "src/usecli/cli/themes"
|
|
89
|
-
hide_init = false
|
|
90
|
-
hide_inspire = false
|
|
91
|
-
hide_make_command = false
|
|
84
|
+
[tool.setuptools.packages.find]
|
|
85
|
+
where = ["."]
|
|
86
|
+
include = ["cli*"]
|
|
@@ -27,7 +27,7 @@ from usecli.cli.core.base_command import BaseCommand
|
|
|
27
27
|
from usecli.cli.core.exceptions import UsecliBadParameter
|
|
28
28
|
from usecli.cli.core.validators import validate_command_name
|
|
29
29
|
from usecli.cli.utils.interactive.terminal_menu import terminal_menu
|
|
30
|
-
from usecli.shared.config.globals import TEMPLATES_DIR, THEMES_DIR,
|
|
30
|
+
from usecli.shared.config.globals import TEMPLATES_DIR, THEMES_DIR, USECLI_CONFIG_TOML
|
|
31
31
|
from usecli.shared.config.manager import ConfigManager, get_config
|
|
32
32
|
|
|
33
33
|
console = Console()
|
|
@@ -44,27 +44,6 @@ class InitCommand(BaseCommand):
|
|
|
44
44
|
def description(self) -> str:
|
|
45
45
|
return "Initialize usecli in the current project"
|
|
46
46
|
|
|
47
|
-
def _replace_config_in_pyproject(
|
|
48
|
-
self, pyproject_path: Path, config_content: str
|
|
49
|
-
) -> None:
|
|
50
|
-
"""Replace existing [tool.usecli] section in pyproject.toml."""
|
|
51
|
-
content = pyproject_path.read_text()
|
|
52
|
-
|
|
53
|
-
# Pattern to match [tool.usecli] section until next section or end of file
|
|
54
|
-
pattern = r"\[tool\.usecli\].*?(?=\n\[|\Z)"
|
|
55
|
-
replacement = config_content.rstrip() + "\n"
|
|
56
|
-
|
|
57
|
-
# Replace the existing section
|
|
58
|
-
new_content = re.sub(pattern, replacement, content, flags=re.DOTALL)
|
|
59
|
-
|
|
60
|
-
pyproject_path.write_text(new_content)
|
|
61
|
-
|
|
62
|
-
def _get_config_source(self, pyproject_path: Path) -> str | None:
|
|
63
|
-
"""Get the source of existing config."""
|
|
64
|
-
if pyproject_path.exists() and "[tool.usecli]" in pyproject_path.read_text():
|
|
65
|
-
return "pyproject.toml"
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
47
|
def _ensure_build_system(self, pyproject_path: Path) -> bool:
|
|
69
48
|
if not pyproject_path.exists():
|
|
70
49
|
return False
|
|
@@ -102,25 +81,29 @@ class InitCommand(BaseCommand):
|
|
|
102
81
|
f'include = ["{root_package}*"]\n\n'
|
|
103
82
|
)
|
|
104
83
|
|
|
105
|
-
|
|
106
|
-
content = content.replace(
|
|
107
|
-
"[tool.usecli]",
|
|
108
|
-
f"{discovery_block}[tool.usecli]",
|
|
109
|
-
)
|
|
110
|
-
else:
|
|
111
|
-
content = content.rstrip() + f"\n\n{discovery_block}"
|
|
84
|
+
content = content.rstrip() + f"\n\n{discovery_block}"
|
|
112
85
|
|
|
113
86
|
pyproject_path.write_text(content)
|
|
114
87
|
return True
|
|
115
88
|
|
|
116
|
-
def
|
|
117
|
-
self,
|
|
89
|
+
def _write_usecli_config(
|
|
90
|
+
self,
|
|
91
|
+
project_root: Path,
|
|
92
|
+
config_content: str,
|
|
93
|
+
force: bool,
|
|
94
|
+
commands_path: Path,
|
|
118
95
|
) -> str:
|
|
119
|
-
|
|
96
|
+
config_root = project_root
|
|
97
|
+
if commands_path.parent != project_root:
|
|
98
|
+
config_root = commands_path.parent
|
|
99
|
+
config_path = config_root / USECLI_CONFIG_TOML
|
|
100
|
+
discovered_config = ConfigManager(start_dir=config_root).usecli_config_path
|
|
101
|
+
if discovered_config.exists():
|
|
102
|
+
config_path = discovered_config
|
|
120
103
|
existed = config_path.exists()
|
|
121
104
|
if existed and not force:
|
|
122
105
|
should_overwrite = Confirm.ask(
|
|
123
|
-
f"[{COLOR.WARNING}]usecli.toml already exists at {config_path}.[/{COLOR.WARNING}]\n"
|
|
106
|
+
f"[{COLOR.WARNING}]usecli.config.toml already exists at {config_path}.[/{COLOR.WARNING}]\n"
|
|
124
107
|
"Overwrite it with the new settings from this init run?",
|
|
125
108
|
default=False,
|
|
126
109
|
)
|
|
@@ -466,7 +449,6 @@ class InitCommand(BaseCommand):
|
|
|
466
449
|
title: str,
|
|
467
450
|
description: str,
|
|
468
451
|
commands_dir: str,
|
|
469
|
-
config_content: str,
|
|
470
452
|
) -> None:
|
|
471
453
|
parts = Path(commands_dir).parts
|
|
472
454
|
root_package = parts[0] if parts else "src"
|
|
@@ -493,8 +475,7 @@ build-backend = "setuptools.build_meta"
|
|
|
493
475
|
[tool.setuptools.packages.find]
|
|
494
476
|
where = ["."]
|
|
495
477
|
include = ["{root_package}*"]
|
|
496
|
-
|
|
497
|
-
{config_content}'''
|
|
478
|
+
'''
|
|
498
479
|
|
|
499
480
|
pyproject_path.write_text(pyproject_content)
|
|
500
481
|
|
|
@@ -612,21 +593,6 @@ include = ["{root_package}*"]
|
|
|
612
593
|
console.print()
|
|
613
594
|
console.print(f"[{COLOR.PRIMARY}]{title_text}")
|
|
614
595
|
|
|
615
|
-
# Check if config already exists
|
|
616
|
-
existing_source = self._get_config_source(pyproject_path)
|
|
617
|
-
|
|
618
|
-
if existing_source and not force:
|
|
619
|
-
should_overwrite = Confirm.ask(
|
|
620
|
-
f"[{COLOR.WARNING}]usecli config already exists in {existing_source}.[/{COLOR.WARNING}]\n"
|
|
621
|
-
"Overwrite it with the new settings from this init run?",
|
|
622
|
-
default=False,
|
|
623
|
-
)
|
|
624
|
-
if not should_overwrite:
|
|
625
|
-
console.print(
|
|
626
|
-
f"[{COLOR.WARNING}]Skipping config update.[/{COLOR.WARNING}]"
|
|
627
|
-
)
|
|
628
|
-
return
|
|
629
|
-
|
|
630
596
|
# Create the commands directory
|
|
631
597
|
if not commands_path.exists():
|
|
632
598
|
commands_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -686,7 +652,9 @@ include = ["{root_package}*"]
|
|
|
686
652
|
)
|
|
687
653
|
|
|
688
654
|
# Load the template
|
|
689
|
-
template_path =
|
|
655
|
+
template_path = (
|
|
656
|
+
Path(__file__).parent.parent / "templates" / "usecli.config.toml.j2"
|
|
657
|
+
)
|
|
690
658
|
template_content = template_path.read_text()
|
|
691
659
|
template = Template(template_content)
|
|
692
660
|
|
|
@@ -702,23 +670,10 @@ include = ["{root_package}*"]
|
|
|
702
670
|
)
|
|
703
671
|
|
|
704
672
|
scripts_status: str | None = None
|
|
705
|
-
|
|
673
|
+
usecli_config_status: str | None = None
|
|
706
674
|
|
|
707
675
|
# Check if pyproject.toml exists
|
|
708
676
|
if pyproject_path.exists():
|
|
709
|
-
content = pyproject_path.read_text()
|
|
710
|
-
if "[tool.usecli]" in content:
|
|
711
|
-
self._replace_config_in_pyproject(pyproject_path, config_content)
|
|
712
|
-
console.print(
|
|
713
|
-
f"[{COLOR.SUCCESS}]Updated [tool.usecli] in {pyproject_path}[/{COLOR.SUCCESS}]"
|
|
714
|
-
)
|
|
715
|
-
else:
|
|
716
|
-
with open(pyproject_path, "a") as f:
|
|
717
|
-
f.write("\n\n" + config_content)
|
|
718
|
-
console.print(
|
|
719
|
-
f"[{COLOR.SUCCESS}]Added [tool.usecli] to {pyproject_path}[/{COLOR.SUCCESS}]"
|
|
720
|
-
)
|
|
721
|
-
|
|
722
677
|
scripts_status = self._ensure_project_scripts(
|
|
723
678
|
pyproject_path, command_name, force
|
|
724
679
|
)
|
|
@@ -753,7 +708,6 @@ include = ["{root_package}*"]
|
|
|
753
708
|
title,
|
|
754
709
|
description,
|
|
755
710
|
commands_dir,
|
|
756
|
-
config_content,
|
|
757
711
|
)
|
|
758
712
|
console.print(
|
|
759
713
|
f"[{COLOR.SUCCESS}]Created {pyproject_path}[/{COLOR.SUCCESS}]"
|
|
@@ -762,20 +716,20 @@ include = ["{root_package}*"]
|
|
|
762
716
|
|
|
763
717
|
self._sync_environment(project_root, command_name)
|
|
764
718
|
|
|
765
|
-
|
|
766
|
-
project_root, config_content, force
|
|
719
|
+
usecli_config_status = self._write_usecli_config(
|
|
720
|
+
project_root, config_content, force, commands_path
|
|
767
721
|
)
|
|
768
|
-
if
|
|
722
|
+
if usecli_config_status == "created":
|
|
769
723
|
console.print(
|
|
770
|
-
f"[{COLOR.SUCCESS}]Created {
|
|
724
|
+
f"[{COLOR.SUCCESS}]Created {USECLI_CONFIG_TOML} for runtime config fallback[/{COLOR.SUCCESS}]"
|
|
771
725
|
)
|
|
772
|
-
elif
|
|
726
|
+
elif usecli_config_status == "updated":
|
|
773
727
|
console.print(
|
|
774
|
-
f"[{COLOR.SUCCESS}]Updated {
|
|
728
|
+
f"[{COLOR.SUCCESS}]Updated {USECLI_CONFIG_TOML} for runtime config fallback[/{COLOR.SUCCESS}]"
|
|
775
729
|
)
|
|
776
|
-
elif
|
|
730
|
+
elif usecli_config_status == "skipped":
|
|
777
731
|
console.print(
|
|
778
|
-
f"[{COLOR.WARNING}]Skipped updating {
|
|
732
|
+
f"[{COLOR.WARNING}]Skipped updating {USECLI_CONFIG_TOML}.[/{COLOR.WARNING}]"
|
|
779
733
|
)
|
|
780
734
|
|
|
781
735
|
# Show summary
|
|
@@ -22,7 +22,7 @@ else:
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
PYPROJECT_TOML = "pyproject.toml"
|
|
25
|
-
|
|
25
|
+
USECLI_CONFIG_TOML = "usecli.config.toml"
|
|
26
26
|
DEFAULT_THEME_NAME = "default"
|
|
27
27
|
THEMES_DIR = Path(__file__).resolve().parent.parent / "themes"
|
|
28
28
|
DEFAULT_THEME_COLORS: dict[str, str] = {
|
|
@@ -48,55 +48,97 @@ DEFAULT_THEME_COLORS: dict[str, str] = {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _find_usecli_config_path(root_dir: Path, start_dir: Path) -> Path | None:
|
|
52
|
+
if not root_dir.exists() or not root_dir.is_dir():
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
candidates = [path for path in root_dir.rglob(USECLI_CONFIG_TOML)]
|
|
56
|
+
if not candidates:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
start_dir = start_dir.resolve()
|
|
60
|
+
preferred: list[Path] = []
|
|
61
|
+
for path in candidates:
|
|
62
|
+
try:
|
|
63
|
+
path.relative_to(start_dir)
|
|
64
|
+
preferred.append(path)
|
|
65
|
+
except ValueError:
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
selection = preferred or candidates
|
|
69
|
+
|
|
70
|
+
def _depth_key(path: Path) -> tuple[int, str]:
|
|
71
|
+
try:
|
|
72
|
+
relative = path.relative_to(start_dir)
|
|
73
|
+
return (len(relative.parts), str(path))
|
|
74
|
+
except ValueError:
|
|
75
|
+
relative = path.relative_to(root_dir)
|
|
76
|
+
return (len(relative.parts), str(path))
|
|
77
|
+
|
|
78
|
+
selection.sort(key=_depth_key)
|
|
79
|
+
return selection[0]
|
|
80
|
+
|
|
81
|
+
|
|
51
82
|
def _find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
52
83
|
if start_dir is None:
|
|
53
84
|
start_dir = Path.cwd()
|
|
54
85
|
|
|
55
86
|
current = start_dir.resolve()
|
|
87
|
+
git_root: Path | None = None
|
|
56
88
|
|
|
57
89
|
while True:
|
|
58
90
|
pyproject_path = current / PYPROJECT_TOML
|
|
59
91
|
if pyproject_path.exists():
|
|
60
92
|
return current
|
|
61
93
|
|
|
62
|
-
usecli_path = current /
|
|
94
|
+
usecli_path = current / USECLI_CONFIG_TOML
|
|
63
95
|
if usecli_path.exists():
|
|
64
96
|
return current
|
|
65
97
|
|
|
66
98
|
git_dir = current / ".git"
|
|
67
99
|
if git_dir.exists():
|
|
68
|
-
|
|
100
|
+
git_root = current
|
|
101
|
+
break
|
|
69
102
|
|
|
70
103
|
parent = current.parent
|
|
71
104
|
if parent == current:
|
|
72
105
|
break
|
|
73
106
|
current = parent
|
|
74
107
|
|
|
75
|
-
|
|
108
|
+
search_root = git_root or start_dir.resolve()
|
|
109
|
+
config_match = _find_usecli_config_path(search_root, start_dir)
|
|
110
|
+
if config_match:
|
|
111
|
+
return config_match.parent
|
|
112
|
+
|
|
113
|
+
return git_root
|
|
76
114
|
|
|
77
115
|
|
|
78
116
|
def _load_usecli_config(project_root: Path | None) -> dict[str, Any]:
|
|
79
117
|
if project_root is None:
|
|
80
118
|
return {}
|
|
81
119
|
|
|
82
|
-
|
|
83
|
-
if not
|
|
120
|
+
config_path = project_root / USECLI_CONFIG_TOML
|
|
121
|
+
if not config_path.exists():
|
|
122
|
+
config_path = _find_usecli_config_path(project_root, project_root)
|
|
123
|
+
if not config_path or not config_path.exists():
|
|
84
124
|
return {}
|
|
85
125
|
|
|
86
126
|
try:
|
|
87
|
-
data = tomllib.loads(
|
|
127
|
+
data = tomllib.loads(config_path.read_text())
|
|
88
128
|
except (tomllib.TOMLDecodeError, OSError):
|
|
89
129
|
return {}
|
|
90
130
|
|
|
91
131
|
tool = data.get("tool", {})
|
|
92
|
-
if
|
|
93
|
-
|
|
132
|
+
if isinstance(tool, dict) and "usecli" in tool:
|
|
133
|
+
usecli_config = tool.get("usecli")
|
|
134
|
+
if isinstance(usecli_config, dict):
|
|
135
|
+
return usecli_config
|
|
94
136
|
|
|
95
|
-
|
|
96
|
-
if
|
|
97
|
-
return
|
|
137
|
+
usecli_section = data.get("usecli", {})
|
|
138
|
+
if isinstance(usecli_section, dict):
|
|
139
|
+
return usecli_section
|
|
98
140
|
|
|
99
|
-
return
|
|
141
|
+
return {}
|
|
100
142
|
|
|
101
143
|
|
|
102
144
|
def _normalize_color(value: Any) -> str | None:
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[usecli]
|
|
2
|
+
title = "usecli"
|
|
3
|
+
title_file = ""
|
|
4
|
+
title_font = "ansi_shadow"
|
|
5
|
+
description = "A custom CLI tool"
|
|
6
|
+
commands_dir = "src/usecli/cli/commands/custom"
|
|
7
|
+
templates_dir = "src/usecli/cli/templates"
|
|
8
|
+
themes_dir = "src/usecli/cli/themes"
|
|
9
|
+
theme = "default"
|
|
10
|
+
hide_init = false
|
|
11
|
+
hide_inspire = false
|
|
12
|
+
hide_make_command = false
|
|
13
|
+
hide_make_theme = false
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"""Configuration manager for useCli CLI.
|
|
2
2
|
|
|
3
3
|
Handles loading and accessing configuration from project-level files.
|
|
4
|
-
Configuration is loaded from (in priority order):
|
|
5
|
-
1. pyproject.toml [tool.usecli] section (preferred for Python projects)
|
|
6
4
|
"""
|
|
7
5
|
|
|
8
6
|
from __future__ import annotations
|
|
@@ -12,7 +10,7 @@ from pathlib import Path
|
|
|
12
10
|
from typing import Any
|
|
13
11
|
|
|
14
12
|
from usecli.cli.core.exceptions.config import UsecliConfigError
|
|
15
|
-
from usecli.shared.config.globals import PYPROJECT_TOML,
|
|
13
|
+
from usecli.shared.config.globals import PYPROJECT_TOML, USECLI_CONFIG_TOML
|
|
16
14
|
|
|
17
15
|
if sys.version_info >= (3, 11):
|
|
18
16
|
import tomllib
|
|
@@ -58,16 +56,7 @@ def _dedupe_items(items: list[str]) -> list[str]:
|
|
|
58
56
|
|
|
59
57
|
|
|
60
58
|
class ConfigManager:
|
|
61
|
-
"""Manages useCli configuration from project-level files.
|
|
62
|
-
|
|
63
|
-
Configuration is loaded from:
|
|
64
|
-
1. pyproject.toml [tool.usecli] in current directory (highest priority)
|
|
65
|
-
2. Default values (lowest priority)
|
|
66
|
-
|
|
67
|
-
Attributes:
|
|
68
|
-
pyproject_path: Path to pyproject.toml in current directory.
|
|
69
|
-
_config: The merged configuration dictionary.
|
|
70
|
-
"""
|
|
59
|
+
"""Manages useCli configuration from project-level files."""
|
|
71
60
|
|
|
72
61
|
DEFAULT_CONFIG: dict[str, Any] = {
|
|
73
62
|
"title": "usecli",
|
|
@@ -88,7 +77,7 @@ class ConfigManager:
|
|
|
88
77
|
def __init__(
|
|
89
78
|
self,
|
|
90
79
|
pyproject_path: Path | None = None,
|
|
91
|
-
|
|
80
|
+
usecli_config_path: Path | None = None,
|
|
92
81
|
start_dir: Path | None = None,
|
|
93
82
|
) -> None:
|
|
94
83
|
"""Initialize the configuration manager.
|
|
@@ -107,17 +96,17 @@ class ConfigManager:
|
|
|
107
96
|
start_dir / PYPROJECT_TOML
|
|
108
97
|
)
|
|
109
98
|
|
|
110
|
-
if
|
|
111
|
-
|
|
112
|
-
start_dir /
|
|
99
|
+
if usecli_config_path is None:
|
|
100
|
+
usecli_config_path = self._find_usecli_config(start_dir) or (
|
|
101
|
+
start_dir / USECLI_CONFIG_TOML
|
|
113
102
|
)
|
|
114
103
|
|
|
115
104
|
self.pyproject_path: Path = pyproject_path
|
|
116
|
-
self.
|
|
105
|
+
self.usecli_config_path: Path = usecli_config_path
|
|
117
106
|
self.start_dir: Path = start_dir
|
|
118
107
|
detected_root = find_project_root(start_dir)
|
|
119
|
-
if detected_root is None and self.
|
|
120
|
-
detected_root = self.
|
|
108
|
+
if detected_root is None and self.usecli_config_path.exists():
|
|
109
|
+
detected_root = self.usecli_config_path.parent
|
|
121
110
|
self.project_root: Path = (detected_root or start_dir).resolve()
|
|
122
111
|
self._config: dict[str, Any] = {}
|
|
123
112
|
self._overrides: dict[str, Any] = {}
|
|
@@ -128,30 +117,16 @@ class ConfigManager:
|
|
|
128
117
|
self._config = self.DEFAULT_CONFIG.copy()
|
|
129
118
|
self._overrides = {}
|
|
130
119
|
|
|
131
|
-
|
|
132
|
-
if self.pyproject_path.exists():
|
|
133
|
-
try:
|
|
134
|
-
pyproject_config = self._load_pyproject_toml(self.pyproject_path)
|
|
135
|
-
if pyproject_config:
|
|
136
|
-
self._config = _deep_merge(self._config, pyproject_config)
|
|
137
|
-
self._overrides = _deep_merge(self._overrides, pyproject_config)
|
|
138
|
-
loaded = True
|
|
139
|
-
except (tomllib.TOMLDecodeError, OSError) as e:
|
|
140
|
-
raise UsecliConfigError(
|
|
141
|
-
f"Failed to load pyproject.toml: {e}",
|
|
142
|
-
config_file=str(self.pyproject_path),
|
|
143
|
-
) from e
|
|
144
|
-
|
|
145
|
-
if not loaded and self.usecli_toml_path.exists():
|
|
120
|
+
if self.usecli_config_path.exists():
|
|
146
121
|
try:
|
|
147
|
-
usecli_config = self._load_usecli_toml(self.
|
|
122
|
+
usecli_config = self._load_usecli_toml(self.usecli_config_path)
|
|
148
123
|
if usecli_config:
|
|
149
124
|
self._config = _deep_merge(self._config, usecli_config)
|
|
150
125
|
self._overrides = _deep_merge(self._overrides, usecli_config)
|
|
151
126
|
except (tomllib.TOMLDecodeError, OSError) as e:
|
|
152
127
|
raise UsecliConfigError(
|
|
153
|
-
f"Failed to load
|
|
154
|
-
config_file=str(self.
|
|
128
|
+
f"Failed to load {USECLI_CONFIG_TOML}: {e}",
|
|
129
|
+
config_file=str(self.usecli_config_path),
|
|
155
130
|
) from e
|
|
156
131
|
|
|
157
132
|
default_themes = _normalize_themes_dir(self.DEFAULT_CONFIG.get("themes_dir"))
|
|
@@ -188,11 +163,11 @@ class ConfigManager:
|
|
|
188
163
|
return None
|
|
189
164
|
|
|
190
165
|
@classmethod
|
|
191
|
-
def
|
|
166
|
+
def _find_usecli_config(cls, start_dir: Path) -> Path | None:
|
|
192
167
|
current = start_dir.resolve()
|
|
193
168
|
|
|
194
169
|
while True:
|
|
195
|
-
config_path = current /
|
|
170
|
+
config_path = current / USECLI_CONFIG_TOML
|
|
196
171
|
if config_path.exists():
|
|
197
172
|
return config_path
|
|
198
173
|
|
|
@@ -201,46 +176,68 @@ class ConfigManager:
|
|
|
201
176
|
break
|
|
202
177
|
current = parent
|
|
203
178
|
|
|
204
|
-
|
|
179
|
+
search_root = find_project_root(start_dir) or start_dir.resolve()
|
|
180
|
+
recursive_match = cls._find_usecli_config_in_tree(search_root, start_dir)
|
|
181
|
+
if recursive_match:
|
|
182
|
+
return recursive_match
|
|
183
|
+
|
|
184
|
+
return cls._find_usecli_config_on_sys_path()
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def _find_usecli_config_in_tree(root_dir: Path, start_dir: Path) -> Path | None:
|
|
188
|
+
if not root_dir.exists() or not root_dir.is_dir():
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
candidates = [path for path in root_dir.rglob(USECLI_CONFIG_TOML)]
|
|
192
|
+
if not candidates:
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
start_dir = start_dir.resolve()
|
|
196
|
+
preferred: list[Path] = []
|
|
197
|
+
for path in candidates:
|
|
198
|
+
try:
|
|
199
|
+
path.relative_to(start_dir)
|
|
200
|
+
preferred.append(path)
|
|
201
|
+
except ValueError:
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
selection = preferred or candidates
|
|
205
|
+
|
|
206
|
+
def _depth_key(path: Path) -> tuple[int, str]:
|
|
207
|
+
try:
|
|
208
|
+
relative = path.relative_to(start_dir)
|
|
209
|
+
return (len(relative.parts), str(path))
|
|
210
|
+
except ValueError:
|
|
211
|
+
relative = path.relative_to(root_dir)
|
|
212
|
+
return (len(relative.parts), str(path))
|
|
213
|
+
|
|
214
|
+
selection.sort(key=_depth_key)
|
|
215
|
+
return selection[0]
|
|
205
216
|
|
|
206
217
|
@staticmethod
|
|
207
|
-
def
|
|
218
|
+
def _find_usecli_config_on_sys_path() -> Path | None:
|
|
208
219
|
for entry in sys.path:
|
|
209
220
|
if not entry:
|
|
210
221
|
continue
|
|
211
222
|
path = Path(entry)
|
|
212
223
|
if not path.exists() or not path.is_dir():
|
|
213
224
|
continue
|
|
214
|
-
candidate = path /
|
|
225
|
+
candidate = path / USECLI_CONFIG_TOML
|
|
215
226
|
if candidate.exists():
|
|
216
227
|
return candidate
|
|
217
|
-
for child in path.glob(f"*/{
|
|
228
|
+
for child in path.glob(f"*/{USECLI_CONFIG_TOML}"):
|
|
218
229
|
if child.exists():
|
|
219
230
|
return child
|
|
220
231
|
return None
|
|
221
232
|
|
|
222
|
-
@staticmethod
|
|
223
|
-
def _load_pyproject_toml(path: Path) -> dict[str, Any]:
|
|
224
|
-
"""Load pyproject.toml and return [tool.usecli] section.
|
|
225
|
-
|
|
226
|
-
Args:
|
|
227
|
-
path: Path to the pyproject.toml file.
|
|
228
|
-
|
|
229
|
-
Returns:
|
|
230
|
-
Parsed [tool.usecli] content as a dictionary, or empty dict.
|
|
231
|
-
"""
|
|
232
|
-
with open(path, "rb") as f:
|
|
233
|
-
data = tomllib.load(f)
|
|
234
|
-
return data.get("tool", {}).get("usecli", {})
|
|
235
|
-
|
|
236
233
|
@staticmethod
|
|
237
234
|
def _load_usecli_toml(path: Path) -> dict[str, Any]:
|
|
238
235
|
with open(path, "rb") as f:
|
|
239
236
|
data = tomllib.load(f)
|
|
240
237
|
|
|
241
238
|
tool_section = data.get("tool", {})
|
|
242
|
-
if isinstance(tool_section, dict):
|
|
243
|
-
usecli_section = tool_section.get("usecli"
|
|
239
|
+
if isinstance(tool_section, dict) and "usecli" in tool_section:
|
|
240
|
+
usecli_section = tool_section.get("usecli")
|
|
244
241
|
if isinstance(usecli_section, dict):
|
|
245
242
|
return usecli_section
|
|
246
243
|
|
|
@@ -329,14 +326,7 @@ class ConfigManager:
|
|
|
329
326
|
|
|
330
327
|
@property
|
|
331
328
|
def pyproject_exists(self) -> bool:
|
|
332
|
-
|
|
333
|
-
if self.pyproject_path.exists() and self._pyproject_has_usecli(
|
|
334
|
-
self.pyproject_path
|
|
335
|
-
):
|
|
336
|
-
return True
|
|
337
|
-
if self.usecli_toml_path.exists():
|
|
338
|
-
return True
|
|
339
|
-
return False
|
|
329
|
+
return self.usecli_config_path.exists()
|
|
340
330
|
|
|
341
331
|
@staticmethod
|
|
342
332
|
def _load_project_version(path: Path) -> str | None:
|
|
@@ -382,22 +372,29 @@ def find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
|
382
372
|
|
|
383
373
|
current = start_dir.resolve()
|
|
384
374
|
|
|
375
|
+
git_root: Path | None = None
|
|
385
376
|
while True:
|
|
386
377
|
pyproject_path = current / PYPROJECT_TOML
|
|
387
378
|
if pyproject_path.exists():
|
|
388
379
|
return current
|
|
389
380
|
|
|
390
|
-
usecli_path = current /
|
|
381
|
+
usecli_path = current / USECLI_CONFIG_TOML
|
|
391
382
|
if usecli_path.exists():
|
|
392
383
|
return current
|
|
393
384
|
|
|
394
385
|
git_dir = current / ".git"
|
|
395
386
|
if git_dir.exists():
|
|
396
|
-
|
|
387
|
+
git_root = current
|
|
388
|
+
break
|
|
397
389
|
|
|
398
390
|
parent = current.parent
|
|
399
391
|
if parent == current:
|
|
400
392
|
break
|
|
401
393
|
current = parent
|
|
402
394
|
|
|
403
|
-
|
|
395
|
+
search_root = git_root or start_dir.resolve()
|
|
396
|
+
config_match = ConfigManager._find_usecli_config_in_tree(search_root, start_dir)
|
|
397
|
+
if config_match:
|
|
398
|
+
return config_match.parent
|
|
399
|
+
|
|
400
|
+
return git_root
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{usecli-0.1.33 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|