iter(g153): ua search 支持多关键词
多词按「最弱命中」做 AND 过滤,排序仍按 id > 标题 > props > 步骤。 未命中输出相近建议并以 1 退出,脚本能直接判断成败。
This commit is contained in:
@@ -1580,6 +1580,14 @@
|
|||||||
"acceptance": "ua list 每行带上组件数;ua list --components 按包分组列出 id / 标题 / 演示锚点并给出总数与后续命令提示,原有无参输出保持可用。",
|
"acceptance": "ua list 每行带上组件数;ua list --components 按包分组列出 id / 标题 / 演示锚点并给出总数与后续命令提示,原有无参输出保持可用。",
|
||||||
"notes": "",
|
"notes": "",
|
||||||
"status": "done"
|
"status": "done"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g153",
|
||||||
|
"title": "ua search 支持多关键词与脚本退出码",
|
||||||
|
"motivation": "单一关键词只能粗筛:想找「日期区间」时搜日期会连 calendar/timeline 一起返回;且未命中仍退 0,脚本里判断不了。",
|
||||||
|
"acceptance": "ua search a b 取各词命中的最弱档做 AND 过滤仍保持 id>标题>props>步骤 的排序;未命中输出相近建议并 exit 1;无参数打印用法。",
|
||||||
|
"notes": "",
|
||||||
|
"status": "open"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,29 +192,32 @@ function nearIds(name) {
|
|||||||
.map(([id]) => id);
|
.map(([id]) => id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 按关键词搜组件:id 命中优先于 title,其次 props 名与步骤文案;--json 输出结构化结果 ***/
|
/*** 按关键词搜组件:多词取最弱命中(AND 语义),id 命中优先于 title,其次 props 名与步骤文案;未命中退出码 1 ***/
|
||||||
function cmdSearch(keyword = '', args = []) {
|
function cmdSearch(words = [], args = []) {
|
||||||
const kw = String(keyword).toLowerCase().trim();
|
const kws = [...new Set(words.map((w) => String(w).toLowerCase().trim()).filter(Boolean))];
|
||||||
if (!kw) return console.error('usage: ua search <关键词> [--json]');
|
if (!kws.length) return console.error('usage: ua search <关键词...> [--json]');
|
||||||
const hits = collectComponents()
|
const scoreOf = (kw, comp) => {
|
||||||
.map(({ pkg, comp }) => {
|
|
||||||
const id = String(comp.id ?? '').toLowerCase();
|
const id = String(comp.id ?? '').toLowerCase();
|
||||||
const title = String(comp.title ?? '').toLowerCase();
|
const title = String(comp.title ?? '').toLowerCase();
|
||||||
const props = (comp.props ?? []).map((p) => String(p.n ?? '').toLowerCase()).join(' ');
|
const props = (comp.props ?? []).map((p) => String(p.n ?? '').toLowerCase()).join(' ');
|
||||||
const steps = (comp.steps ?? []).join(' ').toLowerCase();
|
const steps = (comp.steps ?? []).join(' ').toLowerCase();
|
||||||
let score = 0; // 分数只用于排序:id 命中 > 标题 > props 名 > 步骤
|
if (id === kw) return 4; // 分档只用于排序:id 全等 > id 含 > 标题 > props 名 > 步骤
|
||||||
if (id === kw) score = 4;
|
if (id.includes(kw)) return 3;
|
||||||
else if (id.includes(kw)) score = 3;
|
if (title.includes(kw)) return 2;
|
||||||
else if (title.includes(kw)) score = 2;
|
if (props.includes(kw)) return 1;
|
||||||
else if (props.includes(kw)) score = 1;
|
return steps.includes(kw) ? 0.5 : 0;
|
||||||
else if (steps.includes(kw)) score = 0.5;
|
};
|
||||||
return score ? { pkg, comp, score } : null;
|
const hits = collectComponents()
|
||||||
|
.map(({ pkg, comp }) => {
|
||||||
|
const scores = kws.map((kw) => scoreOf(kw, comp));
|
||||||
|
return scores.every((s) => s > 0) ? { pkg, comp, score: Math.min(...scores) } : null; // 任一词不中即淘汰
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.sort((a, b) => b.score - a.score || String(a.comp.id).localeCompare(String(b.comp.id)));
|
.sort((a, b) => b.score - a.score || String(a.comp.id).localeCompare(String(b.comp.id)));
|
||||||
if (!hits.length) {
|
if (!hits.length) {
|
||||||
const near = nearIds(kw);
|
const near = nearIds(kws.join(''));
|
||||||
return console.error(`no match: ${keyword}${near.length ? `(相近:${near.join(' / ')})` : ''}`);
|
console.error(`no match: ${words.join(' ')}${near.length ? `(相近:${near.join(' / ')})` : ''}`);
|
||||||
|
return process.exit(1);
|
||||||
}
|
}
|
||||||
if (args.includes('--json')) {
|
if (args.includes('--json')) {
|
||||||
return console.log(JSON.stringify(hits.map((h) => ({ package: h.pkg.name, ...h.comp })), null, 2));
|
return console.log(JSON.stringify(hits.map((h) => ({ package: h.pkg.name, ...h.comp })), null, 2));
|
||||||
@@ -1288,7 +1291,7 @@ switch (cmd) {
|
|||||||
cmdList(args);
|
cmdList(args);
|
||||||
break;
|
break;
|
||||||
case 'search':
|
case 'search':
|
||||||
cmdSearch(args[0], args.slice(1));
|
cmdSearch(args.filter((a) => !a.startsWith('--')), args);
|
||||||
break;
|
break;
|
||||||
case 'info':
|
case 'info':
|
||||||
if (!args[0]) {
|
if (!args[0]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user