Skip to content

Commit

Permalink
B2B-866: Support for objects as items (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
levonet authored Nov 15, 2022
1 parent 5c777ad commit dc8ddbc
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 10 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,48 @@ jobs:
run: 'false'
- if: ${{ steps.loop.outputs.b != '{"test":"b1"}' }}
run: 'false'

- uses: ./
id: loop-json
with:
output_properties: 'true'
patterns: |
- test/assets/conf1.yml
- test/assets/{{ item }}/conf1.yml
loop: '["a","a/c","b"]'
loop_items_format: json
- run: echo '${{ steps.loop-json.outputs.result }}'
- if: ${{ steps.loop-json.outputs.result != '{"a":{"test":"a1"},"a/c":{"test":"c1"},"b":{"test":"b1"}}' }}
run: 'false'
- if: ${{ steps.loop-json.outputs.a != '{"test":"a1"}' }}
run: 'false'
- if: ${{ steps.loop-json.outputs.a_c != '{"test":"c1"}' }}
run: 'false'
- if: ${{ steps.loop-json.outputs.b != '{"test":"b1"}' }}
run: 'false'

- uses: ./
id: loop-deep-yaml
with:
output_properties: 'true'
patterns: |
- test/assets/{{ item.name }}.yml
- test/assets/{{ item.path }}/{{ item.name }}.yml
loop: |
- path: a
name: conf1
- path: a/c
name: conf1
- path: b
name: conf1
loop_items_format: yaml
loop_items_key: path
- run: echo '${{ steps.loop-deep-yaml.outputs.result }}'
- if: ${{ steps.loop-deep-yaml.outputs.result != '{"a":{"test":"a1"},"a/c":{"test":"c1"},"b":{"test":"b1"}}' }}
run: 'false'
- if: ${{ steps.loop-deep-yaml.outputs.a != '{"test":"a1"}' }}
run: 'false'
- if: ${{ steps.loop-deep-yaml.outputs.a_c != '{"test":"c1"}' }}
run: 'false'
- if: ${{ steps.loop-deep-yaml.outputs.b != '{"test":"b1"}' }}
run: 'false'
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,21 @@ Example:
- run: echo '${{ steps.config.outputs.dir2_subdir }}'
```

### `loop_items_format`

The format in which the list is passed to the loop
- `text` — each row is an item of a list
- `json` — list in JSON format
- `yaml` — list in YAML format

Default: `text`.

### `loop_items_key`

Object path to the value that acts as the key.
Helps set the key by which the result will be available if the item contains an object.
Otherwise, the index is used as the key.

## Outputs

### `result`
Expand Down
13 changes: 13 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ inputs:
The iterative execution of the action returns the object as JSON, where the key contains a row,
and the value is the result of the execution of the action according to the pattern.
Also it has output for each row as a serialized key with value in JSON if `output_properties` is enabled.
loop_items_format:
description: |
The format in which the list is passed to the loop:
- `text` — each row is an item of a list
- `json` — list in JSON format
- `yaml` — list in YAML format
Default: 'text'.
default: text
loop_items_key:
description: |
Object path to the value that acts as the key.
Helps set the key by which the result will be available if the item contains an object.
Otherwise, the index is used as the key.
outputs:
result:
description: Merged configuration as JSON or plain text.
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

47 changes: 39 additions & 8 deletions lib/action.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const yaml = require('js-yaml')
const logWrapper = require('./log')
const { getFiles, getLevels } = require('./levels')
const { getType, mergeLevels } = require('./merge')
const { modifyPattern } = require('./parser')
const { getByPath, modifyPattern } = require('./parser')

function configLevels(patterns, options, log = console) {
const files = getFiles(patterns)
Expand All @@ -11,7 +12,7 @@ function configLevels(patterns, options, log = console) {
}

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

function setOutputAndPrint(core, key, value) {
Expand All @@ -30,11 +31,26 @@ function setOutputAndPrint(core, key, value) {
core.setOutput(outputKey, outputValue)
}

function getLoopItems(content, format) {
if (format === 'yaml') {
return yaml.load(content)
}
if (format === 'json') {
return JSON.parse(content)
}

return content.split('\n')
.map((item) => item.trim())
.filter((item) => item !== '')
}

function run(core) {
const log = logWrapper(core)
const patterns = core.getInput('patterns', {required: true})
const outputProperties = core.getBooleanInput('output_properties')
const loop = core.getMultilineInput('loop')
const loopContent = core.getInput('loop')
const loopItemsFormat = core.getInput('loop_items_format')
const loopItemsKey = core.getInput('loop_items_key')
const options = {
mergeObject: core.getInput('merge_object'),
mergeArray: core.getInput('merge_array'),
Expand All @@ -53,29 +69,43 @@ function run(core) {
core.error(`Wrong value of "merge_plain": "${options.mergePlain}". Should be one of "concatenating" or "overwrite".`)
return
}
if (!(['text', 'json', 'yaml'].includes(loopItemsFormat))) {
core.error(`Wrong value of "loop_items_format": "${loopItemsFormat}". Should be one of "text", "json" or "yaml".`)
return
}

let result

/* TODO: Loop it using external action (action-loop) based on
* - https://github.com/nektos/act/blob/master/pkg/runner/step_action_remote.go
* - https://github.com/cardinalby/github-action-ts-run-api
*/
const loop = getLoopItems(loopContent, loopItemsFormat)
if (!Array.isArray(loop)) {
core.error('"loop" must contain a list of items.')
return
}

if (loop.length) {
result = {}

for (const item of loop) {
core.startGroup(`Pattern processing with '${item}' item`)
const modifiedPattern = modifyPattern(patterns, item)
for (let i = 0; i < loop.length; i++) {
core.startGroup(`Pattern processing with ${JSON.stringify(loop[i])} item`)
const modifiedPattern = modifyPattern(patterns, loop[i])
const resultValue = processingLevels(core, modifiedPattern, options, log)
if (resultValue === null) {
core.endGroup()
continue
}

result[item] = resultValue
const objectKey = !!loop[i] && loop[i].constructor === Object && !!loopItemsKey
? getByPath(loop[i], loopItemsKey)
: i
const key = (typeof loop[i] === 'string' || typeof loop[i] === 'number') ? loop[i] : objectKey
result[key] = resultValue

if (outputProperties) {
setOutputAndPrint(core, item, resultValue)
setOutputAndPrint(core, key, resultValue)
}
core.endGroup()
}
Expand Down Expand Up @@ -113,6 +143,7 @@ function processingLevels(core, patterns, options, log) {
}

module.exports = {
getLoopItems,
configLevels,
run
}
9 changes: 8 additions & 1 deletion lib/parser.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@

function getByPath(source, target) {
return target.trim()
.split('.')
.reduce((obj, i) => obj[i], source)
}

function modifyPattern(patterns, item) {
const reItem = /\{\{(.*?)\}\}/
const patternContent = patterns.split('\n')
const options = {item: item}

for (let i = 0; i < patternContent.length; i++) {
for (let match = patternContent[i].match(reItem), result; match;) {
result = options[match[1].trim()]
result = getByPath(options, match[1])
patternContent[i] = patternContent[i].replace(match[0], result ? result : '')
match = patternContent[i].match(reItem)
}
Expand All @@ -16,5 +22,6 @@ function modifyPattern(patterns, item) {
}

module.exports = {
getByPath,
modifyPattern
}
12 changes: 12 additions & 0 deletions test/action.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,15 @@ describe('test configLevels()', () => {
expect(action.configLevels('- test/assets/**/file.txt', {}, log)).toEqual('root\ntestA\ntestB\n')
})
})

describe('test getLoopItems()', () => {
test('expect object config', () => {
expect(action.getLoopItems('- boo\n- foo', 'yaml')).toEqual(['boo', 'foo'])
})
test('expect array config', () => {
expect(action.getLoopItems('["boo","foo"]', 'json')).toEqual(['boo', 'foo'])
})
test('expect plain config', () => {
expect(action.getLoopItems('boo\r\nfoo\n\n', 'text')).toEqual(['boo', 'foo'])
})
})
6 changes: 6 additions & 0 deletions test/parser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ describe('test modifyPattern()', () => {
test('expect brackets to be removed if the variable does not exist', () => {
expect(parser.modifyPattern('- test/{{ boo }}/**/foo.yml', 'a')).toEqual('- test//**/foo.yml')
})
test('expect support for elements as objects', () => {
expect(parser.modifyPattern('- test/{{item.boo}}/{{item.foo}}.yml', {boo: 'a', foo: 'b'})).toEqual('- test/a/b.yml')
})
test('expect support for elements as deep objects', () => {
expect(parser.modifyPattern('- test/{{item.a.b.c}}.yml', {a: {b: {c: 123}}})).toEqual('- test/123.yml')
})
})

0 comments on commit dc8ddbc

Please sign in to comment.