usecli 0.1.60__tar.gz → 0.1.61__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.60 → usecli-0.1.61}/PKG-INFO +1 -1
- {usecli-0.1.60 → usecli-0.1.61}/pyproject.toml +1 -1
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/config/colors.py +106 -41
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/shared/config/manager.py +128 -50
- {usecli-0.1.60 → usecli-0.1.61}/LICENSE +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/README.md +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/commands/init_command.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/base_command.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/exceptions/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/ui/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/ui/list.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/ui/title.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/ui/title.txt +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/services/command_service.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/menu.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/params.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/shared/config/globals.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/src/usecli/ui.py +0 -0
- {usecli-0.1.60 → usecli-0.1.61}/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.61"
|
|
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" }]
|
|
@@ -39,6 +39,78 @@ _SKIP_DIRS = {
|
|
|
39
39
|
"pipx",
|
|
40
40
|
"venvs",
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
_MAX_RGLOB_DEPTH = 6
|
|
44
|
+
|
|
45
|
+
_WALK_SKIP_ALWAYS: frozenset[str] = frozenset(
|
|
46
|
+
{
|
|
47
|
+
".git",
|
|
48
|
+
"__pycache__",
|
|
49
|
+
"node_modules",
|
|
50
|
+
".tox",
|
|
51
|
+
".nox",
|
|
52
|
+
".mypy_cache",
|
|
53
|
+
".pytest_cache",
|
|
54
|
+
".ruff_cache",
|
|
55
|
+
".auto",
|
|
56
|
+
".eggs",
|
|
57
|
+
"*.egg-info",
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
_WALK_SKIP_VENV: frozenset[str] = frozenset(
|
|
62
|
+
{
|
|
63
|
+
".venv",
|
|
64
|
+
"venv",
|
|
65
|
+
"site-packages",
|
|
66
|
+
"dist-packages",
|
|
67
|
+
"__pypackages__",
|
|
68
|
+
"pipx",
|
|
69
|
+
"venvs",
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _walk_for_filename(
|
|
75
|
+
directory: Path,
|
|
76
|
+
filename: str,
|
|
77
|
+
depth: int,
|
|
78
|
+
max_depth: int,
|
|
79
|
+
skip_dirs: frozenset[str],
|
|
80
|
+
results: list[Path],
|
|
81
|
+
) -> None:
|
|
82
|
+
if depth > max_depth:
|
|
83
|
+
return
|
|
84
|
+
try:
|
|
85
|
+
entries = list(directory.iterdir())
|
|
86
|
+
except (PermissionError, OSError):
|
|
87
|
+
return
|
|
88
|
+
for entry in entries:
|
|
89
|
+
try:
|
|
90
|
+
if entry.is_file() and entry.name == filename:
|
|
91
|
+
results.append(entry)
|
|
92
|
+
elif entry.is_dir() and entry.name not in skip_dirs:
|
|
93
|
+
_walk_for_filename(
|
|
94
|
+
entry, filename, depth + 1, max_depth, skip_dirs, results
|
|
95
|
+
)
|
|
96
|
+
except (PermissionError, OSError):
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _rglob_limited(
|
|
101
|
+
root_dir: Path,
|
|
102
|
+
filename: str,
|
|
103
|
+
*,
|
|
104
|
+
skip_venv: bool = True,
|
|
105
|
+
max_depth: int = _MAX_RGLOB_DEPTH,
|
|
106
|
+
) -> list[Path]:
|
|
107
|
+
"""Depth-bounded recursive filename search that prunes dirs during the walk."""
|
|
108
|
+
skip_dirs = _WALK_SKIP_ALWAYS | _WALK_SKIP_VENV if skip_venv else _WALK_SKIP_ALWAYS
|
|
109
|
+
results: list[Path] = []
|
|
110
|
+
_walk_for_filename(root_dir, filename, 0, max_depth, skip_dirs, results)
|
|
111
|
+
return results
|
|
112
|
+
|
|
113
|
+
|
|
42
114
|
DEFAULT_THEME_COLORS: dict[str, str] = {
|
|
43
115
|
"primary": "#60D7FF",
|
|
44
116
|
"secondary": "#5EFF87",
|
|
@@ -68,13 +140,7 @@ def _find_usecli_config_path(
|
|
|
68
140
|
if not root_dir.exists() or not root_dir.is_dir():
|
|
69
141
|
return None
|
|
70
142
|
|
|
71
|
-
candidates =
|
|
72
|
-
if skip_venv:
|
|
73
|
-
candidates = [
|
|
74
|
-
path
|
|
75
|
-
for path in candidates
|
|
76
|
-
if not any(part in _SKIP_DIRS for part in path.parts)
|
|
77
|
-
]
|
|
143
|
+
candidates = _rglob_limited(root_dir, USECLI_CONFIG_TOML, skip_venv=skip_venv)
|
|
78
144
|
if not candidates:
|
|
79
145
|
return None
|
|
80
146
|
|
|
@@ -109,17 +175,28 @@ def _get_command_name() -> str | None:
|
|
|
109
175
|
return command if command else None
|
|
110
176
|
|
|
111
177
|
|
|
178
|
+
_distributions_cache: list[Any] | None = None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _get_distributions() -> list[Any]:
|
|
182
|
+
global _distributions_cache
|
|
183
|
+
if _distributions_cache is not None:
|
|
184
|
+
return _distributions_cache
|
|
185
|
+
try:
|
|
186
|
+
import importlib.metadata
|
|
187
|
+
|
|
188
|
+
_distributions_cache = list(importlib.metadata.distributions())
|
|
189
|
+
except Exception:
|
|
190
|
+
_distributions_cache = []
|
|
191
|
+
return _distributions_cache
|
|
192
|
+
|
|
193
|
+
|
|
112
194
|
def _get_console_script_aliases(command_name: str | None) -> set[str]:
|
|
113
195
|
"""Get all aliases for a console script from package metadata."""
|
|
114
196
|
if not command_name:
|
|
115
197
|
return set()
|
|
116
198
|
aliases: set[str] = {command_name}
|
|
117
|
-
|
|
118
|
-
import importlib.metadata
|
|
119
|
-
|
|
120
|
-
distributions = importlib.metadata.distributions()
|
|
121
|
-
except Exception:
|
|
122
|
-
return aliases
|
|
199
|
+
distributions = _get_distributions()
|
|
123
200
|
for dist in distributions:
|
|
124
201
|
try:
|
|
125
202
|
entry_points = dist.entry_points
|
|
@@ -163,13 +240,7 @@ def _find_usecli_config_path_for_command(
|
|
|
163
240
|
if not root_dir.exists() or not root_dir.is_dir():
|
|
164
241
|
return None
|
|
165
242
|
|
|
166
|
-
candidates =
|
|
167
|
-
if skip_venv:
|
|
168
|
-
candidates = [
|
|
169
|
-
path
|
|
170
|
-
for path in candidates
|
|
171
|
-
if not any(part in _SKIP_DIRS for part in path.parts)
|
|
172
|
-
]
|
|
243
|
+
candidates = _rglob_limited(root_dir, USECLI_CONFIG_TOML, skip_venv=skip_venv)
|
|
173
244
|
if not candidates:
|
|
174
245
|
return None
|
|
175
246
|
|
|
@@ -217,9 +288,7 @@ def _find_usecli_config_in_package() -> Path | None:
|
|
|
217
288
|
package_root = Path(location)
|
|
218
289
|
if not package_root.exists() or not package_root.is_dir():
|
|
219
290
|
continue
|
|
220
|
-
candidates =
|
|
221
|
-
path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
|
|
222
|
-
]
|
|
291
|
+
candidates = _rglob_limited(package_root, USECLI_CONFIG_TOML, skip_venv=False)
|
|
223
292
|
if candidates:
|
|
224
293
|
candidates.sort(key=lambda path: (len(path.parts), str(path)))
|
|
225
294
|
return candidates[0]
|
|
@@ -238,9 +307,7 @@ def _find_usecli_config_in_named_package(package_name: str) -> Path | None:
|
|
|
238
307
|
package_root = Path(location)
|
|
239
308
|
if not package_root.exists() or not package_root.is_dir():
|
|
240
309
|
continue
|
|
241
|
-
candidates =
|
|
242
|
-
path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
|
|
243
|
-
]
|
|
310
|
+
candidates = _rglob_limited(package_root, USECLI_CONFIG_TOML, skip_venv=False)
|
|
244
311
|
if candidates:
|
|
245
312
|
candidates.sort(key=lambda path: (len(path.parts), str(path)))
|
|
246
313
|
return candidates[0]
|
|
@@ -248,15 +315,10 @@ def _find_usecli_config_in_named_package(package_name: str) -> Path | None:
|
|
|
248
315
|
|
|
249
316
|
|
|
250
317
|
def _find_usecli_config_for_console_script() -> Path | None:
|
|
251
|
-
import importlib.metadata
|
|
252
|
-
|
|
253
318
|
command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
|
|
254
319
|
if not command_name:
|
|
255
320
|
return None
|
|
256
|
-
|
|
257
|
-
distributions = importlib.metadata.distributions()
|
|
258
|
-
except Exception:
|
|
259
|
-
return None
|
|
321
|
+
distributions = _get_distributions()
|
|
260
322
|
for dist in distributions:
|
|
261
323
|
try:
|
|
262
324
|
entry_points = dist.entry_points
|
|
@@ -334,10 +396,8 @@ def _find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
|
334
396
|
current = parent
|
|
335
397
|
|
|
336
398
|
search_root = git_root or start_dir.resolve()
|
|
337
|
-
config_match = _find_usecli_config_path(search_root, start_dir, skip_venv=True)
|
|
338
|
-
if config_match:
|
|
339
|
-
return config_match.parent
|
|
340
399
|
|
|
400
|
+
# Try fast lookups before expensive rglob (perf: global tools).
|
|
341
401
|
console_match = _find_usecli_config_for_console_script()
|
|
342
402
|
if console_match:
|
|
343
403
|
return console_match.parent
|
|
@@ -346,6 +406,10 @@ def _find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
|
346
406
|
if package_match:
|
|
347
407
|
return package_match.parent
|
|
348
408
|
|
|
409
|
+
config_match = _find_usecli_config_path(search_root, start_dir, skip_venv=True)
|
|
410
|
+
if config_match:
|
|
411
|
+
return config_match.parent
|
|
412
|
+
|
|
349
413
|
return git_root
|
|
350
414
|
|
|
351
415
|
|
|
@@ -357,18 +421,19 @@ def _load_usecli_config(
|
|
|
357
421
|
|
|
358
422
|
config_path = project_root / USECLI_CONFIG_TOML
|
|
359
423
|
if not config_path.exists():
|
|
360
|
-
|
|
361
|
-
project_root,
|
|
362
|
-
project_root,
|
|
363
|
-
skip_venv=True,
|
|
364
|
-
)
|
|
365
|
-
if not config_path or not config_path.exists():
|
|
424
|
+
# Try fast lookups before expensive rglob (perf: global tools).
|
|
366
425
|
console_match = _find_usecli_config_for_console_script()
|
|
367
426
|
if console_match:
|
|
368
427
|
return _load_usecli_config_file(console_match), console_match
|
|
369
428
|
package_match = _find_usecli_config_in_package()
|
|
370
429
|
if package_match:
|
|
371
430
|
config_path = package_match
|
|
431
|
+
if not config_path or not config_path.exists():
|
|
432
|
+
config_path = _find_usecli_config_path_for_command(
|
|
433
|
+
project_root,
|
|
434
|
+
project_root,
|
|
435
|
+
skip_venv=True,
|
|
436
|
+
)
|
|
372
437
|
if not config_path or not config_path.exists():
|
|
373
438
|
return {}, None
|
|
374
439
|
|
|
@@ -18,6 +18,77 @@ if sys.version_info >= (3, 11):
|
|
|
18
18
|
else:
|
|
19
19
|
import tomli as tomllib
|
|
20
20
|
|
|
21
|
+
# Depth cap for rglob – prevents scanning massive trees like ~/ghq.
|
|
22
|
+
_MAX_RGLOB_DEPTH = 6
|
|
23
|
+
|
|
24
|
+
_WALK_SKIP_ALWAYS: frozenset[str] = frozenset(
|
|
25
|
+
{
|
|
26
|
+
".git",
|
|
27
|
+
"__pycache__",
|
|
28
|
+
"node_modules",
|
|
29
|
+
".tox",
|
|
30
|
+
".nox",
|
|
31
|
+
".mypy_cache",
|
|
32
|
+
".pytest_cache",
|
|
33
|
+
".ruff_cache",
|
|
34
|
+
".auto",
|
|
35
|
+
".eggs",
|
|
36
|
+
"*.egg-info",
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
_WALK_SKIP_VENV: frozenset[str] = frozenset(
|
|
41
|
+
{
|
|
42
|
+
".venv",
|
|
43
|
+
"venv",
|
|
44
|
+
"site-packages",
|
|
45
|
+
"dist-packages",
|
|
46
|
+
"__pypackages__",
|
|
47
|
+
"pipx",
|
|
48
|
+
"venvs",
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _walk_for_filename(
|
|
54
|
+
directory: Path,
|
|
55
|
+
filename: str,
|
|
56
|
+
depth: int,
|
|
57
|
+
max_depth: int,
|
|
58
|
+
skip_dirs: frozenset[str],
|
|
59
|
+
results: list[Path],
|
|
60
|
+
) -> None:
|
|
61
|
+
if depth > max_depth:
|
|
62
|
+
return
|
|
63
|
+
try:
|
|
64
|
+
entries = list(directory.iterdir())
|
|
65
|
+
except (PermissionError, OSError):
|
|
66
|
+
return
|
|
67
|
+
for entry in entries:
|
|
68
|
+
try:
|
|
69
|
+
if entry.is_file() and entry.name == filename:
|
|
70
|
+
results.append(entry)
|
|
71
|
+
elif entry.is_dir() and entry.name not in skip_dirs:
|
|
72
|
+
_walk_for_filename(
|
|
73
|
+
entry, filename, depth + 1, max_depth, skip_dirs, results
|
|
74
|
+
)
|
|
75
|
+
except (PermissionError, OSError):
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _rglob_limited(
|
|
80
|
+
root_dir: Path,
|
|
81
|
+
filename: str,
|
|
82
|
+
*,
|
|
83
|
+
skip_venv: bool = True,
|
|
84
|
+
max_depth: int = _MAX_RGLOB_DEPTH,
|
|
85
|
+
) -> list[Path]:
|
|
86
|
+
"""Depth-bounded recursive filename search that prunes dirs during the walk."""
|
|
87
|
+
skip_dirs = _WALK_SKIP_ALWAYS | _WALK_SKIP_VENV if skip_venv else _WALK_SKIP_ALWAYS
|
|
88
|
+
results: list[Path] = []
|
|
89
|
+
_walk_for_filename(root_dir, filename, 0, max_depth, skip_dirs, results)
|
|
90
|
+
return results
|
|
91
|
+
|
|
21
92
|
|
|
22
93
|
def _get_importlib_metadata():
|
|
23
94
|
import importlib.metadata
|
|
@@ -25,6 +96,26 @@ def _get_importlib_metadata():
|
|
|
25
96
|
return importlib.metadata
|
|
26
97
|
|
|
27
98
|
|
|
99
|
+
_distributions_cache: list[Any] | None = None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _get_distributions() -> list[Any]:
|
|
103
|
+
global _distributions_cache
|
|
104
|
+
if _distributions_cache is not None:
|
|
105
|
+
return _distributions_cache
|
|
106
|
+
try:
|
|
107
|
+
metadata = _get_importlib_metadata()
|
|
108
|
+
_distributions_cache = list(metadata.distributions())
|
|
109
|
+
except Exception:
|
|
110
|
+
_distributions_cache = []
|
|
111
|
+
return _distributions_cache
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _reset_distributions_cache() -> None:
|
|
115
|
+
global _distributions_cache
|
|
116
|
+
_distributions_cache = None
|
|
117
|
+
|
|
118
|
+
|
|
28
119
|
def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
|
|
29
120
|
result = base.copy()
|
|
30
121
|
for key, value in override.items():
|
|
@@ -220,6 +311,20 @@ class ConfigManager:
|
|
|
220
311
|
break
|
|
221
312
|
current = parent
|
|
222
313
|
|
|
314
|
+
# Try fast lookups before expensive rglob (perf: global tools).
|
|
315
|
+
console_match = cls._find_usecli_config_for_console_script()
|
|
316
|
+
if console_match:
|
|
317
|
+
return console_match
|
|
318
|
+
|
|
319
|
+
if cls._is_within_usecli_package(start_dir):
|
|
320
|
+
package_match = cls._find_usecli_config_in_package()
|
|
321
|
+
if package_match:
|
|
322
|
+
return package_match
|
|
323
|
+
|
|
324
|
+
sys_match = cls._find_usecli_config_on_sys_path()
|
|
325
|
+
if sys_match:
|
|
326
|
+
return sys_match
|
|
327
|
+
|
|
223
328
|
search_root = find_project_root(start_dir) or start_dir.resolve()
|
|
224
329
|
is_framework = command_name == "usecli" if command_name else True
|
|
225
330
|
recursive_match = cls._find_usecli_config_in_tree(
|
|
@@ -228,18 +333,7 @@ class ConfigManager:
|
|
|
228
333
|
if recursive_match:
|
|
229
334
|
return recursive_match
|
|
230
335
|
|
|
231
|
-
|
|
232
|
-
if console_match:
|
|
233
|
-
return console_match
|
|
234
|
-
|
|
235
|
-
if not cls._is_within_usecli_package(start_dir):
|
|
236
|
-
return None
|
|
237
|
-
|
|
238
|
-
package_match = cls._find_usecli_config_in_package()
|
|
239
|
-
if package_match:
|
|
240
|
-
return package_match
|
|
241
|
-
|
|
242
|
-
return cls._find_usecli_config_on_sys_path()
|
|
336
|
+
return None
|
|
243
337
|
|
|
244
338
|
@staticmethod
|
|
245
339
|
def _find_usecli_config_in_tree(
|
|
@@ -248,13 +342,7 @@ class ConfigManager:
|
|
|
248
342
|
if not root_dir.exists() or not root_dir.is_dir():
|
|
249
343
|
return None
|
|
250
344
|
|
|
251
|
-
candidates =
|
|
252
|
-
if skip_venv:
|
|
253
|
-
candidates = [
|
|
254
|
-
path
|
|
255
|
-
for path in candidates
|
|
256
|
-
if not any(part in ConfigManager._SKIP_DIRS for part in path.parts)
|
|
257
|
-
]
|
|
345
|
+
candidates = _rglob_limited(root_dir, USECLI_CONFIG_TOML, skip_venv=skip_venv)
|
|
258
346
|
command_name = ConfigManager._get_command_name()
|
|
259
347
|
if command_name:
|
|
260
348
|
candidates = [
|
|
@@ -313,9 +401,9 @@ class ConfigManager:
|
|
|
313
401
|
package_root = Path(location)
|
|
314
402
|
if not package_root.exists() or not package_root.is_dir():
|
|
315
403
|
continue
|
|
316
|
-
candidates =
|
|
317
|
-
|
|
318
|
-
|
|
404
|
+
candidates = _rglob_limited(
|
|
405
|
+
package_root, USECLI_CONFIG_TOML, skip_venv=False
|
|
406
|
+
)
|
|
319
407
|
if command_name:
|
|
320
408
|
candidates = [
|
|
321
409
|
path
|
|
@@ -354,9 +442,9 @@ class ConfigManager:
|
|
|
354
442
|
package_root = Path(location)
|
|
355
443
|
if not package_root.exists() or not package_root.is_dir():
|
|
356
444
|
continue
|
|
357
|
-
candidates =
|
|
358
|
-
|
|
359
|
-
|
|
445
|
+
candidates = _rglob_limited(
|
|
446
|
+
package_root, USECLI_CONFIG_TOML, skip_venv=False
|
|
447
|
+
)
|
|
360
448
|
if command_name:
|
|
361
449
|
candidates = [
|
|
362
450
|
path
|
|
@@ -373,11 +461,7 @@ class ConfigManager:
|
|
|
373
461
|
command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
|
|
374
462
|
if not command_name:
|
|
375
463
|
return None
|
|
376
|
-
|
|
377
|
-
metadata = _get_importlib_metadata()
|
|
378
|
-
distributions = metadata.distributions()
|
|
379
|
-
except Exception:
|
|
380
|
-
return None
|
|
464
|
+
distributions = _get_distributions()
|
|
381
465
|
for dist in distributions:
|
|
382
466
|
try:
|
|
383
467
|
entry_points = dist.entry_points
|
|
@@ -478,11 +562,7 @@ class ConfigManager:
|
|
|
478
562
|
if not command_name:
|
|
479
563
|
return set()
|
|
480
564
|
aliases: set[str] = {command_name}
|
|
481
|
-
|
|
482
|
-
metadata = _get_importlib_metadata()
|
|
483
|
-
distributions = metadata.distributions()
|
|
484
|
-
except Exception:
|
|
485
|
-
return aliases
|
|
565
|
+
distributions = _get_distributions()
|
|
486
566
|
for dist in distributions:
|
|
487
567
|
try:
|
|
488
568
|
entry_points = dist.entry_points
|
|
@@ -571,11 +651,7 @@ class ConfigManager:
|
|
|
571
651
|
"""Search a source tree for a ``usecli.config.toml`` that matches."""
|
|
572
652
|
if not source_root.exists() or not source_root.is_dir():
|
|
573
653
|
return None
|
|
574
|
-
candidates =
|
|
575
|
-
p
|
|
576
|
-
for p in source_root.rglob(USECLI_CONFIG_TOML)
|
|
577
|
-
if not any(part in ConfigManager._SKIP_DIRS for part in p.parts)
|
|
578
|
-
]
|
|
654
|
+
candidates = _rglob_limited(source_root, USECLI_CONFIG_TOML)
|
|
579
655
|
if command_name:
|
|
580
656
|
candidates = [
|
|
581
657
|
p
|
|
@@ -685,11 +761,7 @@ class ConfigManager:
|
|
|
685
761
|
project_root = find_project_root(start_dir)
|
|
686
762
|
if project_root is None:
|
|
687
763
|
return None
|
|
688
|
-
candidates =
|
|
689
|
-
p
|
|
690
|
-
for p in project_root.rglob(USECLI_CONFIG_TOML)
|
|
691
|
-
if not any(part in self._SKIP_DIRS for part in p.parts)
|
|
692
|
-
]
|
|
764
|
+
candidates = _rglob_limited(project_root, USECLI_CONFIG_TOML)
|
|
693
765
|
if not candidates:
|
|
694
766
|
return None
|
|
695
767
|
candidates.sort(key=lambda p: (len(p.parts), str(p)))
|
|
@@ -821,6 +893,17 @@ def find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
|
821
893
|
current = parent
|
|
822
894
|
|
|
823
895
|
search_root = git_root or start_dir.resolve()
|
|
896
|
+
|
|
897
|
+
# Try fast lookups before expensive rglob (perf: global tools).
|
|
898
|
+
console_match = ConfigManager._find_usecli_config_for_console_script()
|
|
899
|
+
if console_match:
|
|
900
|
+
return console_match.parent
|
|
901
|
+
|
|
902
|
+
if ConfigManager._is_within_usecli_package(start_dir):
|
|
903
|
+
package_match = ConfigManager._find_usecli_config_in_package()
|
|
904
|
+
if package_match:
|
|
905
|
+
return package_match.parent
|
|
906
|
+
|
|
824
907
|
config_match = ConfigManager._find_usecli_config_in_tree(
|
|
825
908
|
search_root,
|
|
826
909
|
start_dir,
|
|
@@ -829,11 +912,6 @@ def find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
|
829
912
|
if config_match:
|
|
830
913
|
return config_match.parent
|
|
831
914
|
|
|
832
|
-
if ConfigManager._is_within_usecli_package(start_dir):
|
|
833
|
-
package_match = ConfigManager._find_usecli_config_in_package()
|
|
834
|
-
if package_match:
|
|
835
|
-
return package_match.parent
|
|
836
|
-
|
|
837
915
|
return git_root
|
|
838
916
|
|
|
839
917
|
|
|
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.60 → usecli-0.1.61}/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
|