usecli 0.1.32__tar.gz → 0.1.33__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 (69) hide show
  1. {usecli-0.1.32 → usecli-0.1.33}/PKG-INFO +1 -1
  2. {usecli-0.1.32 → usecli-0.1.33}/pyproject.toml +1 -1
  3. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/init_command.py +35 -1
  4. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/config/colors.py +8 -2
  5. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/shared/config/globals.py +1 -0
  6. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/shared/config/manager.py +89 -7
  7. {usecli-0.1.32 → usecli-0.1.33}/LICENSE +0 -0
  8. {usecli-0.1.32 → usecli-0.1.33}/README.md +0 -0
  9. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/__init__.py +0 -0
  10. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/__init__.py +0 -0
  11. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/README.md +0 -0
  12. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/__init__.py +0 -0
  13. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/custom/README.md +0 -0
  14. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/custom/__init__.py +0 -0
  15. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/__init__.py +0 -0
  16. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
  17. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
  18. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
  19. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
  20. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
  21. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
  22. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
  23. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
  24. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
  25. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
  26. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
  27. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/config/__init__.py +0 -0
  28. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/__init__.py +0 -0
  29. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/base_command.py +0 -0
  30. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/error/__init__.py +0 -0
  31. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/error/handler.py +0 -0
  32. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/error/utils.py +0 -0
  33. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/exceptions/__init__.py +0 -0
  34. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/exceptions/base.py +0 -0
  35. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/exceptions/config.py +0 -0
  36. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/exceptions/usage.py +0 -0
  37. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/exceptions/validation.py +0 -0
  38. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/skill_generator.py +0 -0
  39. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/ui/__init__.py +0 -0
  40. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/ui/list.py +0 -0
  41. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/ui/title.py +0 -0
  42. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/validators/__init__.py +0 -0
  43. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/validators/network.py +0 -0
  44. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/validators/numeric.py +0 -0
  45. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/validators/path.py +0 -0
  46. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/core/validators/string.py +0 -0
  47. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/services/__init__.py +0 -0
  48. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/services/command_service.py +0 -0
  49. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/templates/command.py.j2 +0 -0
  50. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/templates/theme.toml.j2 +0 -0
  51. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/templates/usecli.toml.j2 +0 -0
  52. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/themes/ayu_dark.toml +0 -0
  53. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
  54. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
  55. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
  56. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
  57. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/themes/default.toml +0 -0
  58. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/themes/dracula.toml +0 -0
  59. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
  60. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/themes/nord.toml +0 -0
  61. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/themes/tokyo_night.toml +0 -0
  62. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/utils/__init__.py +0 -0
  63. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/utils/interactive/__init__.py +0 -0
  64. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
  65. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/menu.py +0 -0
  66. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/params.py +0 -0
  67. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/shared/__init__.py +0 -0
  68. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/shared/config/__init__.py +0 -0
  69. {usecli-0.1.32 → usecli-0.1.33}/src/usecli/ui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usecli
3
- Version: 0.1.32
3
+ Version: 0.1.33
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.32"
3
+ version = "0.1.33"
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" }]
@@ -27,7 +27,7 @@ from usecli.cli.core.base_command import BaseCommand
27
27
  from usecli.cli.core.exceptions import UsecliBadParameter
28
28
  from usecli.cli.core.validators import validate_command_name
29
29
  from usecli.cli.utils.interactive.terminal_menu import terminal_menu
30
- from usecli.shared.config.globals import TEMPLATES_DIR, THEMES_DIR
30
+ from usecli.shared.config.globals import TEMPLATES_DIR, THEMES_DIR, USECLI_TOML
31
31
  from usecli.shared.config.manager import ConfigManager, get_config
32
32
 
33
33
  console = Console()
@@ -113,6 +113,23 @@ class InitCommand(BaseCommand):
113
113
  pyproject_path.write_text(content)
114
114
  return True
115
115
 
116
+ def _write_usecli_toml(
117
+ self, project_root: Path, config_content: str, force: bool
118
+ ) -> str:
119
+ config_path = project_root / USECLI_TOML
120
+ existed = config_path.exists()
121
+ if existed and not force:
122
+ should_overwrite = Confirm.ask(
123
+ f"[{COLOR.WARNING}]usecli.toml already exists at {config_path}.[/{COLOR.WARNING}]\n"
124
+ "Overwrite it with the new settings from this init run?",
125
+ default=False,
126
+ )
127
+ if not should_overwrite:
128
+ return "skipped"
129
+
130
+ config_path.write_text(config_content.rstrip() + "\n")
131
+ return "updated" if existed else "created"
132
+
116
133
  def _ensure_project_scripts(
117
134
  self, pyproject_path: Path, command_name: str, force: bool
118
135
  ) -> str:
@@ -685,6 +702,7 @@ include = ["{root_package}*"]
685
702
  )
686
703
 
687
704
  scripts_status: str | None = None
705
+ usecli_toml_status: str | None = None
688
706
 
689
707
  # Check if pyproject.toml exists
690
708
  if pyproject_path.exists():
@@ -744,6 +762,22 @@ include = ["{root_package}*"]
744
762
 
