In workflows, the existing join() function concatenates an array of strings to create one long string.
I want to do the opposite. I want to split a long string into an array of shorter strings.
The problem
Workflows triggered via workflow_dispatch or workflow_call can accept inputs.
- An
input must be of type string, number, or boolean.
- It cannot be an
array.
That's a problem because arrays are needed to create a jobs matrix:
jobs:
generate-translations:
strategy:
matrix:
language: ${{ inputs.languages }} # [fr, de, pt, pt_BR]
steps:
- run: echo "Generate ${{ matrix.language }} translation"
Also, arrays are useful to run workflow jobs or steps conditionally via contains():
steps:
- if: ${{ contains(inputs.languages, 'pt') }}
run: echo "Generate Portuguese translation"
- if: ${{ contains(inputs.languages, 'pt_BR') }}
run: echo "Generate Brazilian Portuguese translation"
Note: The above examples assume inputs.languages is an array, which is not currently possible.
Desired solution
Allowing arrays as input is probably quite difficult to implement, hence I am NOT asking for that.
Instead, I can already pass a string like this as input: fr de pt pt_BR, so I just need a function to split this string into an array, using space (or whatever character(s)) as the item separator.
I would use the split() function as follows to create a jobs matrix:
jobs:
generate-translations:
strategy:
matrix:
language: ${{ split(inputs.languages, ' ') }} # fr de pt pt_BR
steps:
- run: echo "Generate ${{ matrix.language }} translation"
Or to run jobs or steps conditionally:
steps:
- if: ${{ contains(split(inputs.languages, ' '), 'pt') }}
run: echo "Generate Portuguese translation"
- if: ${{ contains(split(inputs.languages, ' '), 'pt_BR') }}
run: echo "Generate Brazilian Portuguese translation"
Note: The above examples assume inputs.languages is an ordinary string, such as fr de pt pt_BR.
Additional context
There are workarounds that allow something like the above to be achieved already. However, the workarounds are sub-optimum.
Workarounds
WORKAROUND 1: Use contains() on a string instead of an array
Set inputs.languages to a string like ;fr;de;pt;pt_BR;, where ; (semicolon) is the separator.
In the workflow:
steps:
- if: ${{ contains(inputs.languages, ';pt;') }}
run: echo "Generate Portuguese translation"
- if: ${{ contains(inputs.languages, ';pt_BR;') }}
run: echo "Generate Brazilian Portuguese translation"
Caveats:
- The input string must start and end with the separator, which is unwieldy and easy to forget when typing in the
workflow_dispatch dialog.
- Space cannot be used as the separator; a leading or trailing space might be stripped by the browser or by the
workflow_dispatch dialog.
- This method only works for conditionals; it doesn't allow you to create a jobs matrix.
Notes:
- We can't do
if: ${{ contains(inputs.languages, 'pt') }} (no separators) because that would allow an input like pt_BR to trigger the (non-Brazilian) Portuguese translation.
- We can't use regex (e.g.
if: ${{ contains(inputs.languages, /(^| )pt( |$)/) }}) because it's not supported by the contains() function.
This is method we used in MuseScore Studio for our inputs.platforms variable, although in that case we were able to name the platforms carefully to make it possible to use contains() without separators.
WORKAROUND 2: Pass a JSON array as input
Set inputs.languages to a string like ["fr", "de", "pt", "pt_BR"]. (Note: this is all one string, not multiple strings.)
In the workflow, making use of the fromJSON() function:
jobs:
generate-translations:
strategy:
matrix:
language: ${{ fromJSON(inputs.languages) }}
steps:
- run: echo "Generate ${{ matrix.language }} translation"
Or:
steps:
- if: ${{ contains(fromJSON(inputs.languages), 'pt') }}
run: echo "Generate Portuguese translation"
- if: ${{ contains(fromJSON(inputs.languages), 'pt_BR') }}
run: echo "Generate Brazilian Portuguese translation"
Caveats:
- It's a pain to type JSON manually in the
workflow_dispatch dialog.
- It's easy to make mistakes, like forgetting to close quotes, or adding a comma after the final list item, etc.
WORKAROUND 3: Construct a JSON array in an earlier job or step
Set inputs.languages to a string like fr de pt pt_BR, where (space) is the separator.
In the workflow, again making use of the fromJSON() function:
jobs:
construct-json-job:
runs-on: ubuntu-slim
outputs:
languages: ${{ steps.construct-json-step.outputs.languages }}
steps:
- id: construct-json-step
run: echo "languages=$(jq -Rnc --arg str '${{ inputs.languages }}' '$str | split(" ")')" >> $GITHUB_OUTPUT
generate-translations-job:
needs: construct-json-job
runs-on: ubuntu-latest
strategy:
matrix:
language: ${{ fromJSON(needs.construct-json-job.outputs.languages) }}
steps:
- run: echo "Generate ${{ matrix.language }} translation"
Or:
steps:
- id: construct-json
run: echo "languages=$(jq -Rnc --arg str '${{ inputs.languages }}' '$str | split(" ")')" >> $GITHUB_OUTPUT
- if: ${{ contains(fromJSON(steps.construct-json.outputs.languages), 'pt') }}
run: echo "Generate Portuguese translation"
- if: ${{ contains(fromJSON(steps.construct-json.outputs.languages), 'pt_BR') }}
run: echo "Generate Brazilian Portuguese translation"
Caveats:
- This is a lot of extra code just to perform a simple string operation.
- Adding extra steps / jobs makes the build log more complicated.
- The multi-jobs method (needed to construct a matrix) involves spinning up another runner, which is a huge waste of resources in order to perform such a trivial task.
In workflows, the existing
join()function concatenates anarrayof strings to create one longstring.I want to do the opposite. I want to split a long
stringinto anarrayof shorter strings.The problem
Workflows triggered via
workflow_dispatchorworkflow_callcan acceptinputs.inputmust be of typestring,number, orboolean.array.That's a problem because arrays are needed to create a jobs matrix:
Also, arrays are useful to run workflow jobs or steps conditionally via
contains():Note: The above examples assume
inputs.languagesis an array, which is not currently possible.Desired solution
Allowing arrays as input is probably quite difficult to implement, hence I am NOT asking for that.
Instead, I can already pass a
stringlike this as input:fr de pt pt_BR, so I just need a function to split this string into an array, using space (or whatever character(s)) as the item separator.I would use the
split()function as follows to create a jobs matrix:Or to run jobs or steps conditionally:
Note: The above examples assume
inputs.languagesis an ordinary string, such asfr de pt pt_BR.Additional context
There are workarounds that allow something like the above to be achieved already. However, the workarounds are sub-optimum.
Workarounds
WORKAROUND 1: Use
contains()on a string instead of an arraySet
inputs.languagesto a string like;fr;de;pt;pt_BR;, where;(semicolon) is the separator.In the workflow:
Caveats:
workflow_dispatchdialog.workflow_dispatchdialog.Notes:
if: ${{ contains(inputs.languages, 'pt') }}(no separators) because that would allow an input likept_BRto trigger the (non-Brazilian) Portuguese translation.if: ${{ contains(inputs.languages, /(^| )pt( |$)/) }}) because it's not supported by thecontains()function.This is method we used in MuseScore Studio for our
inputs.platformsvariable, although in that case we were able to name the platforms carefully to make it possible to usecontains()without separators.WORKAROUND 2: Pass a JSON array as input
Set
inputs.languagesto a string like["fr", "de", "pt", "pt_BR"]. (Note: this is all one string, not multiple strings.)In the workflow, making use of the
fromJSON()function:Or:
Caveats:
workflow_dispatchdialog.WORKAROUND 3: Construct a JSON array in an earlier job or step
Set
inputs.languagesto a string likefr de pt pt_BR, where(space) is the separator.In the workflow, again making use of the
fromJSON()function:Or:
Caveats: