Skip to content

Commit e6bcff0

Browse files
authored
Merge pull request #19 from XpressAI/fahreza/eject-suggestions
✨ Eject Module Suggestions
2 parents 267a37c + 2cb9721 commit e6bcff0

File tree

1 file changed

+34
-14
lines changed

1 file changed

+34
-14
lines changed

src/xaibo/cli/__init__.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import subprocess, shlex, sys, os
66
import re
77
import shutil
8+
from difflib import get_close_matches
89

910
import questionary
1011

@@ -225,7 +226,7 @@ def resolve_item(arg: str):
225226
pkg, item = arg.split(".", 1)
226227
contents = list_module_contents(pkg)
227228
if item not in contents:
228-
raise FileNotFoundError(f"No item {item!r} in package {pkg!r}")
229+
raise FileNotFoundError(f"No module {item!r} in package {pkg!r}")
229230
return pkg, contents[item]
230231
else:
231232
# search every package for a matching item
@@ -235,10 +236,10 @@ def resolve_item(arg: str):
235236
if arg in contents:
236237
matches.append((pkg, contents[arg]))
237238
if not matches:
238-
raise FileNotFoundError(f"No item named {arg!r} in any package")
239+
raise FileNotFoundError(f"No module named {arg!r} in any package")
239240
if len(matches) > 1:
240241
pkgs = ", ".join(p for p,_ in matches)
241-
raise ValueError(f"Ambiguous item {arg!r} found in: {pkgs}")
242+
raise ValueError(f"Ambiguous module {arg!r} found in: {pkgs}")
242243
return matches[0]
243244

244245
def ensure_init_py(directory: Path):
@@ -432,7 +433,7 @@ async def test_example_agent():
432433

433434
def eject(args, extra_args=[]):
434435
"""
435-
Eject primitive modules into the current project.
436+
Eject primitive modules into the current project, with fuzzy suggestions on typos.
436437
"""
437438
# If user ran `eject list`, list everything and exit
438439
if args.action == 'list':
@@ -443,19 +444,38 @@ def eject(args, extra_args=[]):
443444
print(f" • {item}")
444445
return
445446

446-
# Otherwise, perform an eject (interactive or via -m)
447-
dest = Path(args.dest) if args.dest else Path.cwd()
447+
# Non-interactive mode: try to resolve each module name, suggest on typos
448448
if args.module:
449-
# Non-interactive: resolve each module and eject
450-
try:
451-
items_with_packages = [resolve_item(arg) for arg in args.module]
452-
eject_items(items_with_packages, dest)
453-
except (FileNotFoundError, ValueError) as e:
454-
print(f"Error: {e}")
455-
return
449+
dest = Path(args.dest) if args.dest else Path.cwd()
450+
451+
# Build a flat list of all valid names to match against
452+
available = []
453+
for pkg in list_top_level_packages():
454+
for item in list_module_contents(pkg).keys():
455+
available.append(item)
456+
available.append(f"{pkg}.{item}")
457+
458+
items_with_packages = []
459+
for arg in args.module:
460+
try:
461+
items_with_packages.append(resolve_item(arg))
462+
except FileNotFoundError:
463+
suggestions = get_close_matches(arg, available, n=3, cutoff=0.6)
464+
if suggestions:
465+
print(f"Error: No module named '{arg}' found. Did you mean: {', '.join(suggestions)}?")
466+
else:
467+
print(f"Error: No module named '{arg}' found.")
468+
return
469+
except ValueError as e:
470+
# Ambiguous without a clear pkg.item
471+
print(f"Error: {e}")
472+
return
473+
474+
eject_items(items_with_packages, dest)
475+
456476
else:
457477
# Interactive mode
458-
interactive_eject_mode(dest)
478+
interactive_eject_mode(Path.cwd())
459479

460480
def dev(args, extra_args=[]):
461481
"""

0 commit comments

Comments
 (0)