usecli 0.1.58__tar.gz → 0.1.59__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.58 → usecli-0.1.59}/PKG-INFO +4 -13
  2. {usecli-0.1.58 → usecli-0.1.59}/README.md +3 -12
  3. {usecli-0.1.58 → usecli-0.1.59}/pyproject.toml +1 -1
  4. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/make/make_command.py +8 -4
  5. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/make/make_theme_command.py +7 -3
  6. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/init_command.py +31 -6
  7. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -3
  8. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/shared/config/manager.py +46 -5
  9. {usecli-0.1.58 → usecli-0.1.59}/LICENSE +0 -0
  10. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/__init__.py +0 -0
  11. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/__init__.py +0 -0
  12. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/README.md +0 -0
  13. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/__init__.py +0 -0
  14. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/custom/README.md +0 -0
  15. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/custom/__init__.py +0 -0
  16. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/__init__.py +0 -0
  17. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
  18. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
  19. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
  20. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
  21. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
  22. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
  23. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
  24. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
  25. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
  26. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/config/__init__.py +0 -0
  27. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/config/colors.py +0 -0
  28. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/__init__.py +0 -0
  29. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/base_command.py +0 -0
  30. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/error/__init__.py +0 -0
  31. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/error/handler.py +0 -0
  32. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/error/utils.py +0 -0
  33. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/exceptions/__init__.py +0 -0
  34. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/exceptions/base.py +0 -0
  35. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/exceptions/config.py +0 -0
  36. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/exceptions/usage.py +0 -0
  37. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/exceptions/validation.py +0 -0
  38. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/ui/__init__.py +0 -0
  39. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/ui/list.py +0 -0
  40. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/ui/title.py +0 -0
  41. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/ui/title.txt +0 -0
  42. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/validators/__init__.py +0 -0
  43. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/validators/network.py +0 -0
  44. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/validators/numeric.py +0 -0
  45. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/validators/path.py +0 -0
  46. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/core/validators/string.py +0 -0
  47. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/services/__init__.py +0 -0
  48. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/services/command_service.py +0 -0
  49. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/templates/command.py.j2 +0 -0
  50. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/templates/theme.toml.j2 +0 -0
  51. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/themes/ayu_dark.toml +0 -0
  52. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
  53. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
  54. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
  55. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
  56. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/themes/default.toml +0 -0
  57. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/themes/dracula.toml +0 -0
  58. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
  59. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/themes/nord.toml +0 -0
  60. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/themes/tokyo_night.toml +0 -0
  61. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/utils/__init__.py +0 -0
  62. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/utils/interactive/__init__.py +0 -0
  63. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
  64. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/menu.py +0 -0
  65. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/params.py +0 -0
  66. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/shared/__init__.py +0 -0
  67. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/shared/config/__init__.py +0 -0
  68. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/shared/config/globals.py +0 -0
  69. {usecli-0.1.58 → usecli-0.1.59}/src/usecli/ui.py +0 -0
  70. {usecli-0.1.58 → usecli-0.1.59}/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.58
3
+ Version: 0.1.59
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>
@@ -182,19 +182,10 @@ choice = Menu(["A", "B", "C"]).show()
182
182
  ```
183
183
  about Show app info
184
184
  help Show help
185
- init Initialize usecli
185
+ init Initialize usecli (usecli only)
186
186
  inspire Random quote
187
- make:command Create new command
188
- ```
189
-
190
- ### Hiding Built-in Commands
191
-
192
- Add this to `usecli.config.toml`:
193
-
194
- ```toml
195
- [usecli]
196
- hide_init = true
197
- hide_inspire = true
187
+ make:command Create new command (usecli only)
188
+ make:theme Create new theme (usecli only)
198
189
  ```
199
190
 
200
191
  <p align="right">(<a href="#readme-top">back to top</a>)</p>
@@ -154,19 +154,10 @@ choice = Menu(["A", "B", "C"]).show()
154
154
  ```
155
155
  about Show app info
156
156
  help Show help
157
- init Initialize usecli
157
+ init Initialize usecli (usecli only)
158
158
  inspire Random quote
159
- make:command Create new command
160
- ```
161
-
162
- ### Hiding Built-in Commands
163
-
164
- Add this to `usecli.config.toml`:
165
-
166
- ```toml
167
- [usecli]
168
- hide_init = true
169
- hide_inspire = true
159
+ make:command Create new command (usecli only)
160
+ make:theme Create new theme (usecli only)
170
161
  ```
171
162
 
172
163
  <p align="right">(<a href="#readme-top">back to top</a>)</p>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usecli"
3
- version = "0.1.58"
3
+ version = "0.1.59"
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,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
6
+ import sys
5
7
  from pathlib import Path
6
8
 
7
9
  import typer
@@ -21,8 +23,8 @@ class MakeCommand(BaseCommand):
21
23
  """Command for generating new CLI command files."""
22
24
 
23
25
  def visible(self) -> bool:
24
- config = get_config()
25
- return not config.get("hide_make_command", False)
26
+ command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
27
+ return command_name == "usecli"
26
28
 
