usecli 0.1.69__tar.gz → 0.1.71__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.69 → usecli-0.1.71}/PKG-INFO +1 -1
  2. {usecli-0.1.69 → usecli-0.1.71}/pyproject.toml +1 -1
  3. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/__init__.py +26 -0
  4. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/make/make_command.py +12 -3
  5. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/base_command.py +5 -1
  6. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/ui/list.py +104 -27
  7. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/services/command_service.py +7 -0
  8. {usecli-0.1.69 → usecli-0.1.71}/LICENSE +0 -0
  9. {usecli-0.1.69 → usecli-0.1.71}/README.md +0 -0
  10. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/__init__.py +0 -0
  11. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/README.md +0 -0
  12. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/__init__.py +0 -0
  13. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/custom/README.md +0 -0
  14. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/custom/__init__.py +0 -0
  15. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/__init__.py +0 -0
  16. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
  17. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
  18. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
  19. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
  20. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
  21. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
  22. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
  23. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
  24. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
  25. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
  26. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/commands/init_command.py +0 -0
  27. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/config/__init__.py +0 -0
  28. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/config/colors.py +0 -0
  29. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/__init__.py +0 -0
  30. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/error/__init__.py +0 -0
  31. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/error/handler.py +0 -0
  32. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/error/utils.py +0 -0
  33. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/exceptions/__init__.py +0 -0
  34. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/exceptions/base.py +0 -0
  35. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/exceptions/config.py +0 -0
  36. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/exceptions/usage.py +0 -0
  37. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/exceptions/validation.py +0 -0
  38. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/ui/__init__.py +0 -0
  39. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/ui/title.py +0 -0
  40. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/ui/title.txt +0 -0
  41. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/validators/__init__.py +0 -0
  42. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/validators/network.py +0 -0
  43. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/validators/numeric.py +0 -0
  44. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/validators/path.py +0 -0
  45. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/core/validators/string.py +0 -0
  46. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/services/__init__.py +0 -0
  47. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/templates/command.py.j2 +0 -0
  48. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/templates/theme.toml.j2 +0 -0
  49. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
  50. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/themes/ayu_dark.toml +0 -0
  51. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
  52. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
  53. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
  54. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
  55. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/themes/default.toml +0 -0
  56. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/themes/dracula.toml +0 -0
  57. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
  58. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/themes/nord.toml +0 -0
  59. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/themes/tokyo_night.toml +0 -0
  60. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/utils/__init__.py +0 -0
  61. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/utils/interactive/__init__.py +0 -0
  62. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
  63. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/menu.py +0 -0
  64. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/params.py +0 -0
  65. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/shared/__init__.py +0 -0
  66. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/shared/config/__init__.py +0 -0
  67. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/shared/config/globals.py +0 -0
  68. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/shared/config/manager.py +0 -0
  69. {usecli-0.1.69 → usecli-0.1.71}/src/usecli/ui.py +0 -0
  70. {usecli-0.1.69 → usecli-0.1.71}/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.69
3
+ Version: 0.1.71
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.69"
3
+ version = "0.1.71"
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" }]
@@ -331,6 +331,32 @@ class _FilteredListCommand:
331
331
  def __init__(self, prefix_filter: str) -> None:
332
332
  self.prefix_filter = prefix_filter
333
333
  self.name = "filtered-list"
334
+ self.allow_extra_args = True
335
+ self.allow_interspersed_args = True
336
+ self.ignore_unknown_options = True
337
+ self.hidden = False
338
+ self.deprecated = False
339
+ self.params = []
340
+ self.callback = None
341
+ self.help = None
342
+ self.epilog = None
343
+ self.short_help = None
344
+ self.no_args_is_help = False
345
+ self.context_settings = {}
346
+
347
+ def get_short_help_str(self, limit=45):
348
+ return ""
349
+
350
+ def make_context(self, info_name: str, args: list[str], **kwargs: object):
351
+ from click import Context
352
+
353
+ ctx = Context(self, info_name=info_name, **kwargs) # type: ignore[arg-type]
354
+ return ctx
355
+
356
+ def invoke(self, ctx):
357
+ from usecli.cli.core.ui.list import list_commands
358
+
359
+ list_commands(_get_app(), prefix_filter=self.prefix_filter)
334
360
 
335
361
  def __call__(self, *args, **kwargs):
336
362
  from usecli.cli.core.ui.list import list_commands
@@ -43,9 +43,18 @@ class MakeCommand(BaseCommand):
43
43
  name: The name of the command to create.
