usecli 0.1.75__tar.gz → 0.1.76__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 (73) hide show
  1. {usecli-0.1.75 → usecli-0.1.76}/PKG-INFO +1 -1
  2. usecli-0.1.76/pyproject.toml +92 -0
  3. usecli-0.1.75/pyproject.toml → usecli-0.1.76/pyproject.toml.orig +4 -2
  4. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/init_command.py +38 -0
  5. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/exceptions/usage.py +18 -2
  6. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/services/command_service.py +9 -4
  7. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/shared/config/manager.py +20 -2
  8. {usecli-0.1.75 → usecli-0.1.76}/LICENSE +0 -0
  9. {usecli-0.1.75 → usecli-0.1.76}/README.md +0 -0
  10. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/__init__.py +0 -0
  11. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/__init__.py +0 -0
  12. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/README.md +0 -0
  13. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/__init__.py +0 -0
  14. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/custom/README.md +0 -0
  15. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/custom/__init__.py +0 -0
  16. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/__init__.py +0 -0
  17. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
  18. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
  19. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
  20. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
  21. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
  22. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
  23. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
  24. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
  25. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
  26. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
  27. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
  28. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/config/__init__.py +0 -0
  29. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/config/colors.py +0 -0
  30. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/__init__.py +0 -0
  31. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/base_command.py +0 -0
  32. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/error/__init__.py +0 -0
  33. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/error/handler.py +0 -0
  34. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/error/utils.py +0 -0
  35. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/exceptions/__init__.py +0 -0
  36. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/exceptions/base.py +0 -0
  37. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/exceptions/config.py +0 -0
  38. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/exceptions/validation.py +0 -0
  39. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/runtime.py +0 -0
  40. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/ui/__init__.py +0 -0
  41. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/ui/list.py +0 -0
  42. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/ui/title.py +0 -0
  43. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/ui/title.txt +0 -0
  44. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/validators/__init__.py +0 -0
  45. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/validators/network.py +0 -0
  46. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/validators/numeric.py +0 -0
  47. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/validators/path.py +0 -0
  48. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/core/validators/string.py +0 -0
  49. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/services/__init__.py +0 -0
  50. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/templates/command.py.j2 +0 -0
  51. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/templates/theme.toml.j2 +0 -0
  52. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
  53. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/themes/ayu_dark.toml +0 -0
  54. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
  55. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
  56. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
  57. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
  58. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/themes/default.toml +0 -0
  59. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/themes/dracula.toml +0 -0
  60. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
  61. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/themes/nord.toml +0 -0
  62. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/themes/tokyo_night.toml +0 -0
  63. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/utils/__init__.py +0 -0
  64. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/utils/interactive/__init__.py +0 -0
  65. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
  66. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/menu.py +0 -0
  67. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/params.py +0 -0
  68. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/progress.py +0 -0
  69. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/shared/__init__.py +0 -0
  70. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/shared/config/__init__.py +0 -0
  71. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/shared/config/globals.py +0 -0
  72. {usecli-0.1.75 → usecli-0.1.76}/src/usecli/ui.py +0 -0
  73. {usecli-0.1.75 → usecli-0.1.76}/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.75