745
763
  self._sync_environment(project_root, command_name)
746
764
 
765
+ usecli_toml_status = self._write_usecli_toml(
766
+ project_root, config_content, force
767
+ )
768
+ if usecli_toml_status == "created":
769
+ console.print(
770
+ f"[{COLOR.SUCCESS}]Created {USECLI_TOML} for runtime config fallback[/{COLOR.SUCCESS}]"
771
+ )
772
+ elif usecli_toml_status == "updated":
773
+ console.print(
774
+ f"[{COLOR.SUCCESS}]Updated {USECLI_TOML} for runtime config fallback[/{COLOR.SUCCESS}]"
775
+ )
776
+ elif usecli_toml_status == "skipped":
777
+ console.print(
778
+ f"[{COLOR.WARNING}]Skipped updating {USECLI_TOML}.[/{COLOR.WARNING}]"
779
+ )
780
+
747
781
  # Show summary
748
782
  summary_command = (
749
783
  command_name
@@ -21,6 +21,8 @@ else:
21
21
  import tomli as tomllib
22
22
 
23
23
 
24
+ PYPROJECT_TOML = "pyproject.toml"
25
+ USECLI_TOML = "usecli.toml"
24
26
  DEFAULT_THEME_NAME = "default"
25
27
  THEMES_DIR = Path(__file__).resolve().parent.parent / "themes"
26
28
  DEFAULT_THEME_COLORS: dict[str, str] = {
@@ -53,10 +55,14 @@ def _find_project_root(start_dir: Path | None = None) -> Path | None:
53
55
  current = start_dir.resolve()
54
56
 
55
57
  while True:
56
- pyproject_path = current / "pyproject.toml"
58
+ pyproject_path = current / PYPROJECT_TOML
57
59
  if pyproject_path.exists():
58
60
  return current
59
61
 
62
+ usecli_path = current / USECLI_TOML
63
+ if usecli_path.exists():
64
+ return current
65
+
60
66
  git_dir = current / ".git"
61
67
  if git_dir.exists():
62
68
  return current
@@ -73,7 +79,7 @@ def _load_usecli_config(project_root: Path | None) -> dict[str, Any]:
73
79
  if project_root is None:
74
80
  return {}
75
81
 
76
- pyproject_path = project_root / "pyproject.toml"
82
+ pyproject_path = project_root / PYPROJECT_TOML
77
83
  if not pyproject_path.exists():
78
84
  return {}
79
85
 
@@ -17,3 +17,4 @@ THEMES_DIR = CLI_ROOT / "themes"
17
17
 
18
18
  # Config file names
19
19
  PYPROJECT_TOML = "pyproject.toml"
20
+ USECLI_TOML = "usecli.toml"
@@ -12,6 +12,7 @@ from pathlib import Path
12
12
  from typing import Any
13
13
 
14
14
  from usecli.cli.core.exceptions.config import UsecliConfigError
15
+ from usecli.shared.config.globals import PYPROJECT_TOML, USECLI_TOML
15
16
 
16
17
  if sys.version_info >= (3, 11):
17
18
  import tomllib
@@ -87,6 +88,7 @@ class ConfigManager:
87
88
  def __init__(
88
89
  self,
89
90
  pyproject_path: Path | None = None,
91
+ usecli_toml_path: Path | None = None,
90
92
  start_dir: Path | None = None,
91
93
  ) -> None:
92
94
  """Initialize the configuration manager.
@@ -102,12 +104,21 @@ class ConfigManager:
102
104
 
103
105
  if pyproject_path is None:
104
106
  pyproject_path = self._find_pyproject_toml(start_dir) or (
105
- start_dir / "pyproject.toml"
107
+ start_dir / PYPROJECT_TOML
108
+ )
109
+
110
+ if usecli_toml_path is None:
111
+ usecli_toml_path = self._find_usecli_toml(start_dir) or (
112
+ start_dir / USECLI_TOML
106
113
  )
107
114
 
108
115
  self.pyproject_path: Path = pyproject_path
116
+ self.usecli_toml_path: Path = usecli_toml_path
109
117
  self.start_dir: Path = start_dir
110
- self.project_root: Path = find_project_root(start_dir) or start_dir.resolve()
118
+ detected_root = find_project_root(start_dir)
119
+ if detected_root is None and self.usecli_toml_path.exists():
120
+ detected_root = self.usecli_toml_path.parent
121
+ self.project_root: Path = (detected_root or start_dir).resolve()
111
122
  self._config: dict[str, Any] = {}
112
123
  self._overrides: dict[str, Any] = {}
113
124
  self._load_config()
@@ -117,18 +128,32 @@ class ConfigManager:
117
128
  self._config = self.DEFAULT_CONFIG.copy()
118
129
  self._overrides = {}
119
130
 
131
+ loaded = False
120
132
  if self.pyproject_path.exists():
121
133
  try:
122
134
  pyproject_config = self._load_pyproject_toml(self.pyproject_path)
123
135
  if pyproject_config:
124
136
  self._config = _deep_merge(self._config, pyproject_config)
125
137
  self._overrides = _deep_merge(self._overrides, pyproject_config)
138
+ loaded = True
126
139
  except (tomllib.TOMLDecodeError, OSError) as e:
127
140
  raise UsecliConfigError(
128
141
  f"Failed to load pyproject.toml: {e}",
129
142
  config_file=str(self.pyproject_path),
130
143
  ) from e
131
144
 
145
+ if not loaded and self.usecli_toml_path.exists():
146
+ try:
147
+ usecli_config = self._load_usecli_toml(self.usecli_toml_path)
148
+ if usecli_config:
149
+ self._config = _deep_merge(self._config, usecli_config)
150
+ self._overrides = _deep_merge(self._overrides, usecli_config)
151
+ except (tomllib.TOMLDecodeError, OSError) as e:
152
+ raise UsecliConfigError(
153
+ f"Failed to load usecli.toml: {e}",
154
+ config_file=str(self.usecli_toml_path),
155
+ ) from e
156
+
132
157
  default_themes = _normalize_themes_dir(self.DEFAULT_CONFIG.get("themes_dir"))
133
158
  override_themes = _normalize_themes_dir(self._overrides.get("themes_dir"))
134
159
  merged_themes = _dedupe_items(default_themes + override_themes)
@@ -151,7 +176,7 @@ class ConfigManager:
151
176
  current = start_dir.resolve()
152
177
 
153
178
  while True:
154
- pyproject_path = current / "pyproject.toml"
179
+ pyproject_path = current / PYPROJECT_TOML
155
180
  if pyproject_path.exists():
156
181
  return pyproject_path
157
182
 
@@ -162,6 +187,38 @@ class ConfigManager:
162
187
 
163
188
  return None
164
189
 
190
+ @classmethod
191
+ def _find_usecli_toml(cls, start_dir: Path) -> Path | None:
192
+ current = start_dir.resolve()
193
+
194
+ while True:
195
+ config_path = current / USECLI_TOML
196
+ if config_path.exists():
197
+ return config_path
198
+
199
+ parent = current.parent
200
+ if parent == current:
201
+ break
202
+ current = parent
203
+
204
+ return cls._find_usecli_toml_on_sys_path()
205
+
206
+ @staticmethod
207
+ def _find_usecli_toml_on_sys_path() -> Path | None:
208
+ for entry in sys.path:
209
+ if not entry:
210
+ continue
211
+ path = Path(entry)
212
+ if not path.exists() or not path.is_dir():
213
+ continue
214
+ candidate = path / USECLI_TOML
215
+ if candidate.exists():
216
+ return candidate
217
+ for child in path.glob(f"*/{USECLI_TOML}"):
218
+ if child.exists():
219
+ return child
220
+ return None
221
+
165
222
  @staticmethod
166
223
  def _load_pyproject_toml(path: Path) -> dict[str, Any]:
167
224
  """Load pyproject.toml and return [tool.usecli] section.
@@ -176,6 +233,23 @@ class ConfigManager:
176
233
  data = tomllib.load(f)
177
234
  return data.get("tool", {}).get("usecli", {})
178
235
 
236
+ @staticmethod
237
+ def _load_usecli_toml(path: Path) -> dict[str, Any]:
238
+ with open(path, "rb") as f:
239
+ data = tomllib.load(f)
240
+
241
+ tool_section = data.get("tool", {})
242
+ if isinstance(tool_section, dict):
243
+ usecli_section = tool_section.get("usecli", {})
244
+ if isinstance(usecli_section, dict):
245
+ return usecli_section
246
+
247
+ usecli_section = data.get("usecli", {})
248
+ if isinstance(usecli_section, dict):
249
+ return usecli_section
250
+
251
+ return {}
252
+
179
253
  def get(self, key: str, default: Any = None) -> Any:
180
254
  """Get a configuration value using dot notation.
181
255
 
@@ -256,9 +330,13 @@ class ConfigManager:
256
330
  @property
257
331
  def pyproject_exists(self) -> bool:
258
332
  """Check if pyproject.toml with [tool.usecli] exists."""
259
- if not self.pyproject_path.exists():
260
- return False
261
- return self._pyproject_has_usecli(self.pyproject_path)
333
+ if self.pyproject_path.exists() and self._pyproject_has_usecli(
334
+ self.pyproject_path
335
+ ):
336
+ return True
337
+ if self.usecli_toml_path.exists():
338
+ return True
339
+ return False
262
340
 
263
341
  @staticmethod
264
342
  def _load_project_version(path: Path) -> str | None:
@@ -305,10 +383,14 @@ def find_project_root(start_dir: Path | None = None) -> Path | None:
305
383
  current = start_dir.resolve()
306
384
 
307
385
  while True:
308
- pyproject_path = current / "pyproject.toml"
386
+ pyproject_path = current / PYPROJECT_TOML
309
387
  if pyproject_path.exists():
310
388
  return current
311
389
 
390
+ usecli_path = current / USECLI_TOML
391
+ if usecli_path.exists():
392
+ return current
393
+
312
394
  git_dir = current / ".git"
313
395
  if git_dir.exists():
314
396
  return current
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes