Skip to content

Commit

Permalink
Placeholder that unfolds into multiple tasks (#134)
Browse files Browse the repository at this point in the history
* Placeholder that unfolds into multiple tasks

This adds a new placeholder, {%}, which unfolds into multiple tasks – one for each input argument. The unfolded tasks gets assigned {1}, {2} etc instead of {%}.

Purpose:

Be able to in parallell process a single npm task X times with a different argument each time.
  • Loading branch information
voxpelli committed May 17, 2024
1 parent c38c745 commit 1816408
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 37 deletions.
6 changes: 6 additions & 0 deletions docs/npm-run-all.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ There are the following placeholders:
- `{1}`, `{2}`, ... -- An argument. `{1}` is the 1st argument. `{2}` is the 2nd.
- `{@}` -- All arguments.
- `{*}` -- All arguments as combined.
- `{%}` -- Repeats the command for every argument. (There's no equivalent shell parameter and does not support suffixes)

Support for following suffixes:

- `{1-=foo}` -- defaults to `'foo'` here when the 1st argument is missing
- `{1:=foo}` -- defaults to `'foo'` here and in all following `{1}` when the 1st argument is missing

Those are similar to [Shell Parameters](http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameters). But please note arguments are enclosed by double quotes automatically (similar to npm).

Expand Down
6 changes: 6 additions & 0 deletions docs/run-p.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ There are the following placeholders:
- `{1}`, `{2}`, ... -- An argument. `{1}` is the 1st argument. `{2}` is the 2nd.
- `{@}` -- All arguments.
- `{*}` -- All arguments as combined.
- `{%}` -- Repeats the command for every argument. (There's no equivalent shell parameter and does not support suffixes)

Support for following suffixes:

- `{1-=foo}` -- defaults to `'foo'` here when the 1st argument is missing
- `{1:=foo}` -- defaults to `'foo'` here and in all following `{1}` when the 1st argument is missing

Those are similar to [Shell Parameters](http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameters). But please note arguments are enclosed by double quotes automatically (similar to npm).

Expand Down
6 changes: 6 additions & 0 deletions docs/run-s.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ There are the following placeholders:
- `{1}`, `{2}`, ... -- An argument. `{1}` is the 1st argument. `{2}` is the 2nd.
- `{@}` -- All arguments.
- `{*}` -- All arguments as combined.
- `{%}` -- Repeats the command for every argument. (There's no equivalent shell parameter and does not support suffixes)

Support for following suffixes:

- `{1-=foo}` -- defaults to `'foo'` here when the 1st argument is missing
- `{1:=foo}` -- defaults to `'foo'` here and in all following `{1}` when the 1st argument is missing

Those are similar to [Shell Parameters](http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameters). But please note arguments are enclosed by double quotes automatically (similar to npm).

Expand Down
24 changes: 22 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ const runTasks = require('./run-tasks')
// Helpers
// ------------------------------------------------------------------------------

const ARGS_PATTERN = /\{(!)?([*@]|\d+)([^}]+)?}/g
const ARGS_PATTERN = /\{(!)?([*@%]|\d+)([^}]+)?}/g
const ARGS_UNPACK_PATTERN = /\{(!)?([%])([^}]+)?}/g

/**
* Converts a given value to an array.
Expand All @@ -44,7 +45,26 @@ function toArray (x) {
function applyArguments (patterns, args) {
const defaults = Object.create(null)

return patterns.map(pattern => pattern.replace(ARGS_PATTERN, (whole, indirectionMark, id, options) => {
const unfoldedPatterns = patterns
.flatMap(pattern => {
const match = ARGS_UNPACK_PATTERN.exec(pattern)
if (match && match[2] === '%') {
const result = []
for (let i = 0, length = args.length; i < length; i++) {
const argPosition = i + 1
result.push(pattern.replace(ARGS_UNPACK_PATTERN, (whole, indirectionMark, id, options) => {
if (indirectionMark != null || options != null || id !== '%') {
throw Error(`Invalid Placeholder: ${whole}`)
}
return `{${argPosition}}`
}))
}
return result
}
return pattern
})

return unfoldedPatterns.map(pattern => pattern.replace(ARGS_PATTERN, (whole, indirectionMark, id, options) => {
if (indirectionMark != null) {
throw Error(`Invalid Placeholder: ${whole}`)
}
Expand Down
93 changes: 58 additions & 35 deletions test/argument-placeholders.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
// ------------------------------------------------------------------------------

const assert = require('assert').strict
const { strictEqual } = assert

const nodeApi = require('../lib')
const util = require('./lib/util')
const result = util.result
Expand All @@ -32,156 +34,177 @@ describe('[argument-placeholders]', () => {
describe("If arguments preceded by '--' are nothing, '{1}' should be empty:", () => {
it('Node API', () =>
nodeApi('test-task:dump {1}')
.then(() => assert(result() === '[]')))
.then(() => strictEqual(result(), '[]')))

it('npm-run-all command', () =>
runAll(['test-task:dump {1}'])
.then(() => assert(result() === '[]')))
.then(() => strictEqual(result(), '[]')))

it("npm-run-all command (only '--' exists)", () =>
runAll(['test-task:dump {1}', '--'])
.then(() => assert(result() === '[]')))
.then(() => strictEqual(result(), '[]')))

it('run-s command', () =>
runSeq(['test-task:dump {1}'])
.then(() => assert(result() === '[]')))
.then(() => strictEqual(result(), '[]')))

it("run-s command (only '--' exists)", () =>
runSeq(['test-task:dump {1}', '--'])
.then(() => assert(result() === '[]')))
.then(() => strictEqual(result(), '[]')))

it('run-p command', () =>
runPar(['test-task:dump {1}'])
.then(() => assert(result() === '[]')))
.then(() => strictEqual(result(), '[]')))

it("run-p command (only '--' exists)", () =>
runPar(['test-task:dump {1}', '--'])
.then(() => assert(result() === '[]')))
.then(() => strictEqual(result(), '[]')))
})

describe("'{1}' should be replaced by the 1st argument preceded by '--':", () => {
it('Node API', () =>
nodeApi('test-task:dump {1}', { arguments: ['1st', '2nd'] })
.then(() => assert(result() === '["1st"]')))
.then(() => strictEqual(result(), '["1st"]')))

it('npm-run-all command', () =>
runAll(['test-task:dump {1}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st"]')))
.then(() => strictEqual(result(), '["1st"]')))

it('run-s command', () =>
runSeq(['test-task:dump {1}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st"]')))
.then(() => strictEqual(result(), '["1st"]')))

it('run-p command', () =>
runPar(['test-task:dump {1}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st"]')))
.then(() => strictEqual(result(), '["1st"]')))
})

describe("'{2}' should be replaced by the 2nd argument preceded by '--':", () => {
it('Node API', () =>
nodeApi('test-task:dump {2}', { arguments: ['1st', '2nd'] })
.then(() => assert(result() === '["2nd"]')))
.then(() => strictEqual(result(), '["2nd"]')))

it('npm-run-all command', () =>
runAll(['test-task:dump {2}', '--', '1st', '2nd'])
.then(() => assert(result() === '["2nd"]')))
.then(() => strictEqual(result(), '["2nd"]')))

it('run-s command', () =>
runSeq(['test-task:dump {2}', '--', '1st', '2nd'])
.then(() => assert(result() === '["2nd"]')))
.then(() => strictEqual(result(), '["2nd"]')))

it('run-p command', () =>
runPar(['test-task:dump {2}', '--', '1st', '2nd'])
.then(() => assert(result() === '["2nd"]')))
.then(() => strictEqual(result(), '["2nd"]')))
})

describe("'{@}' should be replaced by the every argument preceded by '--':", () => {
it('Node API', () =>
nodeApi('test-task:dump {@}', { arguments: ['1st', '2nd'] })
.then(() => assert(result() === '["1st","2nd"]')))
.then(() => strictEqual(result(), '["1st","2nd"]')))

it('npm-run-all command', () =>
runAll(['test-task:dump {@}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st","2nd"]')))
.then(() => strictEqual(result(), '["1st","2nd"]')))

it('run-s command', () =>
runSeq(['test-task:dump {@}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st","2nd"]')))
.then(() => strictEqual(result(), '["1st","2nd"]')))

it('run-p command', () =>
runPar(['test-task:dump {@}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st","2nd"]')))
.then(() => strictEqual(result(), '["1st","2nd"]')))
})

describe("'{*}' should be replaced by the all arguments preceded by '--':", () => {
it('Node API', () =>
nodeApi('test-task:dump {*}', { arguments: ['1st', '2nd'] })
.then(() => assert(result() === '["1st 2nd"]')))
.then(() => strictEqual(result(), '["1st 2nd"]')))

it('npm-run-all command', () =>
runAll(['test-task:dump {*}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st 2nd"]')))
.then(() => strictEqual(result(), '["1st 2nd"]')))

it('run-s command', () =>
runSeq(['test-task:dump {*}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st 2nd"]')))
.then(() => strictEqual(result(), '["1st 2nd"]')))

it('run-p command', () =>
runPar(['test-task:dump {*}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st 2nd"]')))
.then(() => strictEqual(result(), '["1st 2nd"]')))
})

describe("'{%}' should be unfolded into one command for each argument following '--':", () => {
it('Node API', () =>
nodeApi('test-task:dump {%}', { arguments: ['1st', '2nd'] })
.then(() => strictEqual(result(), '["1st"]["2nd"]')))

it('npm-run-all command', () =>
runAll(['test-task:dump {%}', '--', '1st', '2nd'])
.then(() => strictEqual(result(), '["1st"]["2nd"]')))

it('run-s command', () =>
runSeq(['test-task:dump {%}', '--', '1st', '2nd'])
.then(() => strictEqual(result(), '["1st"]["2nd"]')))

it('run-p command', () =>
runPar(['test-task:dump {%}', '--', '1st', '2nd'])
.then(() => {
const value = result()
assert(value === '["1st"]["2nd"]' || value === '["2nd"]["1st"]')
}))
})

describe("Every '{1}', '{2}', '{@}' and '{*}' should be replaced by the arguments preceded by '--':", () => {
it('Node API', () =>
nodeApi('test-task:dump {1} {2} {3} {@} {*}', { arguments: ['1st', '2nd'] })
.then(() => assert(result() === '["1st","2nd","1st","2nd","1st 2nd"]')))
.then(() => strictEqual(result(), '["1st","2nd","1st","2nd","1st 2nd"]')))

it('npm-run-all command', () =>
runAll(['test-task:dump {1} {2} {3} {@} {*}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st","2nd","1st","2nd","1st 2nd"]')))
.then(() => strictEqual(result(), '["1st","2nd","1st","2nd","1st 2nd"]')))

it('run-s command', () =>
runSeq(['test-task:dump {1} {2} {3} {@} {*}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st","2nd","1st","2nd","1st 2nd"]')))
.then(() => strictEqual(result(), '["1st","2nd","1st","2nd","1st 2nd"]')))

it('run-p command', () =>
runPar(['test-task:dump {1} {2} {3} {@} {*}', '--', '1st', '2nd'])
.then(() => assert(result() === '["1st","2nd","1st","2nd","1st 2nd"]')))
.then(() => strictEqual(result(), '["1st","2nd","1st","2nd","1st 2nd"]')))
})

describe("'{1:-foo}' should be replaced by 'foo' if arguments are nothing:", () => {
it('Node API', () =>
nodeApi('test-task:dump {1:-foo} {1}')
.then(() => assert(result() === '["foo"]')))
.then(() => strictEqual(result(), '["foo"]')))

it('npm-run-all command', () =>
runAll(['test-task:dump {1:-foo} {1}'])
.then(() => assert(result() === '["foo"]')))
.then(() => strictEqual(result(), '["foo"]')))

it('run-s command', () =>
runSeq(['test-task:dump {1:-foo} {1}'])
.then(() => assert(result() === '["foo"]')))
.then(() => strictEqual(result(), '["foo"]')))

it('run-p command', () =>
runPar(['test-task:dump {1:-foo} {1}'])
.then(() => assert(result() === '["foo"]')))
.then(() => strictEqual(result(), '["foo"]')))
})

describe("'{1:=foo}' should be replaced by 'foo' and should affect following '{1}' if arguments are nothing:", () => {
it('Node API', () =>
nodeApi('test-task:dump {1:=foo} {1}')
.then(() => assert(result() === '["foo","foo"]')))
.then(() => strictEqual(result(), '["foo","foo"]')))

it('npm-run-all command', () =>
runAll(['test-task:dump {1:=foo} {1}'])
.then(() => assert(result() === '["foo","foo"]')))
.then(() => strictEqual(result(), '["foo","foo"]')))

it('run-s command', () =>
runSeq(['test-task:dump {1:=foo} {1}'])
.then(() => assert(result() === '["foo","foo"]')))
.then(() => strictEqual(result(), '["foo","foo"]')))

it('run-p command', () =>
runPar(['test-task:dump {1:=foo} {1}'])
.then(() => assert(result() === '["foo","foo"]')))
.then(() => strictEqual(result(), '["foo","foo"]')))
})
})

0 comments on commit 1816408

Please sign in to comment.