usecli 0.1.45__tar.gz → 0.1.46__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.45 → usecli-0.1.46}/PKG-INFO +1 -1
- {usecli-0.1.45 → usecli-0.1.46}/pyproject.toml +1 -1
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/init_command.py +52 -9
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/config/colors.py +105 -1
- {usecli-0.1.45 → usecli-0.1.46}/LICENSE +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/README.md +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/base_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/exceptions/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/skill_generator.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/ui/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/ui/list.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/ui/title.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/ui/title.txt +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/services/command_service.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/menu.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/params.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/shared/config/globals.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/shared/config/manager.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/ui.py +0 -0
- {usecli-0.1.45 → usecli-0.1.46}/src/usecli/usecli.config.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "usecli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.46"
|
|
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" }]
|
|
@@ -108,7 +108,17 @@ class InitCommand(BaseCommand):
|
|
|
108
108
|
return "updated" if existed else "created"
|
|
109
109
|
|
|
110
110
|
def _should_skip_config_path(self, path: Path) -> bool:
|
|
111
|
-
|
|
111
|
+
try:
|
|
112
|
+
resolved = path.resolve()
|
|
113
|
+
except OSError:
|
|
114
|
+
resolved = path
|
|
115
|
+
if any(part in ConfigManager._SKIP_DIRS for part in resolved.parts):
|
|
116
|
+
return True
|
|
117
|
+
try:
|
|
118
|
+
resolved.relative_to(Path(sys.prefix).resolve())
|
|
119
|
+
except ValueError:
|
|
120
|
+
return False
|
|
121
|
+
return True
|
|
112
122
|
|
|
113
123
|
def _resolve_config_path(self, value: str, project_root: Path) -> Path:
|
|
114
124
|
path = Path(value).expanduser()
|
|
@@ -186,6 +196,36 @@ class InitCommand(BaseCommand):
|
|
|
186
196
|
|
|
187
197
|
return created
|
|
188
198
|
|
|
199
|
+
def _find_project_root_for_init(self, start_dir: Path) -> Path:
|
|
200
|
+
current = start_dir.resolve()
|
|
201
|
+
git_root: Path | None = None
|
|
202
|
+
while True:
|
|
203
|
+
if (current / "pyproject.toml").exists():
|
|
204
|
+
return current
|
|
205
|
+
if (current / USECLI_CONFIG_TOML).exists():
|
|
206
|
+
return current
|
|
207
|
+
git_dir = current / ".git"
|
|
208
|
+
if git_dir.exists():
|
|
209
|
+
git_root = current
|
|
210
|
+
break
|
|
211
|
+
parent = current.parent
|
|
212
|
+
if parent == current:
|
|
213
|
+
break
|
|
214
|
+
current = parent
|
|
215
|
+
return (git_root or start_dir).resolve()
|
|
216
|
+
|
|
217
|
+
def _find_pyproject_path_for_init(self, start_dir: Path) -> Path | None:
|
|
218
|
+
current = start_dir.resolve()
|
|
219
|
+
while True:
|
|
220
|
+
pyproject_path = current / "pyproject.toml"
|
|
221
|
+
if pyproject_path.exists():
|
|
222
|
+
return pyproject_path
|
|
223
|
+
parent = current.parent
|
|
224
|
+
if parent == current:
|
|
225
|
+
break
|
|
226
|
+
current = parent
|
|
227
|
+
return None
|
|
228
|
+
|
|
189
229
|
def _derive_templates_dir(self, commands_dir: str) -> str:
|
|
190
230
|
commands_path = Path(commands_dir)
|
|
191
231
|
parent = commands_path.parent
|
|
@@ -509,12 +549,9 @@ include = ["{root_package}*"]
|
|
|
509
549
|
),
|
|
510
550
|
) -> None:
|
|
511
551
|
cwd = Path.cwd()
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
config_manager.pyproject_path
|
|
516
|
-
if config_manager.pyproject_path.exists()
|
|
517
|
-
else project_root / "pyproject.toml"
|
|
552
|
+
project_root = self._find_project_root_for_init(cwd)
|
|
553
|
+
pyproject_path = self._find_pyproject_path_for_init(cwd) or (
|
|
554
|
+
project_root / "pyproject.toml"
|
|
518
555
|
)
|
|
519
556
|
|
|
520
557
|
console.print()
|
|
@@ -712,14 +749,20 @@ include = ["{root_package}*"]
|
|
|
712
749
|
config_root = project_root
|
|
713
750
|
if commands_path.parent != project_root:
|
|
714
751
|
config_root = commands_path.parent
|
|
715
|
-
existing_config = ConfigManager(
|
|
716
|
-
|
|
752
|
+
existing_config = ConfigManager._find_usecli_config_in_tree(
|
|
753
|
+
project_root,
|
|
754
|
+
config_root,
|
|
755
|
+
skip_venv=True,
|
|
756
|
+
)
|
|
757
|
+
if existing_config is None or self._should_skip_config_path(existing_config):
|
|
717
758
|
existing_config = config_root / USECLI_CONFIG_TOML
|
|
718
759
|
default_config_path = (
|
|
719
760
|
existing_config
|
|
720
761
|
if existing_config.exists()
|
|
721
762
|
else config_root / USECLI_CONFIG_TOML
|
|
722
763
|
)
|
|
764
|
+
if self._should_skip_config_path(default_config_path):
|
|
765
|
+
default_config_path = config_root / USECLI_CONFIG_TOML
|
|
723
766
|
config_location = Prompt.ask(
|
|
724
767
|
f"[bold {COLOR.SECONDARY}]Config file location[/bold {COLOR.SECONDARY}]"
|
|
725
768
|
" (path or directory)",
|
|
@@ -100,6 +100,110 @@ def _find_usecli_config_path(
|
|
|
100
100
|
return selection[0]
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
def _get_command_name() -> str | None:
|
|
104
|
+
"""Get the current command name from sys.argv."""
|
|
105
|
+
if not sys.argv:
|
|
106
|
+
return None
|
|
107
|
+
command = os.path.basename(sys.argv[0])
|
|
108
|
+
return command if command else None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _get_console_script_aliases(command_name: str | None) -> set[str]:
|
|
112
|
+
"""Get all aliases for a console script from package metadata."""
|
|
113
|
+
if not command_name:
|
|
114
|
+
return set()
|
|
115
|
+
aliases: set[str] = {command_name}
|
|
116
|
+
try:
|
|
117
|
+
distributions = importlib.metadata.distributions()
|
|
118
|
+
except Exception:
|
|
119
|
+
return aliases
|
|
120
|
+
for dist in distributions:
|
|
121
|
+
try:
|
|
122
|
+
entry_points = dist.entry_points
|
|
123
|
+
except Exception:
|
|
124
|
+
continue
|
|
125
|
+
names = [
|
|
126
|
+
entry_point.name
|
|
127
|
+
for entry_point in entry_points
|
|
128
|
+
if entry_point.group == "console_scripts"
|
|
129
|
+
]
|
|
130
|
+
if command_name in names:
|
|
131
|
+
aliases.update(names)
|
|
132
|
+
break
|
|
133
|
+
return aliases
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _config_matches_command(path: Path, command_name: str | None) -> bool:
|
|
137
|
+
"""Check if a config file matches the given command name."""
|
|
138
|
+
if command_name is None:
|
|
139
|
+
return True
|
|
140
|
+
try:
|
|
141
|
+
data = _load_usecli_config_file(path)
|
|
142
|
+
except (tomllib.TOMLDecodeError, OSError):
|
|
143
|
+
return True
|
|
144
|
+
config_command = data.get("command_name")
|
|
145
|
+
if not isinstance(config_command, str):
|
|
146
|
+
return True
|
|
147
|
+
normalized = config_command.strip()
|
|
148
|
+
if not normalized:
|
|
149
|
+
return True
|
|
150
|
+
if normalized == command_name:
|
|
151
|
+
return True
|
|
152
|
+
aliases = _get_console_script_aliases(command_name)
|
|
153
|
+
return normalized in aliases
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _find_usecli_config_path_for_command(
|
|
157
|
+
root_dir: Path, start_dir: Path, *, skip_venv: bool
|
|
158
|
+
) -> Path | None:
|
|
159
|
+
"""Find usecli config that matches the current command."""
|
|
160
|
+
if not root_dir.exists() or not root_dir.is_dir():
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
candidates = [path for path in root_dir.rglob(USECLI_CONFIG_TOML)]
|
|
164
|
+
if skip_venv:
|
|
165
|
+
candidates = [
|
|
166
|
+
path
|
|
167
|
+
for path in candidates
|
|
168
|
+
if not any(part in _SKIP_DIRS for part in path.parts)
|
|
169
|
+
]
|
|
170
|
+
if not candidates:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
command_name = _get_command_name()
|
|
174
|
+
|
|
175
|
+
# Filter candidates by command_name matching
|
|
176
|
+
if command_name:
|
|
177
|
+
candidates = [
|
|
178
|
+
path for path in candidates if _config_matches_command(path, command_name)
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
if not candidates:
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
start_dir = start_dir.resolve()
|
|
185
|
+
preferred: list[Path] = []
|
|
186
|
+
for path in candidates:
|
|
187
|
+
try:
|
|
188
|
+
path.relative_to(start_dir)
|
|
189
|
+
preferred.append(path)
|
|
190
|
+
except ValueError:
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
selection = preferred or candidates
|
|
194
|
+
|
|
195
|
+
def _depth_key(path: Path) -> tuple[int, str]:
|
|
196
|
+
try:
|
|
197
|
+
relative = path.relative_to(start_dir)
|
|
198
|
+
return (len(relative.parts), str(path))
|
|
199
|
+
except ValueError:
|
|
200
|
+
relative = path.relative_to(root_dir)
|
|
201
|
+
return (len(relative.parts), str(path))
|
|
202
|
+
|
|
203
|
+
selection.sort(key=_depth_key)
|
|
204
|
+
return selection[0]
|
|
205
|
+
|
|
206
|
+
|
|
103
207
|
def _find_usecli_config_in_package() -> Path | None:
|
|
104
208
|
spec = importlib.util.find_spec(_get_package_name())
|
|
105
209
|
if spec is None or not spec.submodule_search_locations:
|
|
@@ -242,7 +346,7 @@ def _load_usecli_config(
|
|
|
242
346
|
|
|
243
347
|
config_path = project_root / USECLI_CONFIG_TOML
|
|
244
348
|
if not config_path.exists():
|
|
245
|
-
config_path =
|
|
349
|
+
config_path = _find_usecli_config_path_for_command(
|
|
246
350
|
project_root,
|
|
247
351
|
project_root,
|
|
248
352
|
skip_venv=True,
|
|
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
|
{usecli-0.1.45 → usecli-0.1.46}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|