44
44
  """
45
45
  clean_name = name.replace("Command", "").replace("command", "")
46
- class_name = pascalcase(clean_name.replace(":", "_")) + "Command"
47
- command_name = snakecase(clean_name.replace(":", "_"))
48
- file_name = f"{snakecase(clean_name.replace(':', '_'))}_command.py"
46
+ class_name = (
47
+ pascalcase(clean_name.replace(":", "_").replace(" ", "_")) + "Command"
48
+ )
49
+ if ":" in clean_name:
50
+ command_name = clean_name
51
+ elif " " in clean_name:
52
+ command_name = clean_name
53
+ else:
54
+ command_name = snakecase(clean_name)
55
+ file_name = (
56
+ f"{snakecase(clean_name.replace(':', '_').replace(' ', '_'))}_command.py"
57
+ )
49
58
 
50
59
  config = get_config()
51
60
  current_root = find_project_root(Path.cwd()) or Path.cwd().resolve()
@@ -42,6 +42,7 @@ class NestedCommandRegistry:
42
42
  _instance: ClassVar[NestedCommandRegistry | None] = None
43
43
  _groups: dict[str, typer.Typer]
44
44
  _group_commands: dict[str, list[dict[str, Any]]]
45
+ _main_app: typer.Typer | None
45
46
 
46
47
  def __new__(cls) -> NestedCommandRegistry:
47
48
  """Ensure singleton pattern for the registry."""
@@ -49,6 +50,7 @@ class NestedCommandRegistry:
49
50
  cls._instance = super().__new__(cls)
50
51
  cls._instance._groups = {}
51
52
  cls._instance._group_commands = {}
53
+ cls._instance._main_app = None
52
54
  return cls._instance
53
55
 
54
56
  def get_or_create_group(
@@ -69,9 +71,11 @@ class NestedCommandRegistry:
69
71
  help=f"Commands for {group_name}",
70
72
  invoke_without_command=True,
71
73
  no_args_is_help=False,
74
+ add_completion=False,
72
75
  )
73
76
  self._groups[group_name] = group_app
74
77
  self._group_commands[group_name] = []
78
+ self._main_app = main_app
75
79
 
76
80
  # Add the group app to main app
77
81
  main_app.add_typer(group_app, name=group_name)
@@ -141,7 +145,7 @@ class NestedCommandRegistry:
141
145
 
142
146
  group_app = self._groups.get(group_name)
143
147
  if group_app:
144
- list_group_commands(group_app, group_name)
148
+ list_group_commands(group_app, group_name, self._main_app)
145
149
 
146
150
  def register_command(
147
151
  self,
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, TypedDict
7
7
  import click
8
8
  import typer
9
9
  from rich.console import Console
10
+ from rich.markup import escape
10
11
 
11
12
  from usecli.cli.config.colors import COLOR
12
13
  from usecli.cli.core.ui.title import (
@@ -57,7 +58,8 @@ def list_commands(app: typer.Typer, prefix_filter: str | None = None) -> None:
57
58
  prefix_filter: Optional prefix to filter commands by name.
58
59
  """
59
60
  project_name = get_project_name()
60
- print_title(title=project_name)
61
+ if not prefix_filter:
62
+ print_title(title=project_name)
61
63
 
62
64
  click_group = typer.main.get_command(app)
63
65
 
@@ -122,7 +124,6 @@ def list_commands(app: typer.Typer, prefix_filter: str | None = None) -> None:
122
124
  option_flags.append(flags)
123
125
 
124
126
  help_flags = "--help, -h"
125
- all_option_flags = [help_flags] + option_flags
126
127
  group_entries: list[CommandEntry] = []
127
128
  for group_name, group_help in groups.items():
128
129
  aliases = group_alias_registry.get(group_name, list[str]())
@@ -158,31 +159,73 @@ def list_commands(app: typer.Typer, prefix_filter: str | None = None) -> None:
158
159
  all_display_names = [cmd["display_name"] for cmd in commands] + [
159
160
  entry["display_name"] for entry in group_entries
160
161
  ]
161
- all_labels = all_display_names + all_option_flags
162
+ all_labels = all_display_names + [help_flags]
162
163
  longest_label_length = max((len(label) for label in all_labels), default=0)
163
164
 