3
+ Version: 0.1.76
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>
@@ -0,0 +1,92 @@
1
+ [project]
2
+ name = "usecli"
3
+ version = "0.1.76"
4
+ description = "A powerful Python CLI framework for building beautiful, developer-friendly command-line tools."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ classifiers = [
8
+ "Programming Language :: Python :: 3",
9
+ "Operating System :: OS Independent",
10
+ "Intended Audience :: Developers",
11
+ ]
12
+ dependencies = [
13
+ "case-converter>=1.2.0",
14
+ "click>=8.3.1",
15
+ "fzf-bin>=0.67.0",
16
+ "jinja2>=3.1.6",
17
+ "pyfiglet>=1.0.4",
18
+ "rich>=14.3.1",
19
+ "simple-term-menu>=1.6.6",
20
+ "tomli>=2.4.0",
21
+ "typer>=0.21.1",
22
+ ]
23
+ license = "MIT"
24
+ license-files = ["LICEN[CS]E*"]
25
+
26
+ [[project.authors]]
27
+ name = "Edward Boswell"
28
+ email = "thememium@gmail.com"
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/thememium/usecli"
32
+ Documentation = "https://github.com/thememium/usecli"
33
+ Repository = "https://github.com/thememium/usecli.git"
34
+ Issues = "https://github.com/thememium/usecli/issues"
35
+ Changelog = "https://github.com/thememium/usecli/blob/master/CHANGELOG.md"
36
+
37
+ [project.scripts]
38
+ usecli = "usecli:main"
39
+
40
+ [build-system]
41
+ requires = ["uv_build>=0.10.2,<0.11.0"]
42
+ build-backend = "uv_build"
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "deptry>=0.24.0",
47
+ "isort>=7.0.0",
48
+ "poethepoet>=0.41.0",
49
+ "pytest>=9.0.2",
50
+ "pytest-cov>=7.1.0",
51
+ "ruff>=0.15.1",
52
+ "ty>=0.0.17",
53
+ ]
54
+
55
+ [tool.deptry]
56
+ known_first_party = ["usecli"]
57
+
58
+ [tool.deptry.package_module_name_map]
59
+ case-converter = "caseconverter"
60
+
61
+ [tool.deptry.per_rule_ignores]
62
+ DEP002 = ["fzf-bin"]
63
+
64
+ [tool.poe.tasks]
65
+ dev = "uv run usecli"
66
+ clean-full = "sh -c 'uv run isort . && uv run ruff check . --fix && uv run ruff format . && uv run deptry . && uv run ty check'"
67
+ clean = "sh -c 'uv run isort . && uv run ruff format .'"
68
+ test = "uv run pytest tests/ -v"
69
+ test-cov = "uv run pytest tests/ -v --cov=usecli --cov-report=term-missing"
70
+ sort = "uv run isort ."
71
+ lint = "uv run ruff check ."
72
+ format = "uv run ruff format ."
73
+ deptry = "uv run deptry ."
74
+ typecheck = "uv run ty check"
75
+ build = "uv build --no-sources"
76
+ release = "bash scripts/release.sh"
77
+
78
+ [tool.ty.src]
79
+ include = [
80
+ "src",
81
+ "tests",
82
+ ]
83
+ exclude = [
84
+ "**/__pycache__",
85
+ "**/.venv",
86
+ ]
87
+
88
+ [tool.ty.rules]
89
+
90
+ [tool.setuptools.packages.find]
91
+ where = ["."]
92
+ include = ["cli*"]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usecli"
3
- version = "0.1.75"
3
+ version = "0.1.76"
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" }]
@@ -44,6 +44,7 @@ dev = [
44
44
  "isort>=7.0.0",
45
45
  "poethepoet>=0.41.0",
46
46
  "pytest>=9.0.2",
47
+ "pytest-cov>=7.1.0",
47
48
  "ruff>=0.15.1",
48
49
  "ty>=0.0.17",
49
50
  ]
@@ -62,10 +63,11 @@ dev = "uv run usecli"
62
63
  clean-full = "sh -c 'uv run isort . && uv run ruff check . --fix && uv run ruff format . && uv run deptry . && uv run ty check'"
63
64
  clean = "sh -c 'uv run isort . && uv run ruff format .'"
64
65
  test = "uv run pytest tests/ -v"
66
+ test-cov = "uv run pytest tests/ -v --cov=usecli --cov-report=term-missing"
65
67
  sort = "uv run isort ."
66
68
  lint = "uv run ruff check ."
67
69
  format = "uv run ruff format ."
68
- deptry = "uv deptry ."
70
+ deptry = "uv run deptry ."
69
71
  typecheck = "uv run ty check"
