usecli 0.1.33__tar.gz → 0.1.35__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.35}/PKG-INFO +4 -2
- {usecli-0.1.33 → usecli-0.1.35}/README.md +3 -1
- {usecli-0.1.33 → usecli-0.1.35}/pyproject.toml +4 -9
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/init_command.py +57 -75
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/config/colors.py +59 -13
- usecli-0.1.33/src/usecli/cli/templates/usecli.toml.j2 → usecli-0.1.35/src/usecli/cli/templates/usecli.config.toml.j2 +1 -1
- usecli-0.1.35/src/usecli/cli/usecli.config.toml +13 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/shared/config/globals.py +1 -1
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/shared/config/manager.py +73 -70
- {usecli-0.1.33 → usecli-0.1.35}/LICENSE +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/base_command.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/exceptions/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/skill_generator.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/ui/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/ui/list.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/ui/title.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/services/command_service.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/menu.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/params.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.33 → usecli-0.1.35}/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.35
|
|
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.35"
|
|
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,21 @@ 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
|
+
config_path: Path,
|
|
92
|
+
config_content: str,
|
|
93
|
+
force: bool,
|
|
118
94
|
) -> str:
|
|
119
|
-
config_path = project_root / USECLI_TOML
|
|
120
95
|
existed = config_path.exists()
|
|
121
96
|
if existed and not force:
|
|
122
97
|
should_overwrite = Confirm.ask(
|
|
123
|
-
f"[{COLOR.WARNING}]usecli.toml already exists at {config_path}.[/{COLOR.WARNING}]\n"
|
|
98
|
+
f"[{COLOR.WARNING}]usecli.config.toml already exists at {config_path}.[/{COLOR.WARNING}]\n"
|
|
124
99
|
"Overwrite it with the new settings from this init run?",
|
|
125
100
|
default=False,
|
|
126
101
|
)
|
|
@@ -130,6 +105,14 @@ class InitCommand(BaseCommand):
|
|
|
130
105
|
config_path.write_text(config_content.rstrip() + "\n")
|
|
131
106
|
return "updated" if existed else "created"
|
|
132
107
|
|
|
108
|
+
def _resolve_config_path(self, value: str, project_root: Path) -> Path:
|
|
109
|
+
path = Path(value).expanduser()
|
|
110
|
+
if not path.is_absolute():
|
|
111
|
+
path = project_root / path
|
|
112
|
+
if path.exists() and path.is_dir():
|
|
113
|
+
return (path / USECLI_CONFIG_TOML).resolve()
|
|
114
|
+
return path.resolve()
|
|
115
|
+
|
|
133
116
|
def _ensure_project_scripts(
|
|
134
117
|
self, pyproject_path: Path, command_name: str, force: bool
|
|
135
118
|
) -> str:
|
|
@@ -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,48 @@ include = ["{root_package}*"]
|
|
|
762
716
|
|
|
763
717
|
self._sync_environment(project_root, command_name)
|
|
764
718
|
|
|
765
|
-
|
|
766
|
-
|
|
719
|
+
config_root = project_root
|
|
720
|
+
if commands_path.parent != project_root:
|
|
721
|
+
config_root = commands_path.parent
|
|
722
|
+
existing_config = ConfigManager(start_dir=config_root).usecli_config_path
|
|
723
|
+
default_config_path = (
|
|
724
|
+
existing_config
|
|
725
|
+
if existing_config.exists()
|
|
726
|
+
else config_root / USECLI_CONFIG_TOML
|
|
727
|
+
)
|
|
728
|
+
config_location = Prompt.ask(
|
|
729
|
+
f"[bold {COLOR.SECONDARY}]Config file location[/bold {COLOR.SECONDARY}]"
|
|
730
|
+
" (path or directory)",
|
|
731
|
+
default=str(default_config_path),
|
|
732
|
+
)
|
|
733
|
+
config_path = self._resolve_config_path(config_location, project_root)
|
|
734
|
+
if (
|
|
735
|
+
existing_config.exists()
|
|
736
|
+
and config_path.resolve() != existing_config.resolve()
|
|
737
|
+
and not force
|
|
738
|
+
):
|
|
739
|
+
replace_existing = Confirm.ask(
|
|
740
|
+
f"[{COLOR.WARNING}]Existing {USECLI_CONFIG_TOML} found at {existing_config}.[/{COLOR.WARNING}]\n"
|
|
741
|
+
"Replace it instead of writing to the new location?",
|
|
742
|
+
default=False,
|
|
743
|
+
)
|
|
744
|
+
if replace_existing:
|
|
745
|
+
config_path = existing_config
|
|
746
|
+
|
|
747
|
+
usecli_config_status = self._write_usecli_config(
|
|
748
|
+
config_path, config_content, force
|
|
767
749
|
)
|
|
768
|
-
if
|
|
750
|
+
if usecli_config_status == "created":
|
|
769
751
|
console.print(
|
|
770
|
-
f"[{COLOR.SUCCESS}]Created {
|
|
752
|
+
f"[{COLOR.SUCCESS}]Created {USECLI_CONFIG_TOML} for runtime config fallback[/{COLOR.SUCCESS}]"
|
|
771
753
|
)
|
|
772
|
-
elif
|
|
754
|
+
elif usecli_config_status == "updated":
|
|
773
755
|
console.print(
|
|
774
|
-
f"[{COLOR.SUCCESS}]Updated {
|
|
756
|
+
f"[{COLOR.SUCCESS}]Updated {USECLI_CONFIG_TOML} for runtime config fallback[/{COLOR.SUCCESS}]"
|
|
775
757
|
)
|
|
776
|
-
elif
|
|
758
|
+
elif usecli_config_status == "skipped":
|
|
777
759
|
console.print(
|
|
778
|
-
f"[{COLOR.WARNING}]Skipped updating {
|
|
760
|
+
f"[{COLOR.WARNING}]Skipped updating {USECLI_CONFIG_TOML}.[/{COLOR.WARNING}]"
|
|
779
761
|
)
|
|
780
762
|
|
|
781
763
|
# 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,101 @@ 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 = [
|
|
56
|
+
path
|
|
57
|
+
for path in root_dir.rglob(USECLI_CONFIG_TOML)
|
|
58
|
+
if not any(part in {".venv", "venv"} for part in path.parts)
|
|
59
|
+
]
|
|
60
|
+
if not candidates:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
start_dir = start_dir.resolve()
|
|
64
|
+
preferred: list[Path] = []
|
|
65
|
+
for path in candidates:
|
|
66
|
+
try:
|
|
67
|
+
path.relative_to(start_dir)
|
|
68
|
+
preferred.append(path)
|
|
69
|
+
except ValueError:
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
selection = preferred or candidates
|
|
73
|
+
|
|
74
|
+
def _depth_key(path: Path) -> tuple[int, str]:
|
|
75
|
+
try:
|
|
76
|
+
relative = path.relative_to(start_dir)
|
|
77
|
+
return (len(relative.parts), str(path))
|
|
78
|
+
except ValueError:
|
|
79
|
+
relative = path.relative_to(root_dir)
|
|
80
|
+
return (len(relative.parts), str(path))
|
|
81
|
+
|
|
82
|
+
selection.sort(key=_depth_key)
|
|
83
|
+
return selection[0]
|
|
84
|
+
|
|
85
|
+
|
|
51
86
|
def _find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
52
87
|
if start_dir is None:
|
|
53
88
|
start_dir = Path.cwd()
|
|
54
89
|
|
|
55
90
|
current = start_dir.resolve()
|
|
91
|
+
git_root: Path | None = None
|
|
56
92
|
|
|
57
93
|
while True:
|
|
58
94
|
pyproject_path = current / PYPROJECT_TOML
|
|
59
95
|
if pyproject_path.exists():
|
|
60
96
|
return current
|
|
61
97
|
|
|
62
|
-
usecli_path = current /
|
|
98
|
+
usecli_path = current / USECLI_CONFIG_TOML
|
|
63
99
|
if usecli_path.exists():
|
|
64
100
|
return current
|
|
65
101
|
|
|
66
102
|
git_dir = current / ".git"
|
|
67
103
|
if git_dir.exists():
|
|
68
|
-
|
|
104
|
+
git_root = current
|
|
105
|
+
break
|
|
69
106
|
|
|
70
107
|
parent = current.parent
|
|
71
108
|
if parent == current:
|
|
72
109
|
break
|
|
73
110
|
current = parent
|
|
74
111
|
|
|
75
|
-
|
|
112
|
+
search_root = git_root or start_dir.resolve()
|
|
113
|
+
config_match = _find_usecli_config_path(search_root, start_dir)
|
|
114
|
+
if config_match:
|
|
115
|
+
return config_match.parent
|
|
116
|
+
|
|
117
|
+
return git_root
|
|
76
118
|
|
|
77
119
|
|
|
78
120
|
def _load_usecli_config(project_root: Path | None) -> dict[str, Any]:
|
|
79
121
|
if project_root is None:
|
|
80
122
|
return {}
|
|
81
123
|
|
|
82
|
-
|
|
83
|
-
if not
|
|
124
|
+
config_path = project_root / USECLI_CONFIG_TOML
|
|
125
|
+
if not config_path.exists():
|
|
126
|
+
config_path = _find_usecli_config_path(project_root, project_root)
|
|
127
|
+
if not config_path or not config_path.exists():
|
|
84
128
|
return {}
|
|
85
129
|
|
|
86
130
|
try:
|
|
87
|
-
data = tomllib.loads(
|
|
131
|
+
data = tomllib.loads(config_path.read_text())
|
|
88
132
|
except (tomllib.TOMLDecodeError, OSError):
|
|
89
133
|
return {}
|
|
90
134
|
|
|
91
135
|
tool = data.get("tool", {})
|
|
92
|
-
if
|
|
93
|
-
|
|
136
|
+
if isinstance(tool, dict) and "usecli" in tool:
|
|
137
|
+
usecli_config = tool.get("usecli")
|
|
138
|
+
if isinstance(usecli_config, dict):
|
|
139
|
+
return usecli_config
|
|
94
140
|
|
|
95
|
-
|
|
96
|
-
if
|
|
97
|
-
return
|
|
141
|
+
usecli_section = data.get("usecli", {})
|
|
142
|
+
if isinstance(usecli_section, dict):
|
|
143
|
+
return usecli_section
|
|
98
144
|
|
|
99
|
-
return
|
|
145
|
+
return {}
|
|
100
146
|
|
|
101
147
|
|
|
102
148
|
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,9 @@ def _dedupe_items(items: list[str]) -> list[str]:
|
|
|
58
56
|
|
|
59
57
|
|
|
60
58
|
class ConfigManager:
|
|
61
|
-
"""Manages useCli configuration from project-level files.
|
|
59
|
+
"""Manages useCli configuration from project-level files."""
|
|
62
60
|
|
|
63
|
-
|
|
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
|
-
"""
|
|
61
|
+
_SKIP_DIRS = {".venv", "venv"}
|
|
71
62
|
|
|
72
63
|
DEFAULT_CONFIG: dict[str, Any] = {
|
|
73
64
|
"title": "usecli",
|
|
@@ -88,7 +79,7 @@ class ConfigManager:
|
|
|
88
79
|
def __init__(
|
|
89
80
|
self,
|
|
90
81
|
pyproject_path: Path | None = None,
|
|
91
|
-
|
|
82
|
+
usecli_config_path: Path | None = None,
|
|
92
83
|
start_dir: Path | None = None,
|
|
93
84
|
) -> None:
|
|
94
85
|
"""Initialize the configuration manager.
|
|
@@ -107,17 +98,17 @@ class ConfigManager:
|
|
|
107
98
|
start_dir / PYPROJECT_TOML
|
|
108
99
|
)
|
|
109
100
|
|
|
110
|
-
if
|
|
111
|
-
|
|
112
|
-
start_dir /
|
|
101
|
+
if usecli_config_path is None:
|
|
102
|
+
usecli_config_path = self._find_usecli_config(start_dir) or (
|
|
103
|
+
start_dir / USECLI_CONFIG_TOML
|
|
113
104
|
)
|
|
114
105
|
|
|
115
106
|
self.pyproject_path: Path = pyproject_path
|
|
116
|
-
self.
|
|
107
|
+
self.usecli_config_path: Path = usecli_config_path
|
|
117
108
|
self.start_dir: Path = start_dir
|
|
118
109
|
detected_root = find_project_root(start_dir)
|
|
119
|
-
if detected_root is None and self.
|
|
120
|
-
detected_root = self.
|
|
110
|
+
if detected_root is None and self.usecli_config_path.exists():
|
|
111
|
+
detected_root = self.usecli_config_path.parent
|
|
121
112
|
self.project_root: Path = (detected_root or start_dir).resolve()
|
|
122
113
|
self._config: dict[str, Any] = {}
|
|
123
114
|
self._overrides: dict[str, Any] = {}
|
|
@@ -128,30 +119,16 @@ class ConfigManager:
|
|
|
128
119
|
self._config = self.DEFAULT_CONFIG.copy()
|
|
129
120
|
self._overrides = {}
|
|
130
121
|
|
|
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():
|
|
122
|
+
if self.usecli_config_path.exists():
|
|
146
123
|
try:
|
|
147
|
-
usecli_config = self._load_usecli_toml(self.
|
|
124
|
+
usecli_config = self._load_usecli_toml(self.usecli_config_path)
|
|
148
125
|
if usecli_config:
|
|
149
126
|
self._config = _deep_merge(self._config, usecli_config)
|
|
150
127
|
self._overrides = _deep_merge(self._overrides, usecli_config)
|
|
151
128
|
except (tomllib.TOMLDecodeError, OSError) as e:
|
|
152
129
|
raise UsecliConfigError(
|
|
153
|
-
f"Failed to load
|
|
154
|
-
config_file=str(self.
|
|
130
|
+
f"Failed to load {USECLI_CONFIG_TOML}: {e}",
|
|
131
|
+
config_file=str(self.usecli_config_path),
|
|
155
132
|
) from e
|
|
156
133
|
|
|
157
134
|
default_themes = _normalize_themes_dir(self.DEFAULT_CONFIG.get("themes_dir"))
|
|
@@ -188,11 +165,11 @@ class ConfigManager:
|
|
|
188
165
|
return None
|
|
189
166
|
|
|
190
167
|
@classmethod
|
|
191
|
-
def
|
|
168
|
+
def _find_usecli_config(cls, start_dir: Path) -> Path | None:
|
|
192
169
|
current = start_dir.resolve()
|
|
193
170
|
|
|
194
171
|
while True:
|
|
195
|
-
config_path = current /
|
|
172
|
+
config_path = current / USECLI_CONFIG_TOML
|
|
196
173
|
if config_path.exists():
|
|
197
174
|
return config_path
|
|
198
175
|
|
|
@@ -201,46 +178,72 @@ class ConfigManager:
|
|
|
201
178
|
break
|
|
202
179
|
current = parent
|
|
203
180
|
|
|
204
|
-
|
|
181
|
+
search_root = find_project_root(start_dir) or start_dir.resolve()
|
|
182
|
+
recursive_match = cls._find_usecli_config_in_tree(search_root, start_dir)
|
|
183
|
+
if recursive_match:
|
|
184
|
+
return recursive_match
|
|
185
|
+
|
|
186
|
+
return cls._find_usecli_config_on_sys_path()
|
|
205
187
|
|
|
206
188
|
@staticmethod
|
|
207
|
-
def
|
|
189
|
+
def _find_usecli_config_in_tree(root_dir: Path, start_dir: Path) -> Path | None:
|
|
190
|
+
if not root_dir.exists() or not root_dir.is_dir():
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
candidates = [
|
|
194
|
+
path
|
|
195
|
+
for path in root_dir.rglob(USECLI_CONFIG_TOML)
|
|
196
|
+
if not any(part in ConfigManager._SKIP_DIRS for part in path.parts)
|
|
197
|
+
]
|
|
198
|
+
if not candidates:
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
start_dir = start_dir.resolve()
|
|
202
|
+
preferred: list[Path] = []
|
|
203
|
+
for path in candidates:
|
|
204
|
+
try:
|
|
205
|
+
path.relative_to(start_dir)
|
|
206
|
+
preferred.append(path)
|
|
207
|
+
except ValueError:
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
selection = preferred or candidates
|
|
211
|
+
|
|
212
|
+
def _depth_key(path: Path) -> tuple[int, str]:
|
|
213
|
+
try:
|
|
214
|
+
relative = path.relative_to(start_dir)
|
|
215
|
+
return (len(relative.parts), str(path))
|
|
216
|
+
except ValueError:
|
|
217
|
+
relative = path.relative_to(root_dir)
|
|
218
|
+
return (len(relative.parts), str(path))
|
|
219
|
+
|
|
220
|
+
selection.sort(key=_depth_key)
|
|
221
|
+
return selection[0]
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def _find_usecli_config_on_sys_path() -> Path | None:
|
|
208
225
|
for entry in sys.path:
|
|
209
226
|
if not entry:
|
|
210
227
|
continue
|
|
211
228
|
path = Path(entry)
|
|
212
229
|
if not path.exists() or not path.is_dir():
|
|
213
230
|
continue
|
|
214
|
-
candidate = path /
|
|
231
|
+
candidate = path / USECLI_CONFIG_TOML
|
|
215
232
|
if candidate.exists():
|
|
216
233
|
return candidate
|
|
217
|
-
for child in path.glob(f"*/{
|
|
234
|
+
for child in path.glob(f"*/{USECLI_CONFIG_TOML}"):
|
|
218
235
|
if child.exists():
|
|
219
236
|
return child
|
|
220
237
|
return None
|
|
221
238
|
|
|
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
239
|
@staticmethod
|
|
237
240
|
def _load_usecli_toml(path: Path) -> dict[str, Any]:
|
|
238
241
|
with open(path, "rb") as f:
|
|
239
242
|
data = tomllib.load(f)
|
|
240
243
|
|
|
241
244
|
tool_section = data.get("tool", {})
|
|
242
|
-
if isinstance(tool_section, dict):
|
|
243
|
-
usecli_section = tool_section.get("usecli"
|
|
245
|
+
if isinstance(tool_section, dict) and "usecli" in tool_section:
|
|
246
|
+
usecli_section = tool_section.get("usecli")
|
|
244
247
|
if isinstance(usecli_section, dict):
|
|
245
248
|
return usecli_section
|
|
246
249
|
|
|
@@ -329,14 +332,7 @@ class ConfigManager:
|
|
|
329
332
|
|
|
330
333
|
@property
|
|
331
334
|
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
|
|
335
|
+
return self.usecli_config_path.exists()
|
|
340
336
|
|
|
341
337
|
@staticmethod
|
|
342
338
|
def _load_project_version(path: Path) -> str | None:
|
|
@@ -382,22 +378,29 @@ def find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
|
382
378
|
|
|
383
379
|
current = start_dir.resolve()
|
|
384
380
|
|
|
381
|
+
git_root: Path | None = None
|
|
385
382
|
while True:
|
|
386
383
|
pyproject_path = current / PYPROJECT_TOML
|
|
387
384
|
if pyproject_path.exists():
|
|
388
385
|
return current
|
|
389
386
|
|
|
390
|
-
usecli_path = current /
|
|
387
|
+
usecli_path = current / USECLI_CONFIG_TOML
|
|
391
388
|
if usecli_path.exists():
|
|
392
389
|
return current
|
|
393
390
|
|
|
394
391
|
git_dir = current / ".git"
|
|
395
392
|
if git_dir.exists():
|
|
396
|
-
|
|
393
|
+
git_root = current
|
|
394
|
+
break
|
|
397
395
|
|
|
398
396
|
parent = current.parent
|
|
399
397
|
if parent == current:
|
|
400
398
|
break
|
|
401
399
|
current = parent
|
|
402
400
|
|
|
403
|
-
|
|
401
|
+
search_root = git_root or start_dir.resolve()
|
|
402
|
+
config_match = ConfigManager._find_usecli_config_in_tree(search_root, start_dir)
|
|
403
|
+
if config_match:
|
|
404
|
+
return config_match.parent
|
|
405
|
+
|
|
406
|
+
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.35}/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
|