usecli 0.1.35__tar.gz → 0.1.37__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.35 → usecli-0.1.37}/PKG-INFO +1 -1
- {usecli-0.1.35 → usecli-0.1.37}/pyproject.toml +1 -1
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/config/colors.py +146 -8
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/shared/config/manager.py +167 -11
- {usecli-0.1.35 → usecli-0.1.37}/LICENSE +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/README.md +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/init_command.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/base_command.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/exceptions/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/skill_generator.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/ui/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/ui/list.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/ui/title.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/services/command_service.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/menu.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/params.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/shared/config/globals.py +0 -0
- {usecli-0.1.35 → usecli-0.1.37}/src/usecli/ui.py +0 -0
- {usecli-0.1.35/src/usecli/cli → usecli-0.1.37/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.37"
|
|
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" }]
|
|
@@ -11,6 +11,9 @@ Usage:
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
import importlib.metadata
|
|
15
|
+
import importlib.util
|
|
16
|
+
import os
|
|
14
17
|
import sys
|
|
15
18
|
from pathlib import Path
|
|
16
19
|
from typing import Any, Callable, Final, final
|
|
@@ -25,6 +28,15 @@ PYPROJECT_TOML = "pyproject.toml"
|
|
|
25
28
|
USECLI_CONFIG_TOML = "usecli.config.toml"
|
|
26
29
|
DEFAULT_THEME_NAME = "default"
|
|
27
30
|
THEMES_DIR = Path(__file__).resolve().parent.parent / "themes"
|
|
31
|
+
_SKIP_DIRS = {
|
|
32
|
+
".venv",
|
|
33
|
+
"venv",
|
|
34
|
+
"site-packages",
|
|
35
|
+
"dist-packages",
|
|
36
|
+
"__pypackages__",
|
|
37
|
+
"pipx",
|
|
38
|
+
"venvs",
|
|
39
|
+
}
|
|
28
40
|
DEFAULT_THEME_COLORS: dict[str, str] = {
|
|
29
41
|
"primary": "#60D7FF",
|
|
30
42
|
"secondary": "#5EFF87",
|
|
@@ -48,15 +60,19 @@ DEFAULT_THEME_COLORS: dict[str, str] = {
|
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
|
|
51
|
-
def _find_usecli_config_path(
|
|
63
|
+
def _find_usecli_config_path(
|
|
64
|
+
root_dir: Path, start_dir: Path, *, skip_venv: bool
|
|
65
|
+
) -> Path | None:
|
|
52
66
|
if not root_dir.exists() or not root_dir.is_dir():
|
|
53
67
|
return None
|
|
54
68
|
|
|
55
|
-
candidates = [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
candidates = [path for path in root_dir.rglob(USECLI_CONFIG_TOML)]
|
|
70
|
+
if skip_venv:
|
|
71
|
+
candidates = [
|
|
72
|
+
path
|
|
73
|
+
for path in candidates
|
|
74
|
+
if not any(part in _SKIP_DIRS for part in path.parts)
|
|
75
|
+
]
|
|
60
76
|
if not candidates:
|
|
61
77
|
return None
|
|
62
78
|
|
|
@@ -83,6 +99,98 @@ def _find_usecli_config_path(root_dir: Path, start_dir: Path) -> Path | None:
|
|
|
83
99
|
return selection[0]
|
|
84
100
|
|
|
85
101
|
|
|
102
|
+
def _find_usecli_config_in_package() -> Path | None:
|
|
103
|
+
spec = importlib.util.find_spec(_get_package_name())
|
|
104
|
+
if spec is None or not spec.submodule_search_locations:
|
|
105
|
+
return None
|
|
106
|
+
for location in spec.submodule_search_locations:
|
|
107
|
+
package_root = Path(location)
|
|
108
|
+
if not package_root.exists() or not package_root.is_dir():
|
|
109
|
+
continue
|
|
110
|
+
candidates = [
|
|
111
|
+
path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
|
|
112
|
+
]
|
|
113
|
+
if candidates:
|
|
114
|
+
candidates.sort(key=lambda path: (len(path.parts), str(path)))
|
|
115
|
+
return candidates[0]
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _find_usecli_config_in_named_package(package_name: str) -> Path | None:
|
|
120
|
+
if not package_name:
|
|
121
|
+
return None
|
|
122
|
+
spec = importlib.util.find_spec(package_name)
|
|
123
|
+
if spec is None or not spec.submodule_search_locations:
|
|
124
|
+
return None
|
|
125
|
+
for location in spec.submodule_search_locations:
|
|
126
|
+
package_root = Path(location)
|
|
127
|
+
if not package_root.exists() or not package_root.is_dir():
|
|
128
|
+
continue
|
|
129
|
+
candidates = [
|
|
130
|
+
path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
|
|
131
|
+
]
|
|
132
|
+
if candidates:
|
|
133
|
+
candidates.sort(key=lambda path: (len(path.parts), str(path)))
|
|
134
|
+
return candidates[0]
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _find_usecli_config_for_console_script() -> Path | None:
|
|
139
|
+
command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
|
|
140
|
+
if not command_name:
|
|
141
|
+
return None
|
|
142
|
+
try:
|
|
143
|
+
distributions = importlib.metadata.distributions()
|
|
144
|
+
except Exception:
|
|
145
|
+
return None
|
|
146
|
+
for dist in distributions:
|
|
147
|
+
try:
|
|
148
|
+
entry_points = dist.entry_points
|
|
149
|
+
except Exception:
|
|
150
|
+
continue
|
|
151
|
+
for entry_point in entry_points:
|
|
152
|
+
if entry_point.group != "console_scripts":
|
|
153
|
+
continue
|
|
154
|
+
if entry_point.name != command_name:
|
|
155
|
+
continue
|
|
156
|
+
metadata = dist.metadata
|
|
157
|
+
dist_name = ""
|
|
158
|
+
if "Name" in metadata:
|
|
159
|
+
dist_name = metadata["Name"]
|
|
160
|
+
elif "name" in metadata:
|
|
161
|
+
dist_name = metadata["name"]
|
|
162
|
+
candidates: list[str] = []
|
|
163
|
+
if dist_name:
|
|
164
|
+
candidates.append(dist_name)
|
|
165
|
+
normalized = dist_name.replace("-", "_")
|
|
166
|
+
if normalized not in candidates:
|
|
167
|
+
candidates.append(normalized)
|
|
168
|
+
for package_name in candidates:
|
|
169
|
+
match = _find_usecli_config_in_named_package(package_name)
|
|
170
|
+
if match:
|
|
171
|
+
return match
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _is_preferred_package_path(path: Path) -> bool:
|
|
176
|
+
return any(part in _SKIP_DIRS for part in path.parts)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _is_within_usecli_package(start_dir: Path) -> bool:
|
|
180
|
+
spec = importlib.util.find_spec(_get_package_name())
|
|
181
|
+
if spec is None or not spec.submodule_search_locations:
|
|
182
|
+
return False
|
|
183
|
+
start_dir = start_dir.resolve()
|
|
184
|
+
for location in spec.submodule_search_locations:
|
|
185
|
+
package_root = Path(location)
|
|
186
|
+
try:
|
|
187
|
+
start_dir.relative_to(package_root)
|
|
188
|
+
return True
|
|
189
|
+
except ValueError:
|
|
190
|
+
continue
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
|
|
86
194
|
def _find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
87
195
|
if start_dir is None:
|
|
88
196
|
start_dir = Path.cwd()
|
|
@@ -110,10 +218,18 @@ def _find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
|
110
218
|
current = parent
|
|
111
219
|
|
|
112
220
|
search_root = git_root or start_dir.resolve()
|
|
113
|
-
config_match = _find_usecli_config_path(search_root, start_dir)
|
|
221
|
+
config_match = _find_usecli_config_path(search_root, start_dir, skip_venv=True)
|
|
114
222
|
if config_match:
|
|
115
223
|
return config_match.parent
|
|
116
224
|
|
|
225
|
+
console_match = _find_usecli_config_for_console_script()
|
|
226
|
+
if console_match:
|
|
227
|
+
return console_match.parent
|
|
228
|
+
|
|
229
|
+
package_match = _find_usecli_config_in_package()
|
|
230
|
+
if package_match:
|
|
231
|
+
return package_match.parent
|
|
232
|
+
|
|
117
233
|
return git_root
|
|
118
234
|
|
|
119
235
|
|
|
@@ -123,10 +239,25 @@ def _load_usecli_config(project_root: Path | None) -> dict[str, Any]:
|
|
|
123
239
|
|
|
124
240
|
config_path = project_root / USECLI_CONFIG_TOML
|
|
125
241
|
if not config_path.exists():
|
|
126
|
-
config_path = _find_usecli_config_path(
|
|
242
|
+
config_path = _find_usecli_config_path(
|
|
243
|
+
project_root,
|
|
244
|
+
project_root,
|
|
245
|
+
skip_venv=True,
|
|
246
|
+
)
|
|
247
|
+
if not config_path or not config_path.exists():
|
|
248
|
+
console_match = _find_usecli_config_for_console_script()
|
|
249
|
+
if console_match:
|
|
250
|
+
return _load_usecli_config_file(console_match)
|
|
251
|
+
package_match = _find_usecli_config_in_package()
|
|
252
|
+
if package_match:
|
|
253
|
+
config_path = package_match
|
|
127
254
|
if not config_path or not config_path.exists():
|
|
128
255
|
return {}
|
|
129
256
|
|
|
257
|
+
return _load_usecli_config_file(config_path)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _load_usecli_config_file(config_path: Path) -> dict[str, Any]:
|
|
130
261
|
try:
|
|
131
262
|
data = tomllib.loads(config_path.read_text())
|
|
132
263
|
except (tomllib.TOMLDecodeError, OSError):
|
|
@@ -145,6 +276,13 @@ def _load_usecli_config(project_root: Path | None) -> dict[str, Any]:
|
|
|
145
276
|
return {}
|
|
146
277
|
|
|
147
278
|
|
|
279
|
+
def _get_package_name() -> str:
|
|
280
|
+
package = __package__ or __name__
|
|
281
|
+
if not package:
|
|
282
|
+
return "usecli"
|
|
283
|
+
return package.split(".")[0]
|
|
284
|
+
|
|
285
|
+
|
|
148
286
|
def _normalize_color(value: Any) -> str | None:
|
|
149
287
|
if not isinstance(value, str):
|
|
150
288
|
return None
|
|
@@ -5,6 +5,9 @@ Handles loading and accessing configuration from project-level files.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import importlib.metadata
|
|
9
|
+
import importlib.util
|
|
10
|
+
import os
|
|
8
11
|
import sys
|
|
9
12
|
from pathlib import Path
|
|
10
13
|
from typing import Any
|
|
@@ -58,7 +61,15 @@ def _dedupe_items(items: list[str]) -> list[str]:
|
|
|
58
61
|
class ConfigManager:
|
|
59
62
|
"""Manages useCli configuration from project-level files."""
|
|
60
63
|
|
|
61
|
-
_SKIP_DIRS = {
|
|
64
|
+
_SKIP_DIRS = {
|
|
65
|
+
".venv",
|
|
66
|
+
"venv",
|
|
67
|
+
"site-packages",
|
|
68
|
+
"dist-packages",
|
|
69
|
+
"__pypackages__",
|
|
70
|
+
"pipx",
|
|
71
|
+
"venvs",
|
|
72
|
+
}
|
|
62
73
|
|
|
63
74
|
DEFAULT_CONFIG: dict[str, Any] = {
|
|
64
75
|
"title": "usecli",
|
|
@@ -107,8 +118,25 @@ class ConfigManager:
|
|
|
107
118
|
self.usecli_config_path: Path = usecli_config_path
|
|
108
119
|
self.start_dir: Path = start_dir
|
|
109
120
|
detected_root = find_project_root(start_dir)
|
|
110
|
-
if
|
|
111
|
-
|
|
121
|
+
if self.usecli_config_path.exists():
|
|
122
|
+
config_parent = self.usecli_config_path.parent
|
|
123
|
+
if detected_root is None:
|
|
124
|
+
detected_root = config_parent
|
|
125
|
+
else:
|
|
126
|
+
root_config = detected_root / USECLI_CONFIG_TOML
|
|
127
|
+
if self.usecli_config_path.resolve() != root_config.resolve():
|
|
128
|
+
detected_root = config_parent
|
|
129
|
+
else:
|
|
130
|
+
try:
|
|
131
|
+
self.usecli_config_path.relative_to(detected_root)
|
|
132
|
+
except ValueError:
|
|
133
|
+
detected_root = config_parent
|
|
134
|
+
else:
|
|
135
|
+
if any(
|
|
136
|
+
part in self._SKIP_DIRS
|
|
137
|
+
for part in self.usecli_config_path.parts
|
|
138
|
+
):
|
|
139
|
+
detected_root = config_parent
|
|
112
140
|
self.project_root: Path = (detected_root or start_dir).resolve()
|
|
113
141
|
self._config: dict[str, Any] = {}
|
|
114
142
|
self._overrides: dict[str, Any] = {}
|
|
@@ -179,22 +207,39 @@ class ConfigManager:
|
|
|
179
207
|
current = parent
|
|
180
208
|
|
|
181
209
|
search_root = find_project_root(start_dir) or start_dir.resolve()
|
|
182
|
-
recursive_match = cls._find_usecli_config_in_tree(
|
|
210
|
+
recursive_match = cls._find_usecli_config_in_tree(
|
|
211
|
+
search_root, start_dir, skip_venv=True
|
|
212
|
+
)
|
|
183
213
|
if recursive_match:
|
|
184
214
|
return recursive_match
|
|
185
215
|
|
|
216
|
+
console_match = cls._find_usecli_config_for_console_script()
|
|
217
|
+
if console_match:
|
|
218
|
+
return console_match
|
|
219
|
+
|
|
220
|
+
if not cls._is_within_usecli_package(start_dir):
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
package_match = cls._find_usecli_config_in_package()
|
|
224
|
+
if package_match:
|
|
225
|
+
return package_match
|
|
226
|
+
|
|
186
227
|
return cls._find_usecli_config_on_sys_path()
|
|
187
228
|
|
|
188
229
|
@staticmethod
|
|
189
|
-
def _find_usecli_config_in_tree(
|
|
230
|
+
def _find_usecli_config_in_tree(
|
|
231
|
+
root_dir: Path, start_dir: Path, *, skip_venv: bool
|
|
232
|
+
) -> Path | None:
|
|
190
233
|
if not root_dir.exists() or not root_dir.is_dir():
|
|
191
234
|
return None
|
|
192
235
|
|
|
193
|
-
candidates = [
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
236
|
+
candidates = [path for path in root_dir.rglob(USECLI_CONFIG_TOML)]
|
|
237
|
+
if skip_venv:
|
|
238
|
+
candidates = [
|
|
239
|
+
path
|
|
240
|
+
for path in candidates
|
|
241
|
+
if not any(part in ConfigManager._SKIP_DIRS for part in path.parts)
|
|
242
|
+
]
|
|
198
243
|
if not candidates:
|
|
199
244
|
return None
|
|
200
245
|
|
|
@@ -220,6 +265,98 @@ class ConfigManager:
|
|
|
220
265
|
selection.sort(key=_depth_key)
|
|
221
266
|
return selection[0]
|
|
222
267
|
|
|
268
|
+
@staticmethod
|
|
269
|
+
def _find_usecli_config_in_package() -> Path | None:
|
|
270
|
+
spec = importlib.util.find_spec(_get_package_name())
|
|
271
|
+
if spec is None or not spec.submodule_search_locations:
|
|
272
|
+
return None
|
|
273
|
+
for location in spec.submodule_search_locations:
|
|
274
|
+
package_root = Path(location)
|
|
275
|
+
if not package_root.exists() or not package_root.is_dir():
|
|
276
|
+
continue
|
|
277
|
+
candidates = [
|
|
278
|
+
path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
|
|
279
|
+
]
|
|
280
|
+
if candidates:
|
|
281
|
+
candidates.sort(key=lambda path: (len(path.parts), str(path)))
|
|
282
|
+
return candidates[0]
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
@classmethod
|
|
286
|
+
def _find_usecli_config_in_named_package(cls, package_name: str) -> Path | None:
|
|
287
|
+
if not package_name:
|
|
288
|
+
return None
|
|
289
|
+
spec = importlib.util.find_spec(package_name)
|
|
290
|
+
if spec is None or not spec.submodule_search_locations:
|
|
291
|
+
return None
|
|
292
|
+
for location in spec.submodule_search_locations:
|
|
293
|
+
package_root = Path(location)
|
|
294
|
+
if not package_root.exists() or not package_root.is_dir():
|
|
295
|
+
continue
|
|
296
|
+
candidates = [
|
|
297
|
+
path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
|
|
298
|
+
]
|
|
299
|
+
if candidates:
|
|
300
|
+
candidates.sort(key=lambda path: (len(path.parts), str(path)))
|
|
301
|
+
return candidates[0]
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
@classmethod
|
|
305
|
+
def _find_usecli_config_for_console_script(cls) -> Path | None:
|
|
306
|
+
command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
|
|
307
|
+
if not command_name:
|
|
308
|
+
return None
|
|
309
|
+
try:
|
|
310
|
+
distributions = importlib.metadata.distributions()
|
|
311
|
+
except Exception:
|
|
312
|
+
return None
|
|
313
|
+
for dist in distributions:
|
|
314
|
+
try:
|
|
315
|
+
entry_points = dist.entry_points
|
|
316
|
+
except Exception:
|
|
317
|
+
continue
|
|
318
|
+
for entry_point in entry_points:
|
|
319
|
+
if entry_point.group != "console_scripts":
|
|
320
|
+
continue
|
|
321
|
+
if entry_point.name != command_name:
|
|
322
|
+
continue
|
|
323
|
+
metadata = dist.metadata
|
|
324
|
+
dist_name = ""
|
|
325
|
+
if "Name" in metadata:
|
|
326
|
+
dist_name = metadata["Name"]
|
|
327
|
+
elif "name" in metadata:
|
|
328
|
+
dist_name = metadata["name"]
|
|
329
|
+
candidates = []
|
|
330
|
+
if dist_name:
|
|
331
|
+
candidates.append(dist_name)
|
|
332
|
+
normalized = dist_name.replace("-", "_")
|
|
333
|
+
if normalized not in candidates:
|
|
334
|
+
candidates.append(normalized)
|
|
335
|
+
for package_name in candidates:
|
|
336
|
+
match = cls._find_usecli_config_in_named_package(package_name)
|
|
337
|
+
if match:
|
|
338
|
+
return match
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
@staticmethod
|
|
342
|
+
def _is_preferred_package_path(path: Path) -> bool:
|
|
343
|
+
return any(part in ConfigManager._SKIP_DIRS for part in path.parts)
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
def _is_within_usecli_package(start_dir: Path) -> bool:
|
|
347
|
+
spec = importlib.util.find_spec(_get_package_name())
|
|
348
|
+
if spec is None or not spec.submodule_search_locations:
|
|
349
|
+
return False
|
|
350
|
+
start_dir = start_dir.resolve()
|
|
351
|
+
for location in spec.submodule_search_locations:
|
|
352
|
+
package_root = Path(location)
|
|
353
|
+
try:
|
|
354
|
+
start_dir.relative_to(package_root)
|
|
355
|
+
return True
|
|
356
|
+
except ValueError:
|
|
357
|
+
continue
|
|
358
|
+
return False
|
|
359
|
+
|
|
223
360
|
@staticmethod
|
|
224
361
|
def _find_usecli_config_on_sys_path() -> Path | None:
|
|
225
362
|
for entry in sys.path:
|
|
@@ -328,6 +465,9 @@ class ConfigManager:
|
|
|
328
465
|
|
|
329
466
|
def reload(self) -> None:
|
|
330
467
|
"""Reload configuration from disk."""
|
|
468
|
+
self.usecli_config_path = self._find_usecli_config(self.start_dir) or (
|
|
469
|
+
self.start_dir / USECLI_CONFIG_TOML
|
|
470
|
+
)
|
|
331
471
|
self._load_config()
|
|
332
472
|
|
|
333
473
|
@property
|
|
@@ -399,8 +539,24 @@ def find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
|
399
539
|
current = parent
|
|
400
540
|
|
|
401
541
|
search_root = git_root or start_dir.resolve()
|
|
402
|
-
config_match = ConfigManager._find_usecli_config_in_tree(
|
|
542
|
+
config_match = ConfigManager._find_usecli_config_in_tree(
|
|
543
|
+
search_root,
|
|
544
|
+
start_dir,
|
|
545
|
+
skip_venv=True,
|
|
546
|
+
)
|
|
403
547
|
if config_match:
|
|
404
548
|
return config_match.parent
|
|
405
549
|
|
|
550
|
+
if ConfigManager._is_within_usecli_package(start_dir):
|
|
551
|
+
package_match = ConfigManager._find_usecli_config_in_package()
|
|
552
|
+
if package_match:
|
|
553
|
+
return package_match.parent
|
|
554
|
+
|
|
406
555
|
return git_root
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _get_package_name() -> str:
|
|
559
|
+
package = __package__ or __name__
|
|
560
|
+
if not package:
|
|
561
|
+
return "usecli"
|
|
562
|
+
return package.split(".")[0]
|
|
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.35 → usecli-0.1.37}/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
|