Skip to content

Commit c706f87

Browse files
committed
fix-standard-track-exceptions: allow combining suggestions
1 parent b3df476 commit c706f87

1 file changed

Lines changed: 117 additions & 145 deletions

File tree

scripts/fix-standard-track-exceptions.js

Lines changed: 117 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
*
77
* Usage: node scripts/fix-standard-track-exceptions.js
88
*
9-
* Walks through each exception and prompts for an action:
9+
* Walks through each exception and prompts for an action. Spec_url commands
10+
* accept any space-separated mix of URLs and suggestion numbers (1-N):
1011
*
11-
* https://... — Add the URL as spec_url (space-separated for multiple)
12-
* f / false — Set standard_track to false (includes all subfeatures)
12+
* <items> — Set spec_url to listed URLs/suggestions (e.g. `1 2 https://x`)
1313
* p — Copy the parent feature's spec_url
14-
* p=https://.. — Set spec_url on both the parent and this subfeature
14+
* p <items> — Parent spec_url + extra URLs/suggestions on this subfeature
15+
* p=<items> — Set items on both the parent and this subfeature
16+
* f / false — Set standard_track to false (includes all subfeatures)
1517
* (empty) — Skip this entry
1618
* /foo — Skip ahead until the next entry containing "foo"
1719
* o — Open ancestor spec_url(s) in the browser
@@ -216,13 +218,12 @@ const fetchXrefSuggestions = async (
216218

217219
const instructions = `
218220
${styleText('bold', 'Actions:')}
219-
${styleText('cyan', 'https://...')} Add the URL as spec_url (space-separated for multiple)
220-
${styleText('cyan', '1-9')} Accept a numbered suggestion
221+
${styleText('cyan', '<items>')} URL(s) and/or suggestion #(s), space-separated
221222
${styleText('cyan', 'x')} Fetch xref suggestions for the current feature
222223
${styleText('cyan', 'x <term>')} Fetch xref suggestions for a custom search term
223224
${styleText('cyan', 'p')} Use parent feature's spec_url
224-
${styleText('cyan', 'p https://...')} Use parent spec_url + extra URL on this subfeature
225-
${styleText('cyan', 'p=https://...')} Set spec_url on parent + this subfeature
225+
${styleText('cyan', 'p <items>')} Parent spec_url + extra items on this subfeature
226+
${styleText('cyan', 'p=<items>')} Set items on parent + this subfeature
226227
${styleText('cyan', 'f')} Set standard_track to false (+ all subfeatures)
227228
${styleText('cyan', 'r')} Repeat the previous action
228229
${styleText('cyan', '(Enter)')} Skip this entry
@@ -273,7 +274,7 @@ const rl = readline.createInterface({
273274
output: process.stdout,
274275
});
275276

276-
const prompt = ` ${styleText('dim', 'URL, 1-9 (suggestion), x [term] (xref), p, f, r (repeat), /skip-to, or ?')}
277+
const prompt = ` ${styleText('dim', 'items (URL/1-9), x [term] (xref), p [items], p=items, f, r (repeat), /skip-to, or ?')}
277278
> `;
278279

279280
/** Save progress and exit */
@@ -520,150 +521,121 @@ while (idx < exceptions.length) {
520521
break;
521522
}
522523

524+
// Spec_url command: optional `p` / `p=` prefix followed by space-separated
525+
// tokens that are either URLs or suggestion numbers (1-N).
526+
let parentMode = null;
527+
let remainder;
523528
if (answer === 'p') {
524-
if (!ancestor) {
529+
parentMode = 'add';
530+
remainder = '';
531+
} else if (answer.startsWith('p ')) {
532+
parentMode = 'add';
533+
remainder = answer.slice(2);
534+
} else if (answer.startsWith('p=')) {
535+
parentMode = 'set';
536+
remainder = answer.slice(2);
537+
} else {
538+
remainder = answer;
539+
}
540+
const tokens = remainder.trim().split(/\s+/).filter(Boolean);
541+
/**
542+
* Whether a token looks like a spec_url command token (URL or number).
543+
* @param {string} t Token to test
544+
* @returns {boolean} True if token is a URL or numeric suggestion index
545+
*/
546+
const looksLikeSpecToken = (t) =>
547+
t.startsWith('https://') || /^\d+$/.test(t);
548+
const isSpecCommand =
549+
parentMode !== null ||
550+
(tokens.length > 0 && looksLikeSpecToken(tokens[0]));
551+
552+
if (isSpecCommand) {
553+
if (parentMode === 'add' && !ancestor) {
525554
console.log(styleText('red', ' No ancestor with spec_url found.'));
526555
continue;
527556
}
528-
updateFeatures([featurePath], (c) => {
529-
c.spec_url = ancestor.spec_url;
530-
return c;
531-
});
532-
recordSpecUrl(featurePath, ancestor.spec_url);
533-
remaining.delete(featurePath);
534-
lastAction = {
535-
index: i,
536-
/**
537-
*
538-
*/
539-
undo: () => {
540-
updateFeatures([featurePath], (c) => {
541-
delete c.spec_url;
542-
return c;
543-
});
544-
remaining.add(featurePath);
545-
},
546-
};
547-
lastInput = answer;
548-
break;
549-
}
550-
551-
if (answer.startsWith('p https://')) {
552-
if (!ancestor) {
553-
console.log(styleText('red', ' No ancestor with spec_url found.'));
557+
if (parentMode === 'set' && tokens.length === 0) {
558+
console.log(
559+
styleText('red', ' Need at least one URL or suggestion after p=.'),
560+
);
554561
continue;
555562
}
556-
const extra = answer.slice(2).trim().split(/\s+/);
557-
const specUrl = [...[ancestor.spec_url].flat(), ...extra];
558-
updateFeatures([featurePath], (c) => {
559-
c.spec_url = specUrl;
560-
return c;
561-
});
562-
recordSpecUrl(featurePath, specUrl);
563-
remaining.delete(featurePath);
564-
lastAction = {
565-
index: i,
566-
/**
567-
*
568-
*/
569-
undo: () => {
570-
updateFeatures([featurePath], (c) => {
571-
delete c.spec_url;
572-
return c;
573-
});
574-
remaining.add(featurePath);
575-
},
576-
};
577-
lastInput = answer;
578-
break;
579-
}
580563

581-
if (answer.startsWith('p=https://')) {
582-
const urls = answer.slice(2).trim().split(/\s+/);
583-
const specUrl = urls.length === 1 ? urls[0] : urls;
584-
const parentPath = featurePath.split('.').slice(0, -1).join('.');
585-
updateFeatures([parentPath], (c) => {
586-
c.spec_url = specUrl;
587-
return c;
588-
});
589-
updateFeatures([featurePath], (c) => {
590-
c.spec_url = specUrl;
591-
return c;
592-
});
593-
recordSpecUrl(featurePath, specUrl);
594-
remaining.delete(featurePath);
595-
lastAction = {
596-
index: i,
597-
/**
598-
*
599-
*/
600-
undo: () => {
601-
updateFeatures([parentPath], (c) => {
602-
delete c.spec_url;
603-
return c;
604-
});
605-
updateFeatures([featurePath], (c) => {
606-
delete c.spec_url;
607-
return c;
608-
});
609-
remaining.add(featurePath);
610-
},
611-
};
612-
lastInput = answer;
613-
break;
614-
}
615-
616-
const suggestionNum = Number(answer);
617-
if (
618-
Number.isInteger(suggestionNum) &&
619-
suggestionNum >= 1 &&
620-
suggestionNum <= suggestions.length
621-
) {
622-
const specUrl = suggestions[suggestionNum - 1];
623-
updateFeatures([featurePath], (c) => {
624-
c.spec_url = specUrl;
625-
return c;
626-
});
627-
remaining.delete(featurePath);
628-
lastAction = {
629-
index: i,
630-
/**
631-
*
632-
*/
633-
undo: () => {
634-
updateFeatures([featurePath], (c) => {
635-
delete c.spec_url;
636-
return c;
637-
});
638-
remaining.add(featurePath);
639-
},
640-
};
641-
lastInput = answer;
642-
break;
643-
}
564+
// Resolve tokens to URLs
565+
const resolved = [];
566+
let invalid = null;
567+
for (const t of tokens) {
568+
if (t.startsWith('https://')) {
569+
resolved.push(t);
570+
} else {
571+
const num = Number(t);
572+
if (Number.isInteger(num) && num >= 1 && num <= suggestions.length) {
573+
resolved.push(suggestions[num - 1]);
574+
} else {
575+
invalid = t;
576+
break;
577+
}
578+
}
579+
}
580+
if (invalid !== null) {
581+
console.log(
582+
styleText('yellow', ` Invalid token: ${invalid}. Type ? for help.`),
583+
);
584+
continue;
585+
}
644586

645-
if (answer.startsWith('https://')) {
646-
const urls = answer.split(/\s+/);
647-
const specUrl = urls.length === 1 ? urls[0] : urls;
648-
updateFeatures([featurePath], (c) => {
649-
c.spec_url = specUrl;
650-
return c;
651-
});
652-
recordSpecUrl(featurePath, specUrl);
653-
remaining.delete(featurePath);
654-
lastAction = {
655-
index: i,
656-
/**
657-
*
658-
*/
659-
undo: () => {
660-
updateFeatures([featurePath], (c) => {
661-
delete c.spec_url;
662-
return c;
663-
});
664-
remaining.add(featurePath);
665-
},
666-
};
587+
const allUrls =
588+
parentMode === 'add' && ancestor
589+
? [...[ancestor.spec_url].flat(), ...resolved]
590+
: resolved;
591+
const specUrl = allUrls.length === 1 ? allUrls[0] : allUrls;
592+
593+
if (parentMode === 'set') {
594+
const parentPath = featurePath.split('.').slice(0, -1).join('.');
595+
updateFeatures([parentPath], (c) => {
596+
c.spec_url = specUrl;
597+
return c;
598+
});
599+
updateFeatures([featurePath], (c) => {
600+
c.spec_url = specUrl;
601+
return c;
602+
});
603+
recordSpecUrl(featurePath, specUrl);
604+
remaining.delete(featurePath);
605+
lastAction = {
606+
index: i,
607+
/** Revert spec_url on parent + this feature. */
608+
undo: () => {
609+
updateFeatures([parentPath], (c) => {
610+
delete c.spec_url;
611+
return c;
612+
});
613+
updateFeatures([featurePath], (c) => {
614+
delete c.spec_url;
615+
return c;
616+
});
617+
remaining.add(featurePath);
618+
},
619+
};
620+
} else {
621+
updateFeatures([featurePath], (c) => {
622+
c.spec_url = specUrl;
623+
return c;
624+
});
625+
recordSpecUrl(featurePath, specUrl);
626+
remaining.delete(featurePath);
627+
lastAction = {
628+
index: i,
629+
/** Revert spec_url on this feature. */
630+
undo: () => {
631+
updateFeatures([featurePath], (c) => {
632+
delete c.spec_url;
633+
return c;
634+
});
635+
remaining.add(featurePath);
636+
},
637+
};
638+
}
667639
lastInput = answer;
668640
break;
669641
}

0 commit comments

Comments
 (0)