usecli 0.1.45__tar.gz → 0.1.47__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.47}/PKG-INFO +1 -1
- {usecli-0.1.45 → usecli-0.1.47}/pyproject.toml +1 -1
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/about_command.py +136 -17
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/init_command.py +52 -9
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/config/colors.py +105 -1
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/usecli.config.toml +1 -1
- {usecli-0.1.45 → usecli-0.1.47}/LICENSE +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/README.md +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/base_command.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/exceptions/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/skill_generator.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/ui/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/ui/list.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/ui/title.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/ui/title.txt +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/services/command_service.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/menu.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/params.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/shared/config/globals.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/shared/config/manager.py +0 -0
- {usecli-0.1.45 → usecli-0.1.47}/src/usecli/ui.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "usecli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.47"
|
|
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" }]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
import os
|
|
3
5
|
import platform
|
|
4
6
|
import re
|
|
5
7
|
import sys
|
|
@@ -55,7 +57,51 @@ def _parse_dependency_requirement(req: str) -> tuple[str, str | None]:
|
|
|
55
57
|
return name, remainder
|
|
56
58
|
|
|
57
59
|
|
|
60
|
+
def _get_console_script_distribution(
|
|
61
|
+
command_name: str | None,
|
|
62
|
+
) -> importlib.metadata.Distribution | None:
|
|
63
|
+
if not command_name:
|
|
64
|
+
return None
|
|
65
|
+
try:
|
|
66
|
+
distributions = importlib.metadata.distributions()
|
|
67
|
+
except Exception:
|
|
68
|
+
return None
|
|
69
|
+
for dist in distributions:
|
|
70
|
+
try:
|
|
71
|
+
entry_points = dist.entry_points
|
|
72
|
+
except Exception:
|
|
73
|
+
continue
|
|
74
|
+
for entry_point in entry_points:
|
|
75
|
+
if entry_point.group != "console_scripts":
|
|
76
|
+
continue
|
|
77
|
+
if entry_point.name == command_name:
|
|
78
|
+
return dist
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _get_package_dependencies_from_distribution(
|
|
83
|
+
dist: importlib.metadata.Distribution,
|
|
84
|
+
) -> list[tuple[str, str | None]]:
|
|
85
|
+
requires = dist.requires or []
|
|
86
|
+
result: list[tuple[str, str | None]] = []
|
|
87
|
+
for req in requires:
|
|
88
|
+
if not isinstance(req, str):
|
|
89
|
+
continue
|
|
90
|
+
name, spec = _parse_dependency_requirement(req)
|
|
91
|
+
if name and not req.startswith("("):
|
|
92
|
+
result.append((name, spec))
|
|
93
|
+
return result
|
|
94
|
+
|
|
95
|
+
|
|
58
96
|
def _get_dependencies(config: ConfigManager) -> list[tuple[str, str | None]]:
|
|
97
|
+
command_name = os.path.basename(sys.argv[0]) if sys.argv else None
|
|
98
|
+
dist = _get_console_script_distribution(command_name)
|
|
99
|
+
if dist is None:
|
|
100
|
+
primary_command = get_script_command_name(default=None)
|
|
101
|
+
dist = _get_console_script_distribution(primary_command)
|
|
102
|
+
if dist is not None:
|
|
103
|
+
return _get_package_dependencies_from_distribution(dist)
|
|
104
|
+
|
|
59
105
|
pyproject_path = config.pyproject_path
|
|
60
106
|
if not pyproject_path.exists():
|
|
61
107
|
return []
|
|
@@ -79,8 +125,91 @@ def _get_dependencies(config: ConfigManager) -> list[tuple[str, str | None]]:
|
|
|
79
125
|
return result
|
|
80
126
|
|
|
81
127
|
|
|
128
|
+
def _get_application_distribution() -> importlib.metadata.Distribution | None:
|
|
129
|
+
command_name = os.path.basename(sys.argv[0]) if sys.argv else None
|
|
130
|
+
dist = _get_console_script_distribution(command_name)
|
|
131
|
+
if dist is None:
|
|
132
|
+
primary_command = get_script_command_name(default=None)
|
|
133
|
+
dist = _get_console_script_distribution(primary_command)
|
|
134
|
+
return dist
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _get_application_version(config: ConfigManager) -> str:
|
|
138
|
+
dist = _get_application_distribution()
|
|
139
|
+
if dist is not None:
|
|
140
|
+
return dist.version
|
|
141
|
+
|
|
142
|
+
config_version = config.get_project_version()
|
|
143
|
+
if config_version:
|
|
144
|
+
return config_version
|
|
145
|
+
|
|
146
|
+
return _get_version()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _get_application_description(config: ConfigManager) -> str:
|
|
150
|
+
description = config.get("description")
|
|
151
|
+
if (
|
|
152
|
+
config.has_key("description")
|
|
153
|
+
and isinstance(description, str)
|
|
154
|
+
and description.strip()
|
|
155
|
+
):
|
|
156
|
+
return description.strip()
|
|
157
|
+
|
|
158
|
+
project_description = _get_project_description(config)
|
|
159
|
+
if project_description:
|
|
160
|
+
return project_description
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
"An elegant CLI framework for Python with prefix matching, "
|
|
164
|
+
"rich UI, and command scaffolding."
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _get_project_description(config: ConfigManager) -> str | None:
|
|
169
|
+
pyproject_path = config.pyproject_path
|
|
170
|
+
if not pyproject_path.exists():
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
data = tomllib.loads(pyproject_path.read_text())
|
|
175
|
+
except (tomllib.TOMLDecodeError, OSError):
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
description = data.get("project", {}).get("description")
|
|
179
|
+
if isinstance(description, str) and description.strip():
|
|
180
|
+
return description.strip()
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _get_installed_script_commands(command_name: str | None) -> list[str]:
|
|
185
|
+
dist = _get_console_script_distribution(command_name)
|
|
186
|
+
if dist is None:
|
|
187
|
+
return []
|
|
188
|
+
try:
|
|
189
|
+
entry_points = dist.entry_points
|
|
190
|
+
except Exception:
|
|
191
|
+
return []
|
|
192
|
+
script_names = [
|
|
193
|
+
entry_point.name
|
|
194
|
+
for entry_point in entry_points
|
|
195
|
+
if entry_point.group == "console_scripts"
|
|
196
|
+
]
|
|
197
|
+
if not script_names:
|
|
198
|
+
return []
|
|
199
|
+
if command_name and command_name in script_names:
|
|
200
|
+
return [command_name, *[name for name in script_names if name != command_name]]
|
|
201
|
+
return script_names
|
|
202
|
+
|
|
203
|
+
|
|
82
204
|
def _get_script_commands() -> list[str]:
|
|
83
205
|
primary_command = get_script_command_name(default=None)
|
|
206
|
+
command_name = os.path.basename(sys.argv[0]) if sys.argv else primary_command
|
|
207
|
+
installed_commands = _get_installed_script_commands(command_name)
|
|
208
|
+
if installed_commands:
|
|
209
|
+
if primary_command and primary_command not in installed_commands:
|
|
210
|
+
return [primary_command, *installed_commands]
|
|
211
|
+
return installed_commands
|
|
212
|
+
|
|
84
213
|
pyproject_path = Path.cwd() / "pyproject.toml"
|
|
85
214
|
if not pyproject_path.exists():
|
|
86
215
|
if primary_command:
|
|
@@ -115,19 +244,9 @@ class AboutCommand(BaseCommand):
|
|
|
115
244
|
|
|
116
245
|
def handle(self) -> None:
|
|
117
246
|
config = get_config()
|
|
118
|
-
version = config
|
|
247
|
+
version = _get_application_version(config)
|
|
119
248
|
app_name = get_project_name()
|
|
120
|
-
description = config
|
|
121
|
-
if not (
|
|
122
|
-
config.has_key("description")
|
|
123
|
-
and isinstance(description, str)
|
|
124
|
-
and description.strip()
|
|
125
|
-
):
|
|
126
|
-
description = (
|
|
127
|
-
"An elegant CLI framework for Python with prefix matching, "
|
|
128
|
-
"rich UI, and command scaffolding."
|
|
129
|
-
)
|
|
130
|
-
description = description.strip() if isinstance(description, str) else ""
|
|
249
|
+
description = _get_application_description(config)
|
|
131
250
|
|
|
132
251
|
console.print()
|
|
133
252
|
console.print(f"[bold {COLOR.PRIMARY}]Description[/bold {COLOR.PRIMARY}]")
|
|
@@ -138,8 +257,11 @@ class AboutCommand(BaseCommand):
|
|
|
138
257
|
console.print(f"[bold {COLOR.PRIMARY}]Environment[/bold {COLOR.PRIMARY}]")
|
|
139
258
|
console.print(f"[{COLOR.PRIMARY}]─" * 78)
|
|
140
259
|
|
|
141
|
-
|
|
142
|
-
|
|
260
|
+
dist = _get_application_distribution()
|
|
261
|
+
name_label = "Cli Name" if dist is not None else "Application Name"
|
|
262
|
+
version_label = "Cli Version" if dist is not None else "Application Version"
|
|
263
|
+
self._print_row(name_label, app_name)
|
|
264
|
+
self._print_row(version_label, version)
|
|
143
265
|
self._print_row("Python Version", platform.python_version())
|
|
144
266
|
self._print_row("Platform", f"[{COLOR.FOREGROUND_MUTED}]{platform.platform()}")
|
|
145
267
|
|
|
@@ -159,9 +281,6 @@ class AboutCommand(BaseCommand):
|
|
|
159
281
|
deps = _get_dependencies(config)
|
|
160
282
|
if deps:
|
|
161
283
|
for dep_name, spec in deps:
|
|
162
|
-
if dep_name == "usecli" and spec:
|
|
163
|
-
self._print_row(dep_name, spec)
|
|
164
|
-
continue
|
|
165
284
|
try:
|
|
166
285
|
installed_version = get_version(dep_name)
|
|
167
286
|
self._print_row(dep_name, installed_version)
|
|
@@ -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,
|
|
@@ -3,7 +3,7 @@ command_name = "usecli"
|
|
|
3
3
|
title = "usecli"
|
|
4
4
|
title_file = "cli/core/ui/title.txt"
|
|
5
5
|
title_font = "ansi_shadow"
|
|
6
|
-
description = "A
|
|
6
|
+
description = "A powerful Python CLI framework for building beautiful, developer-friendly command-line tools."
|
|
7
7
|
commands_dir = "cli/commands/custom"
|
|
8
8
|
templates_dir = "cli/templates"
|
|
9
9
|
themes_dir = "cli/themes"
|
|
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.47}/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
|