Skip to content

Commit 1816408

Browse files
authored
Placeholder that unfolds into multiple tasks (#134)
* 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.
1 parent c38c745 commit 1816408

File tree

5 files changed

+98
-37
lines changed

5 files changed

+98
-37
lines changed

docs/npm-run-all.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,12 @@ There are the following placeholders:
177177
- `{1}`, `{2}`, ... -- An argument. `{1}` is the 1st argument. `{2}` is the 2nd.
178178
- `{@}` -- All arguments.
179179
- `{*}` -- All arguments as combined.
180+
- `{%}` -- Repeats the command for every argument. (There's no equivalent shell parameter and does not support suffixes)
181+
182+
Support for following suffixes:
183+
184+
- `{1-=foo}` -- defaults to `'foo'` here when the 1st argument is missing
185+
- `{1:=foo}` -- defaults to `'foo'` here and in all following `{1}` when the 1st argument is missing
180186

181187
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).
182188

docs/run-p.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ There are the following placeholders:
141141
- `{1}`, `{2}`, ... -- An argument. `{1}` is the 1st argument. `{2}` is the 2nd.
142142
- `{@}` -- All arguments.
143143
- `{*}` -- All arguments as combined.
144+
- `{%}` -- Repeats the command for every argument. (There's no equivalent shell parameter and does not support suffixes)
145+
146+
Support for following suffixes:
147+
148+
- `{1-=foo}` -- defaults to `'foo'` here when the 1st argument is missing
149+
- `{1:=foo}` -- defaults to `'foo'` here and in all following `{1}` when the 1st argument is missing
144150

145151
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).
146152

docs/run-s.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ There are the following placeholders:
132132
- `{1}`, `{2}`, ... -- An argument. `{1}` is the 1st argument. `{2}` is the 2nd.
133133
- `{@}` -- All arguments.
134134
- `{*}` -- All arguments as combined.
135+
- `{%}` -- Repeats the command for every argument. (There's no equivalent shell parameter and does not support suffixes)
136+
137+
Support for following suffixes:
138+
139+
- `{1-=foo}` -- defaults to `'foo'` here when the 1st argument is missing
140+
- `{1:=foo}` -- defaults to `'foo'` here and in all following `{1}` when the 1st argument is missing
135141

136142
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).
137143

lib/index.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ const runTasks = require('./run-tasks')
1919
// Helpers
2020
// ------------------------------------------------------------------------------
2121

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

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

47-
return patterns.map(pattern => pattern.replace(ARGS_PATTERN, (whole, indirectionMark, id, options) => {
48+
const unfoldedPatterns = patterns
49+
.flatMap(pattern => {
50+
const match = ARGS_UNPACK_PATTERN.exec(pattern)
51+
if (match && match[2] === '%') {
52+
const result = []
53+
for (let i = 0, length = args.length; i < length; i++) {
54+
const argPosition = i + 1
55+
result.push(pattern.replace(ARGS_UNPACK_PATTERN, (whole, indirectionMark, id, options) => {
56+
if (indirectionMark != null || options != null || id !== '%') {
57+
throw Error(`Invalid Placeholder: ${whole}`)
58+
}
59+
return `{${argPosition}}`
60+
}))
61+
}
62+
return result
63+
}
64+
return pattern
65+
})
66+
67+
return unfoldedPatterns.map(pattern => pattern.replace(ARGS_PATTERN, (whole, indirectionMark, id, options) => {
4868
if (indirectionMark != null) {
4969
throw Error(`Invalid Placeholder: ${whole}`)
5070
}

test/argument-placeholders.js

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
// ------------------------------------------------------------------------------
1212

1313
const assert = require('assert').strict
14+
const { strictEqual } = assert
15+
1416
const nodeApi = require('../lib')
1517
const util = require('./lib/util')
1618
const result = util.result
@@ -32,156 +34,177 @@ describe('[argument-placeholders]', () => {
3234
describe("If arguments preceded by '--' are nothing, '{1}' should be empty:", () => {
3335
it('Node API', () =>
3436
nodeApi('test-task:dump {1}')
35-
.then(() => assert(result() === '[]')))
37+
.then(() => strictEqual(result(), '[]')))
3638

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

129131
it('run-p command', () =>
130132
runPar(['test-task:dump {*}', '--', '1st', '2nd'])
131-
.then(() => assert(result() === '["1st 2nd"]')))
133+
.then(() => strictEqual(result(), '["1st 2nd"]')))
134+
})
135+
136+
describe("'{%}' should be unfolded into one command for each argument following '--':", () => {
137+
it('Node API', () =>
138+
nodeApi('test-task:dump {%}', { arguments: ['1st', '2nd'] })
139+
.then(() => strictEqual(result(), '["1st"]["2nd"]')))
140+
141+
it('npm-run-all command', () =>
142+
runAll(['test-task:dump {%}', '--', '1st', '2nd'])
143+
.then(() => strictEqual(result(), '["1st"]["2nd"]')))
144+
145+
it('run-s command', () =>
146+
runSeq(['test-task:dump {%}', '--', '1st', '2nd'])
147+
.then(() => strictEqual(result(), '["1st"]["2nd"]')))
148+
149+
it('run-p command', () =>
150+
runPar(['test-task:dump {%}', '--', '1st', '2nd'])
151+
.then(() => {
152+
const value = result()
153+
assert(value === '["1st"]["2nd"]' || value === '["2nd"]["1st"]')
154+
}))
132155
})
133156

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

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

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

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

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

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

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

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

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

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

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

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

0 commit comments

Comments
 (0)