70
72
  build = "uv build --no-sources"
71
73
  release = "bash scripts/release.sh"
@@ -87,6 +87,36 @@ class InitCommand(BaseCommand):
87
87
  pyproject_path.write_text(content)
88
88
  return True
89
89
 
90
+ def _add_setuptools_package_data(
91
+ self, pyproject_path: Path, commands_dir: str
92
+ ) -> bool:
93
+ if not pyproject_path.exists():
94
+ return False
95
+
96
+ content = pyproject_path.read_text()
97
+ if "[tool.setuptools.package-data]" in content:
98
+ return False
99
+
100
+ parts = Path(commands_dir).parts
101
+ if not parts:
102
+ return False
103
+
104
+ # setuptools only ships .py files by default. init scaffolds
105
+ # usecli.config.toml, a make-command template (.j2), and a default
106
+ # theme (.toml) inside this package - without this declaration a
107
+ # non-editable install of the scaffolded project silently drops all
108
+ # three, and command discovery/theming then fail with no error.
109
+ root_package = parts[0]
110
+ package_data_block = (
111
+ "[tool.setuptools.package-data]\n"
112
+ f'{root_package} = ["**/*.toml", "**/*.j2"]\n\n'
113
+ )
114
+
115
+ content = content.rstrip() + f"\n\n{package_data_block}"
116
+
117
+ pyproject_path.write_text(content)
118
+ return True
119
+
90
120
  def _write_usecli_config(
91
121
  self,
92
122
  config_path: Path,
@@ -546,6 +576,9 @@ build-backend = "setuptools.build_meta"
546
576
  [tool.setuptools.packages.find]
547
577
  where = ["."]
548
578
  include = ["{root_package}*"]
579
+
580
+ [tool.setuptools.package-data]
581
+ {root_package} = ["**/*.toml", "**/*.j2"]
549
582
  '''
550
583
 
551
584
  pyproject_path.write_text(pyproject_content)
@@ -638,6 +671,7 @@ include = ["{root_package}*"]
638
671
  )
639
672
  self._ensure_build_system(pyproject_path)
640
673
  self._add_setuptools_package_discovery(pyproject_path, commands_dir)
674
+ self._add_setuptools_package_data(pyproject_path, commands_dir)
641
675
  if scripts_status in {"added", "updated", "unchanged"}:
642
676
  self._sync_environment(project_root, command_name)
643
677
  else:
@@ -950,6 +984,10 @@ include = ["{root_package}*"]
950
984
  console.print(
951
985
  f"[{COLOR.SUCCESS}]Added setuptools package discovery to pyproject.toml[/{COLOR.SUCCESS}]"
952
986
  )
987
+ if self._add_setuptools_package_data(pyproject_path, commands_dir):
988
+ console.print(
989
+ f"[{COLOR.SUCCESS}]Added setuptools package data to pyproject.toml[/{COLOR.SUCCESS}]"
990
+ )
953
991
 
954
992
  if scripts_status in {"added", "updated", "unchanged"}:
955
993
  self._sync_environment(project_root, command_name)
@@ -6,7 +6,7 @@ import sys
6
6
  from typing import IO
7
7
 
8
8
  from click.core import Context as ClickContext
9
- from click.exceptions import BadParameter, UsageError
9
+ from click.exceptions import BadParameter, Exit, UsageError
10
10
  from rich.console import Console
11
11
 
12
12
  from usecli.cli.config.colors import COLOR
@@ -16,14 +16,30 @@ console = Console(stderr=True)
16
16
 
17
17
 
18
18
  def _get_help_text_with_command_name(ctx: ClickContext) -> str:
19
+ # ctx.get_help() delegates to ctx.command.get_help(ctx). Commands built
20
+ # from BaseCommand use CustomHelpCommand, whose get_help() prints the
21
+ # help directly and raises Exit(0) instead of returning a string (see
22
+ # base_command.py) so that a bare `--help` exits cleanly. That contract
23
+ # break means this call can raise instead of returning text whenever
24
+ # ctx.command is a CustomHelpCommand (i.e. any real subcommand). Left
25
+ # uncaught, the Exit(0) propagates out of show() and is interpreted by
26
+ # click/typer as "the command exited 0", clobbering the exit code this
27
+ # usage error was about to set. The help has already been printed as a
28
+ # side effect by that point, so it's safe to treat this as "no
29
+ # additional help text to append" and let the caller continue.
19
30
  command_name = get_script_command_name(default=getattr(ctx, "info_name", None))
20
31
  if not command_name:
21
- return ctx.get_help()
32
+ try:
33
+ return ctx.get_help()
34
+ except Exit:
35
+ return ""
22
36
 
23
37
  original_info_name = getattr(ctx, "info_name", None)
24
38
  try:
25
39
  ctx.info_name = command_name
26
40
  return ctx.get_help()
41
+ except Exit:
42
+ return ""
27
43
  finally:
28
44
  ctx.info_name = original_info_name
29
45
 
@@ -77,14 +77,19 @@ class CommandService:
77
77
  self._load_from_dir(project_commands_dir)
78
78
 
79
79
  def _load_version(self) -> None:
80
- config_version = get_config().get_project_version()
81
- if config_version:
82
- self.version = config_version
83
- return
80
+ # Try the installed distribution first, same order as
81
+ # about_command.py's _get_application_version(). A pyproject.toml
82
+ # walked up from cwd has no ownership check - it can belong to an
83
+ # unrelated project the user happens to be standing in - so it's
84
+ # only a fallback, not the first check.
84
85
  app_version = self._get_application_version()
85
86
  if app_version:
86
87
  self.version = app_version
87
88
  return
89
+ config_version = get_config().get_project_version()
90
+ if config_version:
91
+ self.version = config_version
92
+ return
88
93
  try:
89
94
  self.version = get_version("usecli")
90
95
  except Exception as error:
@@ -588,15 +588,23 @@ class ConfigManager:
588
588
  dist_name = metadata["Name"]
589
589
  elif "name" in metadata:
590
590
  dist_name = metadata["name"]
591
- candidates = []
591
+ candidates: list[str] = []
592
592
  if dist_name:
593
593
  candidates.append(dist_name)
594
594
  normalized = dist_name.replace("-", "_")
595
595
  if normalized not in candidates:
596
596
  candidates.append(normalized)
597
+ # Also try the actual importable package names from top_level.txt.
598
+ # For non-editable installs the distribution name (e.g.
599
+ # "my-cli") often differs from the importable package
600
+ # name (e.g. "mycli"), so relying on the distribution name alone fails
601
+ # to locate the package via importlib.util.find_spec.
602
+ for top_level in cls._read_top_level_packages(dist):
603
+ if top_level not in candidates:
604
+ candidates.append(top_level)
597
605
  aliases = cls._get_console_script_aliases(command_name)
606
+ source_root = cls._resolve_editable_source_root(dist)
598
607
  for package_name in candidates:
599
- source_root = cls._resolve_editable_source_root(dist)
600
608
  if source_root:
601
609
  source_config = cls._search_source_for_config(
602
610
  source_root, command_name, aliases
@@ -608,6 +616,16 @@ class ConfigManager:
608
616
  return match
609
617
  return None
610
618
 
619
+ @staticmethod
620
+ def _read_top_level_packages(dist: Any) -> list[str]:
621
+ try:
622
+ text = dist.read_text("top_level.txt")
623
+ except Exception:
624
+ return []
625
+ if not text:
626
+ return []
627
+ return [line.strip() for line in text.splitlines() if line.strip()]
628
+
611
629
  @staticmethod
612
630
  def _is_preferred_package_path(path: Path) -> bool:
613
631
  return any(part in ConfigManager._SKIP_DIRS for part in path.parts)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes