import { die, run } from "./host-command.ts"; import type { SnapshotInfo } from "./types.ts"; export function resolveSnapshot(vmName: string, hint: string): SnapshotInfo { const output = run("prlctl", ["snapshot-list", vmName, "--json"], { quiet: true }).stdout; const payload = JSON.parse(output) as Record; let best: SnapshotInfo | null = null; let bestScore = -1; const aliases = (name: string): string[] => { const values = [name]; for (const pattern of [/^(.*)-poweroff$/, /^(.*)-poweroff-\d{4}-\d{2}-\d{2}$/]) { const match = name.match(pattern); if (match?.[1]) { values.push(match[1]); } } return values.flatMap((value) => { const withoutLatest = value.replace(/\s+latest$/u, "").trim(); return withoutLatest && withoutLatest !== value ? [value, withoutLatest] : [value]; }); }; const normalizedHint = hint.trim().toLowerCase(); const normalizedHints = [normalizedHint, normalizedHint.replace(/\s+latest$/u, "").trim()].filter( (value, index, values) => value && values.indexOf(value) === index, ); for (const [id, meta] of Object.entries(payload)) { const name = (meta.name ?? "").trim(); if (!name) { continue; } let score = 0; for (const hintAlias of normalizedHints) { for (const alias of aliases(name.toLowerCase())) { if (alias === hintAlias) { score = Math.max(score, 10); } else if (hintAlias && alias.includes(hintAlias)) { score = Math.max(score, 5 + hintAlias.length / Math.max(alias.length, 1)); } else { score = Math.max(score, stringSimilarity(hintAlias, alias)); } } } if ((meta.state ?? "").toLowerCase() === "poweroff") { score += 0.5; } if (score > bestScore) { bestScore = score; best = { id, name, state: (meta.state ?? "").trim() }; } } if (!best) { die("no snapshot matched"); } return best; } export function stringSimilarity(a: string, b: string): number { if (a === b) { return 1; } const rows = a.length + 1; const cols = b.length + 1; const matrix = Array.from({ length: rows }, () => Array(cols).fill(0)); for (let i = 0; i < rows; i++) { matrix[i][0] = i; } for (let j = 0; j < cols; j++) { matrix[0][j] = j; } for (let i = 1; i < rows; i++) { for (let j = 1; j < cols; j++) { matrix[i][j] = Math.min( matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1), ); } } const distance = matrix[a.length][b.length]; return 1 - distance / Math.max(a.length, b.length, 1); }