Skip to content
This repository was archived by the owner on Mar 21, 2025. It is now read-only.

Commit dc8ddbc

Browse files
authored
B2B-866: Support for objects as items (#10)
1 parent 5c777ad commit dc8ddbc

File tree

8 files changed

+139
-10
lines changed

8 files changed

+139
-10
lines changed

.github/workflows/test.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,48 @@ jobs:
7272
run: 'false'
7373
- if: ${{ steps.loop.outputs.b != '{"test":"b1"}' }}
7474
run: 'false'
75+
76+
- uses: ./
77+
id: loop-json
78+
with:
79+
output_properties: 'true'
80+
patterns: |
81+
- test/assets/conf1.yml
82+
- test/assets/{{ item }}/conf1.yml
83+
loop: '["a","a/c","b"]'
84+
loop_items_format: json
85+
- run: echo '${{ steps.loop-json.outputs.result }}'
86+
- if: ${{ steps.loop-json.outputs.result != '{"a":{"test":"a1"},"a/c":{"test":"c1"},"b":{"test":"b1"}}' }}
87+
run: 'false'
88+
- if: ${{ steps.loop-json.outputs.a != '{"test":"a1"}' }}
89+
run: 'false'
90+
- if: ${{ steps.loop-json.outputs.a_c != '{"test":"c1"}' }}
91+
run: 'false'
92+
- if: ${{ steps.loop-json.outputs.b != '{"test":"b1"}' }}
93+
run: 'false'
94+
95+
- uses: ./
96+
id: loop-deep-yaml
97+
with:
98+
output_properties: 'true'
99+
patterns: |
100+
- test/assets/{{ item.name }}.yml
101+
- test/assets/{{ item.path }}/{{ item.name }}.yml
102+
loop: |
103+
- path: a
104+
name: conf1
105+
- path: a/c
106+
name: conf1
107+
- path: b
108+
name: conf1
109+
loop_items_format: yaml
110+
loop_items_key: path
111+
- run: echo '${{ steps.loop-deep-yaml.outputs.result }}'
112+
- if: ${{ steps.loop-deep-yaml.outputs.result != '{"a":{"test":"a1"},"a/c":{"test":"c1"},"b":{"test":"b1"}}' }}
113+
run: 'false'
114+
- if: ${{ steps.loop-deep-yaml.outputs.a != '{"test":"a1"}' }}
115+
run: 'false'
116+
- if: ${{ steps.loop-deep-yaml.outputs.a_c != '{"test":"c1"}' }}
117+
run: 'false'
118+
- if: ${{ steps.loop-deep-yaml.outputs.b != '{"test":"b1"}' }}
119+
run: 'false'

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,21 @@ Example:
153153
- run: echo '${{ steps.config.outputs.dir2_subdir }}'
154154
```
155155

156+
### `loop_items_format`
157+
158+
The format in which the list is passed to the loop
159+
- `text` — each row is an item of a list
160+
- `json` — list in JSON format
161+
- `yaml` — list in YAML format
162+
163+
Default: `text`.
164+
165+
### `loop_items_key`
166+
167+
Object path to the value that acts as the key.
168+
Helps set the key by which the result will be available if the item contains an object.
169+
Otherwise, the index is used as the key.
170+
156171
## Outputs
157172

158173
### `result`

action.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ inputs:
3838
The iterative execution of the action returns the object as JSON, where the key contains a row,
3939
and the value is the result of the execution of the action according to the pattern.
4040
Also it has output for each row as a serialized key with value in JSON if `output_properties` is enabled.
41+
loop_items_format:
42+
description: |
43+
The format in which the list is passed to the loop:
44+
- `text` — each row is an item of a list
45+
- `json` — list in JSON format
46+
- `yaml` — list in YAML format
47+
Default: 'text'.
48+
default: text
49+
loop_items_key:
50+
description: |
51+
Object path to the value that acts as the key.
52+
Helps set the key by which the result will be available if the item contains an object.
53+
Otherwise, the index is used as the key.
4154
outputs:
4255
result:
4356
description: Merged configuration as JSON or plain text.

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/action.js

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
const yaml = require('js-yaml')
12
const logWrapper = require('./log')
23
const { getFiles, getLevels } = require('./levels')
34
const { getType, mergeLevels } = require('./merge')
4-
const { modifyPattern } = require('./parser')
5+
const { getByPath, modifyPattern } = require('./parser')
56

67
function configLevels(patterns, options, log = console) {
78
const files = getFiles(patterns)
@@ -11,7 +12,7 @@ function configLevels(patterns, options, log = console) {
1112
}
1213

1314
function safeKey(key) {
14-
return key.replace(/[^\w_-]+/gu, '_')
15+
return key.toString().replace(/[^\w_-]+/gu, '_')
1516
}
1617

1718
function setOutputAndPrint(core, key, value) {
@@ -30,11 +31,26 @@ function setOutputAndPrint(core, key, value) {
3031
core.setOutput(outputKey, outputValue)
3132
}
3233

34+
function getLoopItems(content, format) {
35+
if (format === 'yaml') {
36+
return yaml.load(content)
37+
}
38+
if (format === 'json') {
39+
return JSON.parse(content)
40+
}
41+
42+
return content.split('\n')
43+
.map((item) => item.trim())
44+
.filter((item) => item !== '')
45+
}
46+
3347
function run(core) {
3448
const log = logWrapper(core)
3549
const patterns = core.getInput('patterns', {required: true})
3650
const outputProperties = core.getBooleanInput('output_properties')
37-
const loop = core.getMultilineInput('loop')
51+
const loopContent = core.getInput('loop')
52+
const loopItemsFormat = core.getInput('loop_items_format')
53+
const loopItemsKey = core.getInput('loop_items_key')
3854
const options = {
3955
mergeObject: core.getInput('merge_object'),
4056
mergeArray: core.getInput('merge_array'),
@@ -53,29 +69,43 @@ function run(core) {
5369
core.error(`Wrong value of "merge_plain": "${options.mergePlain}". Should be one of "concatenating" or "overwrite".`)
5470
return
5571
}
72+
if (!(['text', 'json', 'yaml'].includes(loopItemsFormat))) {
73+
core.error(`Wrong value of "loop_items_format": "${loopItemsFormat}". Should be one of "text", "json" or "yaml".`)
74+
return
75+
}
5676

5777
let result
5878

5979
/* TODO: Loop it using external action (action-loop) based on
6080
* - https://github.com/nektos/act/blob/master/pkg/runner/step_action_remote.go
6181
* - https://github.com/cardinalby/github-action-ts-run-api
6282
*/
83+
const loop = getLoopItems(loopContent, loopItemsFormat)
84+
if (!Array.isArray(loop)) {
85+
core.error('"loop" must contain a list of items.')
86+
return
87+
}
88+
6389
if (loop.length) {
6490
result = {}
6591

66-
for (const item of loop) {
67-
core.startGroup(`Pattern processing with '${item}' item`)
68-
const modifiedPattern = modifyPattern(patterns, item)
92+
for (let i = 0; i < loop.length; i++) {
93+
core.startGroup(`Pattern processing with ${JSON.stringify(loop[i])} item`)
94+
const modifiedPattern = modifyPattern(patterns, loop[i])
6995
const resultValue = processingLevels(core, modifiedPattern, options, log)
7096
if (resultValue === null) {
7197
core.endGroup()
7298
continue
7399
}
74100

75-
result[item] = resultValue
101+
const objectKey = !!loop[i] && loop[i].constructor === Object && !!loopItemsKey
102+
? getByPath(loop[i], loopItemsKey)
103+
: i
104+
const key = (typeof loop[i] === 'string' || typeof loop[i] === 'number') ? loop[i] : objectKey
105+
result[key] = resultValue
76106

77107
if (outputProperties) {
78-
setOutputAndPrint(core, item, resultValue)
108+
setOutputAndPrint(core, key, resultValue)
79109
}
80110
core.endGroup()
81111
}
@@ -113,6 +143,7 @@ function processingLevels(core, patterns, options, log) {
113143
}
114144

115145
module.exports = {
146+
getLoopItems,
116147
configLevels,
117148
run
118149
}

lib/parser.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11

2+
function getByPath(source, target) {
3+
return target.trim()
4+
.split('.')
5+
.reduce((obj, i) => obj[i], source)
6+
}
7+
28
function modifyPattern(patterns, item) {
39
const reItem = /\{\{(.*?)\}\}/
410
const patternContent = patterns.split('\n')
511
const options = {item: item}
612

713
for (let i = 0; i < patternContent.length; i++) {
814
for (let match = patternContent[i].match(reItem), result; match;) {
9-
result = options[match[1].trim()]
15+
result = getByPath(options, match[1])
1016
patternContent[i] = patternContent[i].replace(match[0], result ? result : '')
1117
match = patternContent[i].match(reItem)
1218
}
@@ -16,5 +22,6 @@ function modifyPattern(patterns, item) {
1622
}
1723

1824
module.exports = {
25+
getByPath,
1926
modifyPattern
2027
}

test/action.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,15 @@ describe('test configLevels()', () => {
1313
expect(action.configLevels('- test/assets/**/file.txt', {}, log)).toEqual('root\ntestA\ntestB\n')
1414
})
1515
})
16+
17+
describe('test getLoopItems()', () => {
18+
test('expect object config', () => {
19+
expect(action.getLoopItems('- boo\n- foo', 'yaml')).toEqual(['boo', 'foo'])
20+
})
21+
test('expect array config', () => {
22+
expect(action.getLoopItems('["boo","foo"]', 'json')).toEqual(['boo', 'foo'])
23+
})
24+
test('expect plain config', () => {
25+
expect(action.getLoopItems('boo\r\nfoo\n\n', 'text')).toEqual(['boo', 'foo'])
26+
})
27+
})

test/parser.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,10 @@ describe('test modifyPattern()', () => {
1111
test('expect brackets to be removed if the variable does not exist', () => {
1212
expect(parser.modifyPattern('- test/{{ boo }}/**/foo.yml', 'a')).toEqual('- test//**/foo.yml')
1313
})
14+
test('expect support for elements as objects', () => {
15+
expect(parser.modifyPattern('- test/{{item.boo}}/{{item.foo}}.yml', {boo: 'a', foo: 'b'})).toEqual('- test/a/b.yml')
16+
})
17+
test('expect support for elements as deep objects', () => {
18+
expect(parser.modifyPattern('- test/{{item.a.b.c}}.yml', {a: {b: {c: 123}}})).toEqual('- test/123.yml')
19+
})
1420
})

0 commit comments

Comments
 (0)