27
29
  def signature(self) -> str:
28
30
  """Return the command signature."""
@@ -50,7 +52,8 @@ class MakeCommand(BaseCommand):
50
52
  if config.get_project_root().resolve() != current_root:
51
53
  reset_config()
52
54
  config = get_config()
53
- commands_dir = config.get_project_commands_dir()
55
+ project_paths = config.get_project_paths()
56
+ commands_dir = project_paths["commands_dir"]
54
57
  commands_dir.mkdir(parents=True, exist_ok=True)
55
58
  target_file = commands_dir / file_name
56
59
 
@@ -59,7 +62,8 @@ class MakeCommand(BaseCommand):
59
62
  f"[{COLOR.ERROR}]Error: Command file {target_file} already exists.[/{COLOR.ERROR}]"
60
63
  )
61
64
  return
62
- project_template_path = config.get_project_templates_dir() / "command.py.j2"
65
+ templates_dir = project_paths["templates_dir"]
66
+ project_template_path = templates_dir / "command.py.j2"
63
67
  if project_template_path.exists():
64
68
  template_path = project_template_path
65
69
  else:
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
6
+ import sys
5
7
  from pathlib import Path
6
8
 
7
9
  import typer
@@ -23,8 +25,8 @@ console = Console()
23
25
 
24
26
  class MakeThemeCommand(BaseCommand):
25
27
  def visible(self) -> bool:
26
- config = get_config()
27
- return not config.get("hide_make_theme", False)
28
+ command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
29
+ return command_name == "usecli"
28
30
 
29
31
  def signature(self) -> str:
30
32
  return "make:theme"
@@ -46,6 +48,7 @@ class MakeThemeCommand(BaseCommand):
46
48
  reset_config()
47
49
  config = get_config()
48
50
 
51
+ project_paths = config.get_project_paths()
49
52
  themes_entries = self._normalize_theme_entries(config.get("themes_dir", []))
50
53
  if not themes_entries:
