usecli 0.1.55__tar.gz → 0.1.56__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.55 → usecli-0.1.56}/PKG-INFO +1 -1
- {usecli-0.1.55 → usecli-0.1.56}/pyproject.toml +1 -1
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/__init__.py +4 -1
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/shared/config/manager.py +110 -5
- {usecli-0.1.55 → usecli-0.1.56}/LICENSE +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/README.md +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/init_command.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/config/colors.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/base_command.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/exceptions/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/ui/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/ui/list.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/ui/title.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/ui/title.txt +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/services/command_service.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/menu.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/params.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/shared/config/globals.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/src/usecli/ui.py +0 -0
- {usecli-0.1.55 → usecli-0.1.56}/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.56"
|
|
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" }]
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import shutil
|
|
5
6
|
import sys
|
|
6
7
|
from importlib import import_module
|
|
7
8
|
from typing import Any, Optional, Sequence
|
|
@@ -282,8 +283,10 @@ def run_app(
|
|
|
282
283
|
raise typer.Exit()
|
|
283
284
|
|
|
284
285
|
if version:
|
|
286
|
+
config = get_config()
|
|
287
|
+
command_path = shutil.which(sys.argv[0]) or sys.argv[0]
|
|
285
288
|
console.print(
|
|
286
|
-
f"[bold
|
|
289
|
+
f"[bold {theme.SECONDARY}]{config.get('title')} {service.version}[/bold {theme.SECONDARY}] [{theme.INFO}]({command_path})[/{theme.INFO}]"
|
|
287
290
|
)
|
|
288
291
|
raise typer.Exit()
|
|
289
292
|
|
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
import importlib.metadata
|
|
9
9
|
import importlib.util
|
|
10
|
+
import json
|
|
10
11
|
import os
|
|
11
12
|
import sys
|
|
12
13
|
from pathlib import Path
|
|
@@ -138,6 +139,8 @@ class ConfigManager:
|
|
|
138
139
|
):
|
|
139
140
|
detected_root = config_parent
|
|
140
141
|
self.project_root: Path = (detected_root or start_dir).resolve()
|
|
142
|
+
if self._is_in_venv(self.project_root):
|
|
143
|
+
self.project_root = start_dir.resolve()
|
|
141
144
|
self._config: dict[str, Any] = {}
|
|
142
145
|
self._overrides: dict[str, Any] = {}
|
|
143
146
|
self._load_config()
|
|
@@ -279,9 +282,26 @@ class ConfigManager:
|
|
|
279
282
|
|
|
280
283
|
@staticmethod
|
|
281
284
|
def _find_usecli_config_in_package() -> Path | None:
|
|
282
|
-
|
|
285
|
+
package_name = _get_package_name()
|
|
286
|
+
spec = importlib.util.find_spec(package_name)
|
|
283
287
|
if spec is None or not spec.submodule_search_locations:
|
|
284
288
|
return None
|
|
289
|
+
|
|
290
|
+
command_name = ConfigManager._get_command_name()
|
|
291
|
+
aliases = ConfigManager._get_console_script_aliases(command_name)
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
dist = importlib.metadata.distribution(package_name)
|
|
295
|
+
source_root = ConfigManager._resolve_editable_source_root(dist)
|
|
296
|
+
if source_root:
|
|
297
|
+
source_config = ConfigManager._search_source_for_config(
|
|
298
|
+
source_root, command_name, aliases
|
|
299
|
+
)
|
|
300
|
+
if source_config:
|
|
301
|
+
return source_config
|
|
302
|
+
except Exception:
|
|
303
|
+
pass
|
|
304
|
+
|
|
285
305
|
for location in spec.submodule_search_locations:
|
|
286
306
|
package_root = Path(location)
|
|
287
307
|
if not package_root.exists() or not package_root.is_dir():
|
|
@@ -289,8 +309,6 @@ class ConfigManager:
|
|
|
289
309
|
candidates = [
|
|
290
310
|
path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
|
|
291
311
|
]
|
|
292
|
-
command_name = ConfigManager._get_command_name()
|
|
293
|
-
aliases = ConfigManager._get_console_script_aliases(command_name)
|
|
294
312
|
if command_name:
|
|
295
313
|
candidates = [
|
|
296
314
|
path
|
|
@@ -311,6 +329,22 @@ class ConfigManager:
|
|
|
311
329
|
spec = importlib.util.find_spec(package_name)
|
|
312
330
|
if spec is None or not spec.submodule_search_locations:
|
|
313
331
|
return None
|
|
332
|
+
|
|
333
|
+
command_name = cls._get_command_name()
|
|
334
|
+
aliases = cls._get_console_script_aliases(command_name)
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
dist = importlib.metadata.distribution(package_name)
|
|
338
|
+
source_root = cls._resolve_editable_source_root(dist)
|
|
339
|
+
if source_root:
|
|
340
|
+
source_config = cls._search_source_for_config(
|
|
341
|
+
source_root, command_name, aliases
|
|
342
|
+
)
|
|
343
|
+
if source_config:
|
|
344
|
+
return source_config
|
|
345
|
+
except Exception:
|
|
346
|
+
pass
|
|
347
|
+
|
|
314
348
|
for location in spec.submodule_search_locations:
|
|
315
349
|
package_root = Path(location)
|
|
316
350
|
if not package_root.exists() or not package_root.is_dir():
|
|
@@ -318,8 +352,6 @@ class ConfigManager:
|
|
|
318
352
|
candidates = [
|
|
319
353
|
path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
|
|
320
354
|
]
|
|
321
|
-
command_name = cls._get_command_name()
|
|
322
|
-
aliases = cls._get_console_script_aliases(command_name)
|
|
323
355
|
if command_name:
|
|
324
356
|
candidates = [
|
|
325
357
|
path
|
|
@@ -362,7 +394,15 @@ class ConfigManager:
|
|
|
362
394
|
normalized = dist_name.replace("-", "_")
|
|
363
395
|
if normalized not in candidates:
|
|
364
396
|
candidates.append(normalized)
|
|
397
|
+
aliases = cls._get_console_script_aliases(command_name)
|
|
365
398
|
for package_name in candidates:
|
|
399
|
+
source_root = cls._resolve_editable_source_root(dist)
|
|
400
|
+
if source_root:
|
|
401
|
+
source_config = cls._search_source_for_config(
|
|
402
|
+
source_root, command_name, aliases
|
|
403
|
+
)
|
|
404
|
+
if source_config:
|
|
405
|
+
return source_config
|
|
366
406
|
match = cls._find_usecli_config_in_named_package(package_name)
|
|
367
407
|
if match:
|
|
368
408
|
return match
|
|
@@ -471,6 +511,71 @@ class ConfigManager:
|
|
|
471
511
|
aliases = {command_name}
|
|
472
512
|
return normalized in aliases
|
|
473
513
|
|
|
514
|
+
@staticmethod
|
|
515
|
+
def _is_in_venv(path: Path) -> bool:
|
|
516
|
+
resolved = path.resolve()
|
|
517
|
+
return any(part in ConfigManager._SKIP_DIRS for part in resolved.parts)
|
|
518
|
+
|
|
519
|
+
@staticmethod
|
|
520
|
+
def _resolve_editable_source_root(
|
|
521
|
+
dist: importlib.metadata.Distribution,
|
|
522
|
+
) -> Path | None:
|
|
523
|
+
"""Resolve the source directory for an editable-installed package.
|
|
524
|
+
|
|
525
|
+
Reads ``direct_url.json`` from the distribution's metadata to find the
|
|
526
|
+
local source tree. Returns the source root or ``None`` when the
|
|
527
|
+
distribution is not an editable install or the source no longer exists.
|
|
528
|
+
"""
|
|
529
|
+
try:
|
|
530
|
+
text = dist.read_text("direct_url.json")
|
|
531
|
+
except Exception:
|
|
532
|
+
return None
|
|
533
|
+
if not text:
|
|
534
|
+
return None
|
|
535
|
+
try:
|
|
536
|
+
data = json.loads(text)
|
|
537
|
+
except (json.JSONDecodeError, TypeError):
|
|
538
|
+
return None
|
|
539
|
+
if not isinstance(data, dict):
|
|
540
|
+
return None
|
|
541
|
+
if data.get("dir_info", {}).get("editable") is not True:
|
|
542
|
+
return None
|
|
543
|
+
url = data.get("url", "")
|
|
544
|
+
if not url:
|
|
545
|
+
return None
|
|
546
|
+
# ``url`` is a ``file://`` URI.
|
|
547
|
+
if url.startswith("file://"):
|
|
548
|
+
url = url[len("file://") :]
|
|
549
|
+
source = Path(url)
|
|
550
|
+
if source.exists() and source.is_dir():
|
|
551
|
+
return source.resolve()
|
|
552
|
+
return None
|
|
553
|
+
|
|
554
|
+
@staticmethod
|
|
555
|
+
def _search_source_for_config(
|
|
556
|
+
source_root: Path,
|
|
557
|
+
command_name: str | None,
|
|
558
|
+
aliases: set[str] | None,
|
|
559
|
+
) -> Path | None:
|
|
560
|
+
"""Search a source tree for a ``usecli.config.toml`` that matches."""
|
|
561
|
+
if not source_root.exists() or not source_root.is_dir():
|
|
562
|
+
return None
|
|
563
|
+
candidates = [
|
|
564
|
+
p
|
|
565
|
+
for p in source_root.rglob(USECLI_CONFIG_TOML)
|
|
566
|
+
if not any(part in ConfigManager._SKIP_DIRS for part in p.parts)
|
|
567
|
+
]
|
|
568
|
+
if command_name:
|
|
569
|
+
candidates = [
|
|
570
|
+
p
|
|
571
|
+
for p in candidates
|
|
572
|
+
if ConfigManager._config_matches_command(p, command_name, aliases)
|
|
573
|
+
]
|
|
574
|
+
if not candidates:
|
|
575
|
+
return None
|
|
576
|
+
candidates.sort(key=lambda p: (len(p.parts), str(p)))
|
|
577
|
+
return candidates[0]
|
|
578
|
+
|
|
474
579
|
def get(self, key: str, default: Any = None) -> Any:
|
|
475
580
|
"""Get a configuration value using dot notation.
|
|
476
581
|
|
|
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.55 → usecli-0.1.56}/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
|