Add indexed %oN output substitution

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Christopher Monsanto
2026-08-17 00:13:24 -04:00
parent a132276e78
commit 11282e1002
2 changed files with 21 additions and 1 deletions

View File

@@ -22,8 +22,16 @@ export function basenameNoExt(path : string) : string {
// Tup-style substitutions:
// %f inputs, space-joined %b input basenames
// %o outputs, space-joined %B input basenames without extension
// %oN (1-based) a single output, for rules with several
export function substitute(s : string, inputs : string[], outputs : string[]) : string {
return s.replace(/%([a-zA-Z])/g, (match, c : string) => {
return s.replace(/%o(\d+)|%([a-zA-Z])/g, (match, n : string | undefined, c : string | undefined) => {
if (n !== undefined) {
const i = Number(n);
if (i < 1 || i > outputs.length) {
throw new Error(`Output index out of range (${outputs.length} outputs): ${match} in: ${s}`);
}
return outputs[i - 1]!;
}
switch (c) {
case 'f': return inputs.join(' ');
case 'o': return outputs.join(' ');

View File

@@ -19,6 +19,18 @@ test('substitute handles quoted frame selector', () => {
'magick convert "src/models/a.gif[0]" -trim out/a.png');
});
test('substitute expands indexed %oN', () => {
assert.equal(
substitute('tool --image %o1 --stylesheet %o2 -- %f', ['a.png'], ['x.png', 'x.css']),
'tool --image x.png --stylesheet x.css -- a.png');
assert.equal(substitute('%o10', [], ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'ten']), 'ten');
});
test('substitute rejects out-of-range %oN', () => {
assert.throws(() => substitute('%o2', ['a'], ['b']), /out of range/);
assert.throws(() => substitute('%o0', ['a'], ['b']), /out of range/);
});
test('substitute rejects unknown escapes', () => {
assert.throws(() => substitute('%d', ['a'], ['b']), /Unknown substitution/);
});