164
- console.print(f"[bold {COLOR.SECONDARY}]Usage:[/bold {COLOR.SECONDARY}]")
165
- console.print(f" [{COLOR.PRIMARY}]{command_name} [OPTIONS] [ARGUMENTS]")
166
- console.print()
165
+ if prefix_filter:
166
+ command_name = get_script_command_name(default="usecli")
167
+ interactive_flags = "--interactive, -i"
168
+ filtered_labels = (
169
+ [cmd["display_name"] for cmd in commands]
170
+ + [entry["display_name"] for entry in group_entries]
171
+ + [help_flags, interactive_flags]
172
+ )
173
+ longest_label_length = max((len(label) for label in filtered_labels), default=0)
174
+ console.print()
175
+ console.print(f"[bold {COLOR.SECONDARY}]Usage:[/bold {COLOR.SECONDARY}]")
176
+ # Detect colon-separated commands (e.g., "make:command" matching prefix "make")
177
+ has_colon_commands = any(
178
+ ":" in cmd["name"] and cmd["name"].startswith(prefix_filter + ":")
179
+ for cmd in commands
180
+ )
181
+ if has_colon_commands:
182
+ # Colon-separated: use make:[COMMAND] [OPTIONS]
183
+ console.print(
184
+ f" [not bold {COLOR.PRIMARY}]{command_name} {prefix_filter}:{escape('[COMMAND]')} {escape('[OPTIONS]')}[/]"
185
+ )
186
+ else:
187
+ # Space-separated nested groups: use make [COMMAND] [OPTIONS]
188
+ console.print(
189
+ f" [not bold {COLOR.PRIMARY}]{command_name} {prefix_filter} {escape('[COMMAND]')} {escape('[OPTIONS]')}[/]"
190
+ )
191
+ console.print()
192
+ console.print(f"[bold {COLOR.SECONDARY}]Options:")
193
+ help_padding = " " * (longest_label_length - len(help_flags) + SPACER_LENGTH)
194
+ console.print(
195
+ f" [{COLOR.OPTION}]{help_flags}[/{COLOR.OPTION}]{help_padding}Show this message and exit."
196
+ )
197
+ interactive_padding = " " * (
198
+ longest_label_length - len(interactive_flags) + SPACER_LENGTH
199
+ )
200
+ console.print(
201
+ f" [{COLOR.OPTION}]{interactive_flags}[/{COLOR.OPTION}]{interactive_padding}Run in interactive mode."
202
+ )
203
+ console.print()
204
+ else:
205
+ console.print(f"[bold {COLOR.SECONDARY}]Usage:[/bold {COLOR.SECONDARY}]")
206
+ console.print(
207
+ f" [not bold {COLOR.PRIMARY}]{command_name} {escape('[COMMAND]')} {escape('[OPTIONS]')} {escape('[ARGUMENTS]')}[/]"
208
+ )
209
+ console.print()
167
210
 
168
- console.print(f"[bold {COLOR.SECONDARY}]Options:")
211
+ console.print(f"[bold {COLOR.SECONDARY}]Options:")
169
212
 
170
- help_padding = " " * (longest_label_length - len(help_flags) + SPACER_LENGTH)
171
- console.print(
172
- f" [{COLOR.OPTION}]{help_flags}[/{COLOR.OPTION}]{help_padding}Show this message and exit."
173
- )
213
+ help_padding = " " * (longest_label_length - len(help_flags) + SPACER_LENGTH)
214
+ console.print(
215
+ f" [{COLOR.OPTION}]{help_flags}[/{COLOR.OPTION}]{help_padding}Show this message and exit."
216
+ )
174
217
 
175
- if display_params:
176
- for param in display_params:
177
- flags = ", ".join(param.opts)
178
- if "--help" in flags:
179
- continue
180
- description = _get_option_description(param)
181
- padding = " " * (longest_label_length - len(flags) + SPACER_LENGTH)
182
- console.print(
183
- f" [{COLOR.OPTION}]{flags}[/{COLOR.OPTION}]{padding}{description}"
184
- )
185
- console.print()
218
+ if display_params:
219
+ for param in display_params:
220
+ flags = ", ".join(param.opts)
221
+ if "--help" in flags:
222
+ continue
223
+ description = _get_option_description(param)
224
+ padding = " " * (longest_label_length - len(flags) + SPACER_LENGTH)
225
+ console.print(
226
+ f" [{COLOR.OPTION}]{flags}[/{COLOR.OPTION}]{padding}{description}"
227
+ )
228
+ console.print()
186
229
 
187
230
  if not prefix_filter:
188
231
  console.print(f"[bold {COLOR.SECONDARY}]Available commands:")
@@ -224,7 +267,9 @@ def list_commands(app: typer.Typer, prefix_filter: str | None = None) -> None:
224
267
  console.print()
225
268
 
226
269
 
