iter(g153): ua search 支持多关键词
多词按「最弱命中」做 AND 过滤,排序仍按 id > 标题 > props > 步骤。 未命中输出相近建议并以 1 退出,脚本能直接判断成败。
This commit is contained in:
@@ -192,29 +192,32 @@ function nearIds(name) {
|
||||
.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]');
|
||||
/*** 按关键词搜组件:多词取最弱命中(AND 语义),id 命中优先于 title,其次 props 名与步骤文案;未命中退出码 1 ***/
|
||||
function cmdSearch(words = [], args = []) {
|
||||
const kws = [...new Set(words.map((w) => String(w).toLowerCase().trim()).filter(Boolean))];
|
||||
if (!kws.length) return console.error('usage: ua search <关键词...> [--json]');
|
||||
const scoreOf = (kw, 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();
|
||||
if (id === kw) return 4; // 分档只用于排序:id 全等 > id 含 > 标题 > props 名 > 步骤
|
||||
if (id.includes(kw)) return 3;
|
||||
if (title.includes(kw)) return 2;
|
||||
if (props.includes(kw)) return 1;
|
||||
return steps.includes(kw) ? 0.5 : 0;
|
||||
};
|
||||
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;
|
||||
const scores = kws.map((kw) => scoreOf(kw, comp));
|
||||
return scores.every((s) => s > 0) ? { pkg, comp, score: Math.min(...scores) } : 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(' / ')})` : ''}`);
|
||||
const near = nearIds(kws.join(''));
|
||||
console.error(`no match: ${words.join(' ')}${near.length ? `(相近:${near.join(' / ')})` : ''}`);
|
||||
return process.exit(1);
|
||||
}
|
||||
if (args.includes('--json')) {
|
||||
return console.log(JSON.stringify(hits.map((h) => ({ package: h.pkg.name, ...h.comp })), null, 2));
|
||||
@@ -1288,7 +1291,7 @@ switch (cmd) {
|
||||
cmdList(args);
|
||||
break;
|
||||
case 'search':
|
||||
cmdSearch(args[0], args.slice(1));
|
||||
cmdSearch(args.filter((a) => !a.startsWith('--')), args);
|
||||
break;
|
||||
case 'info':
|
||||
if (!args[0]) {
|
||||
|
||||
Reference in New Issue
Block a user