51
54
  console.print(
@@ -86,7 +89,8 @@ class MakeThemeCommand(BaseCommand):
86
89
  break
87
90
  counter += 1
88
91
 
89
- project_template_path = config.get_project_templates_dir() / "theme.toml.j2"
92
+ templates_dir = project_paths["templates_dir"]
93
+ project_template_path = templates_dir / "theme.toml.j2"
90
94
  if project_template_path.exists():
91
95
  template_path = project_template_path
92
96
  else:
@@ -29,15 +29,15 @@ from usecli.cli.core.exceptions import UsecliBadParameter
29
29
  from usecli.cli.core.validators import validate_command_name
30
30
  from usecli.cli.utils.interactive.terminal_menu import terminal_menu
31
31
  from usecli.shared.config.globals import TEMPLATES_DIR, THEMES_DIR, USECLI_CONFIG_TOML
32
- from usecli.shared.config.manager import ConfigManager, get_config
32
+ from usecli.shared.config.manager import ConfigManager
33
33
 
34
34
  console = Console()
35
35
 
36
36
 
37
37
  class InitCommand(BaseCommand):
38
38
  def visible(self) -> bool:
39
- config = get_config()
40
- return not config.get("hide_init", False)
39
+ command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
40
+ return command_name == "usecli"
41
41
 
42
42
  def signature(self) -> str:
43
43
  return "init"
@@ -244,6 +244,30 @@ class InitCommand(BaseCommand):
244
244
  return "themes"
245
245
  return str(parent / "themes")
246
246
 
247
+ def _infer_commands_dir(self, project_root: Path) -> str:
248
+ src_dir = project_root / "src"
249
+ if src_dir.exists() and src_dir.is_dir():
250
+ packages = [
251
+ d
252
+ for d in src_dir.iterdir()
253
+ if d.is_dir() and not d.name.startswith((".", "_"))
254
+ ]
255
+ if len(packages) == 1:
256
+ package_name = packages[0].name
257
+ return f"src/{package_name}/cli/commands"
258
+ pyproject_path = project_root / "pyproject.toml"
259
+ if pyproject_path.exists():
260
+ try:
261
+ data = tomllib.loads(pyproject_path.read_text())
262
+ project_name = data.get("project", {}).get("name")
263
+ if project_name:
264
+ package_name = project_name.replace("-", "_").replace(" ", "_")
265
+ if (project_root / package_name).is_dir():
266
+ return f"{package_name}/cli/commands"
267
+ except (tomllib.TOMLDecodeError, OSError):
268
+ pass
269
+ return "cli/commands"
270
+
247
271
  def _get_existing_usecli_script_name(self, pyproject_path: Path) -> str | None:
248
272
  if not pyproject_path.exists():
249
273
  return None
@@ -532,9 +556,7 @@ include = ["{root_package}*"]
532
556
  description: str = typer.Option(
533
557
  "A custom CLI tool", help="Description for your CLI"
534
558
  ),
535
- commands_dir: str = typer.Option(
536
- "cli/commands", help="Directory for custom commands"
537
- ),
559
+ commands_dir: str = typer.Option(None, help="Directory for custom commands"),
538
560
  command_name: Annotated[
539
561
  str,
540
562
  typer.Option(
@@ -554,6 +576,9 @@ include = ["{root_package}*"]
554
576
  project_root / "pyproject.toml"
555
577
  )
556
578
 
579
+ if commands_dir is None:
580
+ commands_dir = self._infer_commands_dir(project_root)
581
+
557
582
  console.print()
558
583
  existing_command_name = self._get_existing_usecli_script_name(pyproject_path)
559
584
  if existing_command_name and command_name == "usecli":
@@ -8,7 +8,4 @@ commands_dir = "{{ commands_dir | default('cli/commands') }}"
8
8
  templates_dir = "{{ templates_dir | default('cli/templates') }}"
9
9
  themes_dir = "{{ themes_dir | default('cli/themes') }}"
10
10
  theme = "{{ theme | default('default') }}"
11
- hide_init = {{ hide_init | default(false) | lower }}
12
11
  hide_inspire = {{ hide_inspire | default(false) | lower }}
13
- hide_make_command = {{ hide_make_command | default(false) | lower }}
14
- hide_make_theme = {{ hide_make_theme | default(false) | lower }}
@@ -83,9 +83,7 @@ class ConfigManager:
83
83
  "theme": "default",
84
84
  "environment": "prod",
85
85
  "command_name": "usecli",
86
- "hide_init": False,
87
86
  "hide_inspire": False,
88
- "hide_make_command": False,
89
87
  }
90
88
 
91
89
  def __init__(
@@ -627,26 +625,69 @@ class ConfigManager:
627
625
  commands_path = Path(commands_dir)
628
626
  if commands_path.is_absolute():
629
627
  return commands_path
630
- return (self.project_root / commands_path).resolve()
628
+ # Resolve relative to the config file's directory, not project_root.
629
+ # This ensures nested configs (e.g., src/mycli/cli/usecli.config.toml)
630
+ # resolve paths correctly relative to their location.
631
+ config_dir = self.usecli_config_path.parent
632
+ return (config_dir / commands_path).resolve()
631
633
 
632
634
  def get_project_templates_dir(self) -> Path:
633
635
  templates_dir = self.get("templates_dir", "cli/templates")
634
636
  templates_path = Path(templates_dir)
635
637
  if templates_path.is_absolute():
636
638
  return templates_path
637
- return (self.project_root / templates_path).resolve()
639
+ config_dir = self.usecli_config_path.parent
640
+ return (config_dir / templates_path).resolve()
638
641
 
639
642
  def get_project_themes_dirs(self) -> list[Path]:
640
643
  themes_dir = self.get("themes_dir", [])
641
644
  themes_entries = _normalize_themes_dir(themes_dir)
642
645
  result: list[Path] = []
646
+ config_dir = self.usecli_config_path.parent
643
647
  for entry in themes_entries:
644
648
  theme_path = Path(entry)
645
649
  if not theme_path.is_absolute():
646
- theme_path = self.project_root / theme_path
650
+ theme_path = config_dir / theme_path
647
651
  result.append(theme_path.resolve())
648
652
  return result
649
653
 
654
+ def get_project_paths(self) -> dict[str, Path]:
655
+ project_config = self._find_project_config()
656
+ if project_config is None:
657
+ return {
658
+ "commands_dir": self.get_project_commands_dir(),
659
+ "templates_dir": self.get_project_templates_dir(),
660
+ }
661
+ config_dir = project_config.parent
662
+ config_data = self._load_usecli_toml(project_config)
663
+ commands_dir = config_data.get("commands_dir", "cli/commands")
664
+ templates_dir = config_data.get("templates_dir", "cli/templates")
665
+ commands_path = Path(commands_dir)
666
+ templates_path = Path(templates_dir)
667
+ if not commands_path.is_absolute():
668
+ commands_path = config_dir / commands_path
669
+ if not templates_path.is_absolute():
670
+ templates_path = config_dir / templates_path
671
+ return {
672
+ "commands_dir": commands_path.resolve(),
673
+ "templates_dir": templates_path.resolve(),
674
+ }
675
+
676
+ def _find_project_config(self) -> Path | None:
677
+ start_dir = self.start_dir
678
+ project_root = find_project_root(start_dir)
679
+ if project_root is None:
680
+ return None
681
+ candidates = [
682
+ p
683
+ for p in project_root.rglob(USECLI_CONFIG_TOML)
684
+ if not any(part in self._SKIP_DIRS for part in p.parts)
685
+ ]
686
+ if not candidates:
687
+ return None
688
+ candidates.sort(key=lambda p: (len(p.parts), str(p)))
689
+ return candidates[0]
690
+
650
691
  def is_dev(self) -> bool:
651
692
  """Check if running in development environment."""
652
693
  return self.get("environment", "prod") == "dev"
File without changes
File without changes
File without changes
File without changes
File without changes