iter(g149): 补 ua search 关键词检索
按 id / 标题 / props 名 / 步骤四档打分排序,支持 --json 结构化输出。 ua info 与 search 未命中时给出编辑距离 ≤3 的相近 id,拼错也能自己纠正。 usage 与 CLI 清单、README、AGENTS 的命令说明同步。
This commit is contained in:
@@ -146,6 +146,62 @@ function formatComponent(pkg, comp) {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/*** 编辑距离(截断版):只用来给拼错的名字找相近候选,超过 3 直接当无建议 ***/
|
||||
function editDistance(a, b) {
|
||||
const prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
||||
for (let i = 1; i <= a.length; i++) {
|
||||
let diag = prev[0];
|
||||
prev[0] = i;
|
||||
for (let j = 1; j <= b.length; j++) {
|
||||
const tmp = prev[j];
|
||||
prev[j] = Math.min(prev[j] + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
|
||||
diag = tmp;
|
||||
}
|
||||
}
|
||||
return prev[b.length];
|
||||
}
|
||||
|
||||
/*** 未命中时给相近组件 id(编辑距离 ≤ 3),避免只回一句 not found ***/
|
||||
function nearIds(name) {
|
||||
return collectComponents()
|
||||
.map((x) => [x.comp.id, editDistance(String(name).toLowerCase(), x.comp.id.toLowerCase())])
|
||||
.filter(([, d]) => d <= 3)
|
||||
.sort((x, y) => x[1] - y[1])
|
||||
.slice(0, 3)
|
||||
.map(([id]) => id);
|
||||
}
|
||||
|
||||
/*** 按关键词搜组件:id 命中优先于 title,其次 props 名与步骤文案;--json 输出结构化结果 ***/
|
||||
function cmdSearch(keyword = '', args = []) {
|
||||
const kw = String(keyword).toLowerCase().trim();
|
||||
if (!kw) return console.error('usage: ua search <关键词> [--json]');
|
||||
const hits = collectComponents()
|
||||
.map(({ pkg, comp }) => {
|
||||
const id = String(comp.id ?? '').toLowerCase();
|
||||
const title = String(comp.title ?? '').toLowerCase();
|
||||
const props = (comp.props ?? []).map((p) => String(p.n ?? '').toLowerCase()).join(' ');
|
||||
const steps = (comp.steps ?? []).join(' ').toLowerCase();
|
||||
let score = 0; // 分数只用于排序:id 命中 > 标题 > props 名 > 步骤
|
||||
if (id === kw) score = 4;
|
||||
else if (id.includes(kw)) score = 3;
|
||||
else if (title.includes(kw)) score = 2;
|
||||
else if (props.includes(kw)) score = 1;
|
||||
else if (steps.includes(kw)) score = 0.5;
|
||||
return score ? { pkg, comp, score } : null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => b.score - a.score || String(a.comp.id).localeCompare(String(b.comp.id)));
|
||||
if (!hits.length) {
|
||||
const near = nearIds(kw);
|
||||
return console.error(`no match: ${keyword}${near.length ? `(相近:${near.join(' / ')})` : ''}`);
|
||||
}
|
||||
if (args.includes('--json')) {
|
||||
return console.log(JSON.stringify(hits.map((h) => ({ package: h.pkg.name, ...h.comp })), null, 2));
|
||||
}
|
||||
for (const h of hits) console.log(`${String(h.comp.id).padEnd(16)} ${h.comp.title ?? ''} (${h.pkg.name})`);
|
||||
console.log(`\n共 ${hits.length} 个组件命中;查参数:ua info <组件id>`);
|
||||
}
|
||||
|
||||
/*** 输出单包或单组件信息:默认人读摘要,--json 输出原始 JSON ***/
|
||||
function cmdInfo(name, args = []) {
|
||||
const { packages } = loadIndex();
|
||||
@@ -159,8 +215,9 @@ function cmdInfo(name, args = []) {
|
||||
}
|
||||
const hit = collectComponents(packages).find((x) => x.comp.id === name);
|
||||
if (!hit) {
|
||||
console.error(`not found: ${name}(包名与组件 id 都没命中)`);
|
||||
process.exit(1);
|
||||
const near = nearIds(name);
|
||||
console.error(`not found: ${name}(包名与组件 id 都没命中)${near.length ? `;相近:${near.join(' / ')}` : ''}`);
|
||||
return process.exit(1);
|
||||
}
|
||||
console.log(asJson ? JSON.stringify({ package: hit.pkg.name, ...hit.comp }, null, 2) : formatComponent(hit.pkg, hit.comp));
|
||||
}
|
||||
@@ -1182,6 +1239,9 @@ switch (cmd) {
|
||||
case 'list':
|
||||
cmdList();
|
||||
break;
|
||||
case 'search':
|
||||
cmdSearch(args[0], args.slice(1));
|
||||
break;
|
||||
case 'info':
|
||||
if (!args[0]) {
|
||||
console.error('usage: ua info <包名|组件id> [--json]');
|
||||
@@ -1217,6 +1277,6 @@ switch (cmd) {
|
||||
cmdMcp();
|
||||
break;
|
||||
default:
|
||||
console.log('usage: ua <index|list|info <name>|docs|tokens [--json]|style|sync-counts|verify|commit-lint|lint-iterations|changelog|mcp>');
|
||||
console.log('usage: ua <index|list|search <kw>|info <name>|docs|tokens [--json]|style|sync-counts|verify|commit-lint|lint-iterations|changelog|mcp>');
|
||||
process.exit(cmd ? 1 : 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user