diff --git a/tools/build/subst.ts b/tools/build/subst.ts index 1a3fc9fb..30f07096 100644 --- a/tools/build/subst.ts +++ b/tools/build/subst.ts @@ -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(' '); diff --git a/tools/build/test/subst.test.ts b/tools/build/test/subst.test.ts index 31746512..1e786b83 100644 --- a/tools/build/test/subst.test.ts +++ b/tools/build/test/subst.test.ts @@ -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/); });