usecli 0.1.45__tar.gz → 0.1.46__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 (71) hide show
  1. {usecli-0.1.45 → usecli-0.1.46}/PKG-INFO +1 -1
  2. {usecli-0.1.45 → usecli-0.1.46}/pyproject.toml +1 -1
  3. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/init_command.py +52 -9
  4. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/config/colors.py +105 -1
  5. {usecli-0.1.45 → usecli-0.1.46}/LICENSE +0 -0
  6. {usecli-0.1.45 → usecli-0.1.46}/README.md +0 -0
  7. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/__init__.py +0 -0
  8. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/__init__.py +0 -0
  9. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/README.md +0 -0
  10. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/__init__.py +0 -0
  11. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/custom/README.md +0 -0
  12. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/custom/__init__.py +0 -0
  13. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/__init__.py +0 -0
  14. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
  15. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
  16. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
  17. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
  18. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
  19. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
  20. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
  21. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
  22. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
  23. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
  24. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
  25. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/config/__init__.py +0 -0
  26. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/__init__.py +0 -0
  27. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/base_command.py +0 -0
  28. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/error/__init__.py +0 -0
  29. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/error/handler.py +0 -0
  30. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/error/utils.py +0 -0
  31. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/exceptions/__init__.py +0 -0
  32. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/exceptions/base.py +0 -0
  33. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/exceptions/config.py +0 -0
  34. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/exceptions/usage.py +0 -0
  35. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/exceptions/validation.py +0 -0
  36. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/skill_generator.py +0 -0
  37. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/ui/__init__.py +0 -0
  38. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/ui/list.py +0 -0
  39. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/ui/title.py +0 -0
  40. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/ui/title.txt +0 -0
  41. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/validators/__init__.py +0 -0
  42. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/validators/network.py +0 -0
  43. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/validators/numeric.py +0 -0
  44. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/validators/path.py +0 -0
  45. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/core/validators/string.py +0 -0
  46. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/services/__init__.py +0 -0
  47. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/services/command_service.py +0 -0
  48. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/templates/command.py.j2 +0 -0
  49. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/templates/theme.toml.j2 +0 -0
  50. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
  51. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/ayu_dark.toml +0 -0
  52. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
  53. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
  54. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
  55. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
  56. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/default.toml +0 -0
  57. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/dracula.toml +0 -0
  58. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
  59. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/nord.toml +0 -0
  60. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/themes/tokyo_night.toml +0 -0
  61. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/utils/__init__.py +0 -0
  62. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/utils/interactive/__init__.py +0 -0
  63. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
  64. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/menu.py +0 -0
  65. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/params.py +0 -0
  66. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/shared/__init__.py +0 -0
  67. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/shared/config/__init__.py +0 -0
  68. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/shared/config/globals.py +0 -0
  69. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/shared/config/manager.py +0 -0
  70. {usecli-0.1.45 → usecli-0.1.46}/src/usecli/ui.py +0 -0
  71. {usecli-0.1.45 → usecli-0.1.46}/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.45
3
+ Version: 0.1.46
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.45"
3
+ version = "0.1.46"
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" }]
@@ -108,7 +108,17 @@ class InitCommand(BaseCommand):
108
108
  return "updated" if existed else "created"
109
109
 
110
110
  def _should_skip_config_path(self, path: Path) -> bool:
111
- return any(part in ConfigManager._SKIP_DIRS for part in path.parts)
111
+ try:
112
+ resolved = path.resolve()
113
+ except OSError:
114
+ resolved = path
115
+ if any(part in ConfigManager._SKIP_DIRS for part in resolved.parts):
116
+ return True
117
+ try:
118
+ resolved.relative_to(Path(sys.prefix).resolve())
119
+ except ValueError:
120
+ return False
121
+ return True
112
122
 
113
123
  def _resolve_config_path(self, value: str, project_root: Path) -> Path:
114
124
  path = Path(value).expanduser()
@@ -186,6 +196,36 @@ class InitCommand(BaseCommand):
186
196
 
187
197
  return created
188
198
 
199
+ def _find_project_root_for_init(self, start_dir: Path) -> Path:
200
+ current = start_dir.resolve()
201
+ git_root: Path | None = None
202
+ while True:
203
+ if (current / "pyproject.toml").exists():
204
+ return current
205
+ if (current / USECLI_CONFIG_TOML).exists():
206
+ return current
207
+ git_dir = current / ".git"
208
+ if git_dir.exists():
209
+ git_root = current
210
+ break
211
+ parent = current.parent
212
+ if parent == current:
213
+ break
214
+ current = parent
215
+ return (git_root or start_dir).resolve()
216
+
217
+ def _find_pyproject_path_for_init(self, start_dir: Path) -> Path | None:
218
+ current = start_dir.resolve()
219
+ while True:
220
+ pyproject_path = current / "pyproject.toml"
221
+ if pyproject_path.exists():
222
+ return pyproject_path
223
+ parent = current.parent
224
+ if parent == current:
225
+ break
226
+ current = parent
227
+ return None
228
+
189
229
  def _derive_templates_dir(self, commands_dir: str) -> str:
190
230
  commands_path = Path(commands_dir)
191
231
  parent = commands_path.parent
@@ -509,12 +549,9 @@ include = ["{root_package}*"]
509
549
  ),
510
550
  ) -> None:
511
551
  cwd = Path.cwd()
