pkgs/clan: Add machine validator with suggestion logic
Add machine validator with suggestion logic to: - `clan machines update` - `clan machines delete` - `clan machines update-hardware-config`
This commit is contained in:
72
pkgs/clan-cli/clan_lib/machines/suggestions.py
Normal file
72
pkgs/clan-cli/clan_lib/machines/suggestions.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from clan_lib.errors import ClanError
|
||||
from clan_lib.flake import Flake
|
||||
|
||||
|
||||
def _levenshtein_distance(s1: str, s2: str) -> int:
|
||||
"""
|
||||
Calculate the Levenshtein distance between two strings.
|
||||
"""
|
||||
if len(s1) < len(s2):
|
||||
return _levenshtein_distance(s2, s1)
|
||||
|
||||
if len(s2) == 0:
|
||||
return len(s1)
|
||||
|
||||
previous_row = list(range(len(s2) + 1))
|
||||
for i, c1 in enumerate(s1):
|
||||
current_row = [i + 1]
|
||||
for j, c2 in enumerate(s2):
|
||||
insertions = previous_row[j + 1] + 1
|
||||
deletions = current_row[j] + 1
|
||||
substitutions = previous_row[j] + (c1 != c2)
|
||||
current_row.append(min(insertions, deletions, substitutions))
|
||||
previous_row = current_row
|
||||
|
||||
return previous_row[-1]
|
||||
|
||||
|
||||
def _suggest_similar_names(
|
||||
target: str, candidates: list[str], max_suggestions: int = 3
|
||||
) -> list[str]:
|
||||
if not candidates:
|
||||
return []
|
||||
|
||||
distances = [
|
||||
(candidate, _levenshtein_distance(target.lower(), candidate.lower()))
|
||||
for candidate in candidates
|
||||
]
|
||||
|
||||
distances.sort(key=lambda x: (x[1], x[0]))
|
||||
|
||||
return [candidate for candidate, _ in distances[:max_suggestions]]
|
||||
|
||||
|
||||
def get_available_machines(flake: Flake) -> list[str]:
|
||||
from clan_lib.machines.list import list_machines
|
||||
|
||||
machines = list_machines(flake)
|
||||
return list(machines.keys())
|
||||
|
||||
|
||||
def validate_machine_names(machine_names: list[str], flake: Flake) -> None:
|
||||
if not machine_names:
|
||||
return
|
||||
|
||||
available_machines = get_available_machines(flake)
|
||||
invalid_machines = [
|
||||
name for name in machine_names if name not in available_machines
|
||||
]
|
||||
|
||||
if invalid_machines:
|
||||
error_lines = []
|
||||
for machine_name in invalid_machines:
|
||||
suggestions = _suggest_similar_names(machine_name, available_machines)
|
||||
if suggestions:
|
||||
suggestion_text = f"Did you mean: {', '.join(suggestions)}?"
|
||||
else:
|
||||
suggestion_text = (
|
||||
f"Available machines: {', '.join(sorted(available_machines))}"
|
||||
)
|
||||
error_lines.append(f"Machine '{machine_name}' not found. {suggestion_text}")
|
||||
|
||||
raise ClanError("\n".join(error_lines))
|
||||
Reference in New Issue
Block a user