227
- def list_group_commands(group_app: typer.Typer, group_name: str) -> None:
270
+ def list_group_commands(
271
+ group_app: typer.Typer, group_name: str, main_app: typer.Typer | None = None
272
+ ) -> None:
228
273
  """List all commands within a specific command group.
229
274
 
230
275
  Displays commands in a formatted list for a nested command group,
@@ -233,16 +278,31 @@ def list_group_commands(group_app: typer.Typer, group_name: str) -> None:
233
278
  Args:
234
279
  group_app: The Typer sub-app for the command group.
235
280
  group_name: The name of the command group.
281
+ main_app: Optional main app to include colon-separated commands.
236
282
  """
237
283
  alias_registry = _get_alias_registry(group_app)
238
284
  alias_to_primary = _build_alias_to_primary(alias_registry)
239
285
 
240
286
  command_name = get_script_command_name(default="usecli")
241
287
 
242
- console.print(f"[bold {COLOR.SECONDARY}]Usage:[/bold {COLOR.SECONDARY}]")
243
- console.print(
244
- f" [{COLOR.PRIMARY}]{command_name} {group_name} [COMMAND] [OPTIONS][/]"
288
+ # Check if group has both colon-separated and space-separated commands
289
+ has_colon_commands = main_app is not None and any(
290
+ cmd.name and cmd.name.startswith(f"{group_name}:")
291
+ for cmd in (main_app.registered_commands or [])
245
292
  )
293
+ has_nested_commands = len(group_app.registered_commands) > 0
294
+
295
+ console.print(f"[bold {COLOR.SECONDARY}]Usage:[/bold {COLOR.SECONDARY}]")
296
+ if has_colon_commands and has_nested_commands:
297
+ # Both formats exist: usecli [COMMAND] [OPTIONS] [ARGUMENTS]
298
+ console.print(
299
+ f" [not bold {COLOR.PRIMARY}]{command_name} {escape('[COMMAND]')} {escape('[OPTIONS]')} {escape('[ARGUMENTS]')}[/]"
300
+ )
301
+ else:
302
+ # Only space-separated: usecli make [COMMAND] [OPTIONS]
303
+ console.print(
304
+ f" [not bold {COLOR.PRIMARY}]{command_name} {group_name} {escape('[COMMAND]')} {escape('[OPTIONS]')}[/]"
305
+ )
246
306
  console.print()
247
307
  commands_by_name: dict[str, CommandMeta] = {}
248
308
  for command in group_app.registered_commands:
@@ -261,6 +321,23 @@ def list_group_commands(group_app: typer.Typer, group_name: str) -> None:
261
321
  "aliases": alias_registry.get(name, list[str]()),
262
322
  }
263
323
 
324
+ # Include colon-separated commands from main app with matching prefix
325
+ if main_app is not None:
326
+ prefix = f"{group_name}:"
327
+ main_alias_registry = _get_alias_registry(main_app)
328
+ for command in main_app.registered_commands:
329
+ cmd_name = command.name or ""
330
+ if not cmd_name.startswith(prefix):
331
+ continue
332
+ if cmd_name in commands_by_name:
333
+ continue
334
+ help_text = command.help or ""
335
+ aliases = main_alias_registry.get(cmd_name, [])
336
+ commands_by_name[cmd_name] = {
337
+ "help": help_text,
338
+ "aliases": aliases,
339
+ }
340
+
264
341
  commands: list[CommandEntry] = []
265
342
  for name in commands_by_name:
266
343
  aliases = commands_by_name[name]["aliases"]
@@ -313,7 +390,7 @@ def list_group_commands(group_app: typer.Typer, group_name: str) -> None:
313
390
  display_name = cmd["display_name"]
314
391
  padding = " " * (longest_label_length - len(display_name) + SPACER_LENGTH)
315
392
  console.print(
316
- f" [{COLOR.COMMAND}]{display_name}[/{COLOR.COMMAND}]{padding}{cmd['help']}"
393
+ f" [{COLOR.COMMAND} not bold]{display_name}[/{COLOR.COMMAND} not bold]{padding}{cmd['help']}"
317
394
  )
318
395
 
319
396
  console.print()
@@ -177,6 +177,13 @@ class CommandService:
177
177
  if spec and spec.loader:
178
178
  module = importlib.util.module_from_spec(spec)
179
179
  sys.modules[module_name] = module
180
+
181
+ from usecli.shared.config.manager import find_project_root
182
+
183
+ project_root = find_project_root(path.parent)
184
+ if project_root and str(project_root) not in sys.path:
185
+ sys.path.insert(0, str(project_root))
186
+
180
187
  spec.loader.exec_module(module)
181
188
  return module
182
189
  return None
File without changes
File without changes
File without changes
File without changes
File without changes