512
- config_manager = ConfigManager(start_dir=cwd)
513
- project_root = config_manager.get_project_root()
514
- pyproject_path = (
515
- config_manager.pyproject_path
516
- if config_manager.pyproject_path.exists()
517
- else project_root / "pyproject.toml"
552
+ project_root = self._find_project_root_for_init(cwd)
553
+ pyproject_path = self._find_pyproject_path_for_init(cwd) or (
554
+ project_root / "pyproject.toml"
518
555
  )
519
556
 
520
557
  console.print()
@@ -712,14 +749,20 @@ include = ["{root_package}*"]
712
749
  config_root = project_root
713
750
  if commands_path.parent != project_root:
714
751
  config_root = commands_path.parent
715
- existing_config = ConfigManager(start_dir=config_root).usecli_config_path
716
- if existing_config.exists() and self._should_skip_config_path(existing_config):
752
+ existing_config = ConfigManager._find_usecli_config_in_tree(
753
+ project_root,
754
+ config_root,
755
+ skip_venv=True,
756
+ )
757
+ if existing_config is None or self._should_skip_config_path(existing_config):
717
758
  existing_config = config_root / USECLI_CONFIG_TOML
718
759
  default_config_path = (
719
760
  existing_config
720
761
  if existing_config.exists()
721
762
  else config_root / USECLI_CONFIG_TOML
722
763
  )
764
+ if self._should_skip_config_path(default_config_path):
765
+ default_config_path = config_root / USECLI_CONFIG_TOML
723
766
  config_location = Prompt.ask(
724
767
  f"[bold {COLOR.SECONDARY}]Config file location[/bold {COLOR.SECONDARY}]"
725
768
  " (path or directory)",
@@ -100,6 +100,110 @@ def _find_usecli_config_path(
100
100
  return selection[0]
101
101
 
102
102
 
103
+ def _get_command_name() -> str | None:
104
+ """Get the current command name from sys.argv."""
105
+ if not sys.argv:
106
+ return None
107
+ command = os.path.basename(sys.argv[0])
108
+ return command if command else None
109
+
110
+
111
+ def _get_console_script_aliases(command_name: str | None) -> set[str]:
112
+ """Get all aliases for a console script from package metadata."""
113
+ if not command_name:
114
+ return set()
115
+ aliases: set[str] = {command_name}
116
+ try:
117
+ distributions = importlib.metadata.distributions()
118
+ except Exception:
119
+ return aliases
120
+ for dist in distributions:
121
+ try:
122
+ entry_points = dist.entry_points
123
+ except Exception:
124
+ continue
125
+ names = [
126
+ entry_point.name
127
+ for entry_point in entry_points
128
+ if entry_point.group == "console_scripts"
129
+ ]
130
+ if command_name in names:
131
+ aliases.update(names)
132
+ break
133
+ return aliases
134
+
135
+
136
+ def _config_matches_command(path: Path, command_name: str | None) -> bool:
137
+ """Check if a config file matches the given command name."""
138
+ if command_name is None:
139
+ return True
140
+ try:
141
+ data = _load_usecli_config_file(path)
142
+ except (tomllib.TOMLDecodeError, OSError):
143
+ return True
144
+ config_command = data.get("command_name")
145
+ if not isinstance(config_command, str):
146
+ return True
147
+ normalized = config_command.strip()
148
+ if not normalized:
149
+ return True
150
+ if normalized == command_name:
151
+ return True
152
+ aliases = _get_console_script_aliases(command_name)
153
+ return normalized in aliases
154
+
155
+
156
+ def _find_usecli_config_path_for_command(
157
+ root_dir: Path, start_dir: Path, *, skip_venv: bool
158
+ ) -> Path | None:
159
+ """Find usecli config that matches the current command."""
160
+ if not root_dir.exists() or not root_dir.is_dir():
161
+ return None
162
+
163
+ candidates = [path for path in root_dir.rglob(USECLI_CONFIG_TOML)]
164
+ if skip_venv:
165
+ candidates = [
166
+ path
167
+ for path in candidates
168
+ if not any(part in _SKIP_DIRS for part in path.parts)
169
+ ]
170
+ if not candidates:
171
+ return None
172
+
173
+ command_name = _get_command_name()
174
+
175
+ # Filter candidates by command_name matching
176
+ if command_name:
177
+ candidates = [
178
+ path for path in candidates if _config_matches_command(path, command_name)
179
+ ]
180
+
181
+ if not candidates:
182
+ return None
183
+
184
+ start_dir = start_dir.resolve()
185
+ preferred: list[Path] = []
186
+ for path in candidates:
187
+ try:
188
+ path.relative_to(start_dir)
189
+ preferred.append(path)
190
+ except ValueError:
191
+ continue
192
+
193
+ selection = preferred or candidates
194
+
195
+ def _depth_key(path: Path) -> tuple[int, str]:
196
+ try:
197
+ relative = path.relative_to(start_dir)
198
+ return (len(relative.parts), str(path))
199
+ except ValueError:
200
+ relative = path.relative_to(root_dir)
201
+ return (len(relative.parts), str(path))
202
+
203
+ selection.sort(key=_depth_key)
204
+ return selection[0]
205
+
206
+
103
207
  def _find_usecli_config_in_package() -> Path | None:
104
208
  spec = importlib.util.find_spec(_get_package_name())
105
209
  if spec is None or not spec.submodule_search_locations:
@@ -242,7 +346,7 @@ def _load_usecli_config(
242
346
 
243
347
  config_path = project_root / USECLI_CONFIG_TOML
244
348
  if not config_path.exists():
245
- config_path = _find_usecli_config_path(
349
+ config_path = _find_usecli_config_path_for_command(
246
350
  project_root,
247
351
  project_root,
248
352
  skip_venv=True,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes