usecli 0.1.32__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.32 → usecli-0.1.34}/PKG-INFO +4 -2
- {usecli-0.1.32 → usecli-0.1.34}/README.md +3 -1
- {usecli-0.1.32 → usecli-0.1.34}/pyproject.toml +4 -9
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/init_command.py +50 -62
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/config/colors.py +60 -12
- usecli-0.1.32/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.32 → usecli-0.1.34}/src/usecli/shared/config/globals.py +1 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/shared/config/manager.py +116 -37
- {usecli-0.1.32 → usecli-0.1.34}/LICENSE +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/base_command.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/exceptions/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/skill_generator.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/ui/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/ui/list.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/ui/title.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/services/command_service.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/menu.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/params.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.32 → usecli-0.1.34}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.32 → 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,17 +81,38 @@ 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
|
|
|
89
|
+
def _write_usecli_config(
|
|
90
|
+
self,
|
|
91
|
+
project_root: Path,
|
|
92
|
+
config_content: str,
|
|
93
|
+
force: bool,
|
|
94
|
+
commands_path: Path,
|
|
95
|
+
) -> str:
|
|
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
|
|
103
|
+
existed = config_path.exists()
|
|
104
|
+
if existed and not force:
|
|
105
|
+
should_overwrite = Confirm.ask(
|
|
106
|
+
f"[{COLOR.WARNING}]usecli.config.toml already exists at {config_path}.[/{COLOR.WARNING}]\n"
|
|
107
|
+
"Overwrite it with the new settings from this init run?",
|
|
108
|
+
default=False,
|
|
109
|
+
)
|
|
110
|
+
if not should_overwrite:
|
|
111
|
+
return "skipped"
|
|
112
|
+
|
|
113
|
+
config_path.write_text(config_content.rstrip() + "\n")
|
|
114
|
+
return "updated" if existed else "created"
|
|
115
|
+
|
|
116
116
|
def _ensure_project_scripts(
|
|
117
117
|
self, pyproject_path: Path, command_name: str, force: bool
|
|
118
118
|
) -> str:
|
|
@@ -449,7 +449,6 @@ class InitCommand(BaseCommand):
|
|
|
449
449
|
title: str,
|
|
450
450
|
description: str,
|
|
451
451
|
commands_dir: str,
|
|
452
|
-
config_content: str,
|
|
453
452
|
) -> None:
|
|
454
453
|
parts = Path(commands_dir).parts
|
|
455
454
|
root_package = parts[0] if parts else "src"
|
|
@@ -476,8 +475,7 @@ build-backend = "setuptools.build_meta"
|
|
|
476
475
|
[tool.setuptools.packages.find]
|
|
477
476
|
where = ["."]
|
|
478
477
|
include = ["{root_package}*"]
|
|
479
|
-
|
|
480
|
-
{config_content}'''
|
|
478
|
+
'''
|
|
481
479
|
|
|
482
480
|
pyproject_path.write_text(pyproject_content)
|
|
483
481
|
|
|
@@ -595,21 +593,6 @@ include = ["{root_package}*"]
|
|
|
595
593
|
console.print()
|
|
596
594
|
console.print(f"[{COLOR.PRIMARY}]{title_text}")
|
|
597
595
|
|
|
598
|
-
# Check if config already exists
|
|
599
|
-
existing_source = self._get_config_source(pyproject_path)
|
|
600
|
-
|
|
601
|
-
if existing_source and not force:
|
|
602
|
-
should_overwrite = Confirm.ask(
|
|
603
|
-
f"[{COLOR.WARNING}]usecli config already exists in {existing_source}.[/{COLOR.WARNING}]\n"
|
|
604
|
-
"Overwrite it with the new settings from this init run?",
|
|
605
|
-
default=False,
|
|
606
|
-
)
|
|
607
|
-
if not should_overwrite:
|
|
608
|
-
console.print(
|
|
609
|
-
f"[{COLOR.WARNING}]Skipping config update.[/{COLOR.WARNING}]"
|
|
610
|
-
)
|
|
611
|
-
return
|
|
612
|
-
|
|
613
596
|
# Create the commands directory
|
|
614
597
|
if not commands_path.exists():
|
|
615
598
|
commands_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -669,7 +652,9 @@ include = ["{root_package}*"]
|
|
|
669
652
|
)
|
|
670
653
|
|
|
671
654
|
# Load the template
|
|
672
|
-
template_path =
|
|
655
|
+
template_path = (
|
|
656
|
+
Path(__file__).parent.parent / "templates" / "usecli.config.toml.j2"
|
|
657
|
+
)
|
|
673
658
|
template_content = template_path.read_text()
|
|
674
659
|
template = Template(template_content)
|
|
675
660
|
|
|
@@ -685,22 +670,10 @@ include = ["{root_package}*"]
|
|
|
685
670
|
)
|
|
686
671
|
|
|
687
672
|
scripts_status: str | None = None
|
|
673
|
+
usecli_config_status: str | None = None
|
|
688
674
|
|
|
689
675
|
# Check if pyproject.toml exists
|
|
690
676
|
if pyproject_path.exists():
|
|
691
|
-
content = pyproject_path.read_text()
|
|
692
|
-
if "[tool.usecli]" in content:
|
|
693
|
-
self._replace_config_in_pyproject(pyproject_path, config_content)
|
|
694
|
-
console.print(
|
|
695
|
-
f"[{COLOR.SUCCESS}]Updated [tool.usecli] in {pyproject_path}[/{COLOR.SUCCESS}]"
|
|
696
|
-
)
|
|
697
|
-
else:
|
|
698
|
-
with open(pyproject_path, "a") as f:
|
|
699
|
-
f.write("\n\n" + config_content)
|
|
700
|
-
console.print(
|
|
701
|
-
f"[{COLOR.SUCCESS}]Added [tool.usecli] to {pyproject_path}[/{COLOR.SUCCESS}]"
|
|
702
|
-
)
|
|
703
|
-
|
|
704
677
|
scripts_status = self._ensure_project_scripts(
|
|
705
678
|
pyproject_path, command_name, force
|
|
706
679
|
)
|
|
@@ -735,7 +708,6 @@ include = ["{root_package}*"]
|
|
|
735
708
|
title,
|
|
736
709
|
description,
|
|
737
710
|
commands_dir,
|
|
738
|
-
config_content,
|
|
739
711
|
)
|
|
740
712
|
console.print(
|
|
741
713
|
f"[{COLOR.SUCCESS}]Created {pyproject_path}[/{COLOR.SUCCESS}]"
|
|
@@ -744,6 +716,22 @@ include = ["{root_package}*"]
|
|
|
744
716
|
|
|
745
717
|
self._sync_environment(project_root, command_name)
|
|
746
718
|
|
|
719
|
+
usecli_config_status = self._write_usecli_config(
|
|
720
|
+
project_root, config_content, force, commands_path
|
|
721
|
+
)
|
|
722
|
+
if usecli_config_status == "created":
|
|
723
|
+
console.print(
|
|
724
|
+
f"[{COLOR.SUCCESS}]Created {USECLI_CONFIG_TOML} for runtime config fallback[/{COLOR.SUCCESS}]"
|
|
725
|
+
)
|
|
726
|
+
elif usecli_config_status == "updated":
|
|
727
|
+
console.print(
|
|
728
|
+
f"[{COLOR.SUCCESS}]Updated {USECLI_CONFIG_TOML} for runtime config fallback[/{COLOR.SUCCESS}]"
|
|
729
|
+
)
|
|
730
|
+
elif usecli_config_status == "skipped":
|
|
731
|
+
console.print(
|
|
732
|
+
f"[{COLOR.WARNING}]Skipped updating {USECLI_CONFIG_TOML}.[/{COLOR.WARNING}]"
|
|
733
|
+
)
|
|
734
|
+
|
|
747
735
|
# Show summary
|
|
748
736
|
summary_command = (
|
|
749
737
|
command_name
|
|
@@ -21,6 +21,8 @@ else:
|
|
|
21
21
|
import tomli as tomllib
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
PYPROJECT_TOML = "pyproject.toml"
|
|
25
|
+
USECLI_CONFIG_TOML = "usecli.config.toml"
|
|
24
26
|
DEFAULT_THEME_NAME = "default"
|
|
25
27
|
THEMES_DIR = Path(__file__).resolve().parent.parent / "themes"
|
|
26
28
|
DEFAULT_THEME_COLORS: dict[str, str] = {
|
|
@@ -46,51 +48,97 @@ DEFAULT_THEME_COLORS: dict[str, str] = {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
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
|
+
|
|
49
82
|
def _find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
50
83
|
if start_dir is None:
|
|
51
84
|
start_dir = Path.cwd()
|
|
52
85
|
|
|
53
86
|
current = start_dir.resolve()
|
|
87
|
+
git_root: Path | None = None
|
|
54
88
|
|
|
55
89
|
while True:
|
|
56
|
-
pyproject_path = current /
|
|
90
|
+
pyproject_path = current / PYPROJECT_TOML
|
|
57
91
|
if pyproject_path.exists():
|
|
58
92
|
return current
|
|
59
93
|
|
|
94
|
+
usecli_path = current / USECLI_CONFIG_TOML
|
|
95
|
+
if usecli_path.exists():
|
|
96
|
+
return current
|
|
97
|
+
|
|
60
98
|
git_dir = current / ".git"
|
|
61
99
|
if git_dir.exists():
|
|
62
|
-
|
|
100
|
+
git_root = current
|
|
101
|
+
break
|
|
63
102
|
|
|
64
103
|
parent = current.parent
|
|
65
104
|
if parent == current:
|
|
66
105
|
break
|
|
67
106
|
current = parent
|
|
68
107
|
|
|
69
|
-
|
|
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
|
|
70
114
|
|
|
71
115
|
|
|
72
116
|
def _load_usecli_config(project_root: Path | None) -> dict[str, Any]:
|
|
73
117
|
if project_root is None:
|
|
74
118
|
return {}
|
|
75
119
|
|
|
76
|
-
|
|
77
|
-
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():
|
|
78
124
|
return {}
|
|
79
125
|
|
|
80
126
|
try:
|
|
81
|
-
data = tomllib.loads(
|
|
127
|
+
data = tomllib.loads(config_path.read_text())
|
|
82
128
|
except (tomllib.TOMLDecodeError, OSError):
|
|
83
129
|
return {}
|
|
84
130
|
|
|
85
131
|
tool = data.get("tool", {})
|
|
86
|
-
if
|
|
87
|
-
|
|
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
|
|
88
136
|
|
|
89
|
-
|
|
90
|
-
if
|
|
91
|
-
return
|
|
137
|
+
usecli_section = data.get("usecli", {})
|
|
138
|
+
if isinstance(usecli_section, dict):
|
|
139
|
+
return usecli_section
|
|
92
140
|
|
|
93
|
-
return
|
|
141
|
+
return {}
|
|
94
142
|
|
|
95
143
|
|
|
96
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,6 +10,7 @@ from pathlib import Path
|
|
|
12
10
|
from typing import Any
|
|
13
11
|
|
|
14
12
|
from usecli.cli.core.exceptions.config import UsecliConfigError
|
|
13
|
+
from usecli.shared.config.globals import PYPROJECT_TOML, USECLI_CONFIG_TOML
|
|
15
14
|
|
|
16
15
|
if sys.version_info >= (3, 11):
|
|
17
16
|
import tomllib
|
|
@@ -57,16 +56,7 @@ def _dedupe_items(items: list[str]) -> list[str]:
|
|
|
57
56
|
|
|
58
57
|
|
|
59
58
|
class ConfigManager:
|
|
60
|
-
"""Manages useCli configuration from project-level files.
|
|
61
|
-
|
|
62
|
-
Configuration is loaded from:
|
|
63
|
-
1. pyproject.toml [tool.usecli] in current directory (highest priority)
|
|
64
|
-
2. Default values (lowest priority)
|
|
65
|
-
|
|
66
|
-
Attributes:
|
|
67
|
-
pyproject_path: Path to pyproject.toml in current directory.
|
|
68
|
-
_config: The merged configuration dictionary.
|
|
69
|
-
"""
|
|
59
|
+
"""Manages useCli configuration from project-level files."""
|
|
70
60
|
|
|
71
61
|
DEFAULT_CONFIG: dict[str, Any] = {
|
|
72
62
|
"title": "usecli",
|
|
@@ -87,6 +77,7 @@ class ConfigManager:
|
|
|
87
77
|
def __init__(
|
|
88
78
|
self,
|
|
89
79
|
pyproject_path: Path | None = None,
|
|
80
|
+
usecli_config_path: Path | None = None,
|
|
90
81
|
start_dir: Path | None = None,
|
|
91
82
|
) -> None:
|
|
92
83
|
"""Initialize the configuration manager.
|
|
@@ -102,12 +93,21 @@ class ConfigManager:
|
|
|
102
93
|
|
|
103
94
|
if pyproject_path is None:
|
|
104
95
|
pyproject_path = self._find_pyproject_toml(start_dir) or (
|
|
105
|
-
start_dir /
|
|
96
|
+
start_dir / PYPROJECT_TOML
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if usecli_config_path is None:
|
|
100
|
+
usecli_config_path = self._find_usecli_config(start_dir) or (
|
|
101
|
+
start_dir / USECLI_CONFIG_TOML
|
|
106
102
|
)
|
|
107
103
|
|
|
108
104
|
self.pyproject_path: Path = pyproject_path
|
|
105
|
+
self.usecli_config_path: Path = usecli_config_path
|
|
109
106
|
self.start_dir: Path = start_dir
|
|
110
|
-
|
|
107
|
+
detected_root = find_project_root(start_dir)
|
|
108
|
+
if detected_root is None and self.usecli_config_path.exists():
|
|
109
|
+
detected_root = self.usecli_config_path.parent
|
|
110
|
+
self.project_root: Path = (detected_root or start_dir).resolve()
|
|
111
111
|
self._config: dict[str, Any] = {}
|
|
112
112
|
self._overrides: dict[str, Any] = {}
|
|
113
113
|
self._load_config()
|
|
@@ -117,16 +117,16 @@ class ConfigManager:
|
|
|
117
117
|
self._config = self.DEFAULT_CONFIG.copy()
|
|
118
118
|
self._overrides = {}
|
|
119
119
|
|
|
120
|
-
if self.
|
|
120
|
+
if self.usecli_config_path.exists():
|
|
121
121
|
try:
|
|
122
|
-
|
|
123
|
-
if
|
|
124
|
-
self._config = _deep_merge(self._config,
|
|
125
|
-
self._overrides = _deep_merge(self._overrides,
|
|
122
|
+
usecli_config = self._load_usecli_toml(self.usecli_config_path)
|
|
123
|
+
if usecli_config:
|
|
124
|
+
self._config = _deep_merge(self._config, usecli_config)
|
|
125
|
+
self._overrides = _deep_merge(self._overrides, usecli_config)
|
|
126
126
|
except (tomllib.TOMLDecodeError, OSError) as e:
|
|
127
127
|
raise UsecliConfigError(
|
|
128
|
-
f"Failed to load
|
|
129
|
-
config_file=str(self.
|
|
128
|
+
f"Failed to load {USECLI_CONFIG_TOML}: {e}",
|
|
129
|
+
config_file=str(self.usecli_config_path),
|
|
130
130
|
) from e
|
|
131
131
|
|
|
132
132
|
default_themes = _normalize_themes_dir(self.DEFAULT_CONFIG.get("themes_dir"))
|
|
@@ -151,7 +151,7 @@ class ConfigManager:
|
|
|
151
151
|
current = start_dir.resolve()
|
|
152
152
|
|
|
153
153
|
while True:
|
|
154
|
-
pyproject_path = current /
|
|
154
|
+
pyproject_path = current / PYPROJECT_TOML
|
|
155
155
|
if pyproject_path.exists():
|
|
156
156
|
return pyproject_path
|
|
157
157
|
|
|
@@ -162,19 +162,90 @@ class ConfigManager:
|
|
|
162
162
|
|
|
163
163
|
return None
|
|
164
164
|
|
|
165
|
+
@classmethod
|
|
166
|
+
def _find_usecli_config(cls, start_dir: Path) -> Path | None:
|
|
167
|
+
current = start_dir.resolve()
|
|
168
|
+
|
|
169
|
+
while True:
|
|
170
|
+
config_path = current / USECLI_CONFIG_TOML
|
|
171
|
+
if config_path.exists():
|
|
172
|
+
return config_path
|
|
173
|
+
|
|
174
|
+
parent = current.parent
|
|
175
|
+
if parent == current:
|
|
176
|
+
break
|
|
177
|
+
current = parent
|
|
178
|
+
|
|
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
|
+
|
|
165
186
|
@staticmethod
|
|
166
|
-
def
|
|
167
|
-
|
|
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
|
|
168
190
|
|
|
169
|
-
|
|
170
|
-
|
|
191
|
+
candidates = [path for path in root_dir.rglob(USECLI_CONFIG_TOML)]
|
|
192
|
+
if not candidates:
|
|
193
|
+
return None
|
|
171
194
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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]
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def _find_usecli_config_on_sys_path() -> Path | None:
|
|
219
|
+
for entry in sys.path:
|
|
220
|
+
if not entry:
|
|
221
|
+
continue
|
|
222
|
+
path = Path(entry)
|
|
223
|
+
if not path.exists() or not path.is_dir():
|
|
224
|
+
continue
|
|
225
|
+
candidate = path / USECLI_CONFIG_TOML
|
|
226
|
+
if candidate.exists():
|
|
227
|
+
return candidate
|
|
228
|
+
for child in path.glob(f"*/{USECLI_CONFIG_TOML}"):
|
|
229
|
+
if child.exists():
|
|
230
|
+
return child
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def _load_usecli_toml(path: Path) -> dict[str, Any]:
|
|
175
235
|
with open(path, "rb") as f:
|
|
176
236
|
data = tomllib.load(f)
|
|
177
|
-
|
|
237
|
+
|
|
238
|
+
tool_section = data.get("tool", {})
|
|
239
|
+
if isinstance(tool_section, dict) and "usecli" in tool_section:
|
|
240
|
+
usecli_section = tool_section.get("usecli")
|
|
241
|
+
if isinstance(usecli_section, dict):
|
|
242
|
+
return usecli_section
|
|
243
|
+
|
|
244
|
+
usecli_section = data.get("usecli", {})
|
|
245
|
+
if isinstance(usecli_section, dict):
|
|
246
|
+
return usecli_section
|
|
247
|
+
|
|
248
|
+
return {}
|
|
178
249
|
|
|
179
250
|
def get(self, key: str, default: Any = None) -> Any:
|
|
180
251
|
"""Get a configuration value using dot notation.
|
|
@@ -255,10 +326,7 @@ class ConfigManager:
|
|
|
255
326
|
|
|
256
327
|
@property
|
|
257
328
|
def pyproject_exists(self) -> bool:
|
|
258
|
-
|
|
259
|
-
if not self.pyproject_path.exists():
|
|
260
|
-
return False
|
|
261
|
-
return self._pyproject_has_usecli(self.pyproject_path)
|
|
329
|
+
return self.usecli_config_path.exists()
|
|
262
330
|
|
|
263
331
|
@staticmethod
|
|
264
332
|
def _load_project_version(path: Path) -> str | None:
|
|
@@ -304,18 +372,29 @@ def find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
|
304
372
|
|
|
305
373
|
current = start_dir.resolve()
|
|
306
374
|
|
|
375
|
+
git_root: Path | None = None
|
|
307
376
|
while True:
|
|
308
|
-
pyproject_path = current /
|
|
377
|
+
pyproject_path = current / PYPROJECT_TOML
|
|
309
378
|
if pyproject_path.exists():
|
|
310
379
|
return current
|
|
311
380
|
|
|
381
|
+
usecli_path = current / USECLI_CONFIG_TOML
|
|
382
|
+
if usecli_path.exists():
|
|
383
|
+
return current
|
|
384
|
+
|
|
312
385
|
git_dir = current / ".git"
|
|
313
386
|
if git_dir.exists():
|
|
314
|
-
|
|
387
|
+
git_root = current
|
|
388
|
+
break
|
|
315
389
|
|
|
316
390
|
parent = current.parent
|
|
317
391
|
if parent == current:
|
|
318
392
|
break
|
|
319
393
|
current = parent
|
|
320
394
|
|
|
321
|
-
|
|
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.32 → 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
|