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.
Files changed (70) hide show
  1. {usecli-0.1.55 → usecli-0.1.56}/PKG-INFO +1 -1
  2. {usecli-0.1.55 → usecli-0.1.56}/pyproject.toml +1 -1
  3. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/__init__.py +4 -1
  4. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/shared/config/manager.py +110 -5
  5. {usecli-0.1.55 → usecli-0.1.56}/LICENSE +0 -0
  6. {usecli-0.1.55 → usecli-0.1.56}/README.md +0 -0
  7. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/__init__.py +0 -0
  8. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/README.md +0 -0
  9. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/__init__.py +0 -0
  10. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/custom/README.md +0 -0
  11. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/custom/__init__.py +0 -0
  12. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/__init__.py +0 -0
  13. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
  14. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
  15. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
  16. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
  17. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
  18. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
  19. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
  20. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
  21. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
  22. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
  23. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
  24. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/commands/init_command.py +0 -0
  25. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/config/__init__.py +0 -0
  26. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/config/colors.py +0 -0
  27. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/__init__.py +0 -0
  28. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/base_command.py +0 -0
  29. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/error/__init__.py +0 -0
  30. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/error/handler.py +0 -0
  31. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/error/utils.py +0 -0
  32. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/exceptions/__init__.py +0 -0
  33. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/exceptions/base.py +0 -0
  34. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/exceptions/config.py +0 -0
  35. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/exceptions/usage.py +0 -0
  36. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/exceptions/validation.py +0 -0
  37. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/ui/__init__.py +0 -0
  38. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/ui/list.py +0 -0
  39. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/ui/title.py +0 -0
  40. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/ui/title.txt +0 -0
  41. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/validators/__init__.py +0 -0
  42. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/validators/network.py +0 -0
  43. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/validators/numeric.py +0 -0
  44. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/validators/path.py +0 -0
  45. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/core/validators/string.py +0 -0
  46. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/services/__init__.py +0 -0
  47. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/services/command_service.py +0 -0
  48. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/templates/command.py.j2 +0 -0
  49. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/templates/theme.toml.j2 +0 -0
  50. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
  51. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/ayu_dark.toml +0 -0
  52. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
  53. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
  54. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
  55. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
  56. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/default.toml +0 -0
  57. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/dracula.toml +0 -0
  58. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
  59. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/nord.toml +0 -0
  60. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/themes/tokyo_night.toml +0 -0
  61. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/utils/__init__.py +0 -0
  62. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/utils/interactive/__init__.py +0 -0
  63. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
  64. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/menu.py +0 -0
  65. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/params.py +0 -0
  66. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/shared/__init__.py +0 -0
  67. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/shared/config/__init__.py +0 -0
  68. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/shared/config/globals.py +0 -0
  69. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/ui.py +0 -0
  70. {usecli-0.1.55 → usecli-0.1.56}/src/usecli/usecli.config.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usecli
3
- Version: 0.1.55
3
+ Version: 0.1.56
4
4
  Summary: A powerful Python CLI framework for building beautiful, developer-friendly command-line tools.
5
5
  Author: Edward Boswell
6
6
  Author-email: Edward Boswell <thememium@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usecli"
3
- version = "0.1.55"
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 blue]CLI Version:[/bold blue] [green]{service.version}[/green]"
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
- spec = importlib.util.find_spec(_get_package_name())
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