# Reusable workflow for drawing and committing an automated keymap diagram # for ZMK config repos using https://github.com/caksoylar/keymap-drawer name: Draw ZMK keymaps on: workflow_call: inputs: keymap_patterns: description: 'Path specification for keymaps to be parsed' type: string default: 'config/*.keymap' config_path: description: 'Path to the keymap-drawer configuration file, ignored if non-existent' type: string default: 'keymap_drawer.config.yaml' west_config_path: description: 'Path to the folder containing west.yml, e.g. `config`. Set to empty to skip fetching modules from west.yml' type: string default: 'config' output_folder: description: 'Output folder for SVG and YAML files' type: string default: 'keymap-drawer' json_path: description: 'Path containing .json physical layout description files, ignored if non-existent' type: string default: 'config' parse_args: description: "Map of keyboard names to extra `keymap parse` args, e.g. `corne:'--layer-names Def Lwr Rse Fun'`" type: string draw_args: description: "Map of keyboard names to extra `keymap draw` args, e.g. `corne:'-k corne_rotated -l LAYOUT_split_3x5_3'`" type: string extra_keymap_yaml: description: "Map of keyboard names to extra keymap YAMLs to be passed to `keymap draw` after the parsed one, e.g. `corne:'keymap-drawer/extra_combos.yaml'`" type: string commit_message: description: 'Commit message for updated images. Ignored if `amend_commit` is `true`.' type: string default: 'keymap-drawer render' amend_commit: description: 'Whether to amend the last commit instead of creating a new one. Make sure you understand the implications of rewriting the branch history if you use this option!' type: boolean install_branch: description: 'Install keymap-drawer from a git branch, use empty to install from a pypi release obeying `install_version` (default)' type: string install_repo: description: 'Install keymap-drawer from a different git remote, primarily for testing changes using a keymap-drawer fork. Ignored if `install_branch` is unset/empty.' type: string default: 'https://github.com/caksoylar/keymap-drawer.git' install_version: description: 'Install keymap-drawer from pypi using the specified version, use empty for latest pypi release (default)' type: string destination: description: 'Add the output files to a commit, as artifacts or both, values: `commit`, `artifact`, `both`' type: string default: 'commit' artifact_name: description: 'Name of the produced artifact containing SVG and YAML outputs. Ignored if `destination` is `commit`.' type: string default: 'drawings' fail_on_error: description: 'Fail the action if an error occurs during parse/draw' type: boolean debug_mode: description: 'Enable debug mode' type: boolean outputs: drawings: description: 'Archive with keymap in YAML and drawing in SVG formats' value: ${{ jobs.draw.outputs.drawings }} jobs: draw: runs-on: ubuntu-latest outputs: drawings: ${{ steps.artifact-upload-step.outputs.artifact-id }} steps: - name: Checkout uses: actions/checkout@v6 with: # So the reference to the parent commit is available when amending # See: # - https://github.com/stefanzweifel/git-auto-commit-action#using---amend-and---no-edit-as-commit-options # - https://github.com/stefanzweifel/git-auto-commit-action/issues/159#issuecomment-845347950 # - https://github.com/actions/checkout fetch-depth: ${{ (inputs.amend_commit == true && 2) || 1 }} submodules: recursive - name: Install keymap-drawer (pypi) if: inputs.install_branch == '' run: | [ -n "${{ inputs.install_version }}" ] && version="==${{ inputs.install_version }}" || version="" pipx install "keymap-drawer$version" - name: Install keymap-drawer (git) if: inputs.install_branch != '' run: pipx install "git+${{ inputs.install_repo }}@${{ inputs.install_branch }}" - name: Install west if: inputs.west_config_path != '' run: pipx install west - name: Fetch west modules if: inputs.west_config_path != '' run: | west init -l ${{ inputs.west_config_path }} west config --local manifest.project-filter " -zmk,-zephyr" west update --fetch-opt=--filter=tree:0 - name: Draw keymaps id: draw continue-on-error: ${{ !inputs.fail_on_error }} run: | [ "${{ inputs.debug_mode }}" == "true" ] && set -x get_args() { local keyboard=$2 local output=() eval set -- "$1" for arg; do local key=${arg%%:*} local val=${arg#*:} if [ "$key" = "$keyboard" ]; then output+=("$val") break fi done echo "${output[@]}" } check_for_layout_args() { local args=("$@") for val in "-j" "--qmk-info-json" "-k" "--qmk-keyboard" "-z" "--zmk-keyboard" "-d" "--dts-layout" "--ortho-layout" "-n" "--cols-thumbs-notation"; do for item in "${args[@]}"; do if [ "$val" = "$item" ]; then return 0; fi done done return 1 } shopt -s nullglob extglob artifacts=() error_occurred=0 mkdir -p "${{ inputs.output_folder }}" config_path="${{ inputs.config_path }}" [ -e "$config_path" ] && config_arg=(-c "$config_path") || config_arg=() echo "INFO: using config args:" "${config_arg[@]}" [ "${{ inputs.debug_mode }}" == "true" ] && debug_arg="-d" || debug_arg="" for keymap_file in ${{ inputs.keymap_patterns }}; do keyboard=$(basename -s .keymap "$keymap_file") echo "INFO: drawing for $keyboard" IFS=" " read -r -a parse_args <<< "$(get_args "${{ inputs.parse_args }}" "$keyboard")" echo "INFO: got extra parse args:" "${parse_args[@]}" IFS=" " read -r -a draw_args <<< "$(get_args "${{ inputs.draw_args }}" "$keyboard")" echo "INFO: got extra draw args:" "${draw_args[@]}" IFS=" " read -r -a extra_keymap_yaml <<< "$(get_args "${{ inputs.extra_keymap_yaml }}" "$keyboard")" echo "INFO: got extra keymap YAML args:" "${extra_keymap_yaml[@]}" json_path="${{ inputs.json_path }}" if check_for_layout_args "${draw_args[@]}"; then echo "INFO: found layout specifiers in draw args" elif [ -n "$json_path" ] && [ -f "$json_path/${keyboard}.json" ]; then echo "INFO: found $json_path/${keyboard}.json"; draw_args+=(-j "$json_path/${keyboard}.json") else dts_candidates=({,*/}boards/*/*/${keyboard}-layout{,s}.dtsi) if [ ${#dts_candidates[@]} -gt 0 ]; then echo "INFO: found ${dts_candidates[0]}"; draw_args+=(-d "${dts_candidates[0]}") fi fi tmp_yaml=$(mktemp) tmp_svg=$(mktemp) if keymap $debug_arg "${config_arg[@]}" parse -z "$keymap_file" "${parse_args[@]}" >"$tmp_yaml"; then mv "$tmp_yaml" "${{ inputs.output_folder }}/$keyboard.yaml" artifacts+=("${{ inputs.output_folder }}/$keyboard.yaml") else echo "ERROR: parsing failed for $keyboard!" error_occurred=1 continue fi if keymap $debug_arg "${config_arg[@]}" draw "${{ inputs.output_folder }}/$keyboard.yaml" "${extra_keymap_yaml[@]}" "${draw_args[@]}" >"$tmp_svg"; then mv "$tmp_svg" "${{ inputs.output_folder }}/$keyboard.svg" artifacts+=("${{ inputs.output_folder }}/$keyboard.svg") else echo "ERROR: drawing failed for $keyboard!" error_occurred=1 fi done joined_artifacts=$(printf '"%s", ' "${artifacts[@]}") printf 'artifacts=[%s]\n' "${joined_artifacts%, }" >> "$GITHUB_OUTPUT" if [ $error_occurred -eq 1 ]; then exit 1 fi - name: Get last commit message id: last_commit_message if: inputs.amend_commit == true && (inputs.destination == 'commit' || inputs.destination == 'both') run: | echo "msg=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT - name: Commit updated images if: ( inputs.destination == 'commit' || inputs.destination == 'both' ) uses: stefanzweifel/git-auto-commit-action@v7 with: file_pattern: '${{ inputs.output_folder }}/*.svg ${{ inputs.output_folder }}/*.yaml' # So the previous commit is amended instead of creating a new one when desired # See: # - https://github.com/stefanzweifel/git-auto-commit-action#using---amend-and---no-edit-as-commit-options # - https://github.com/stefanzweifel/git-auto-commit-action/issues/159#issuecomment-845347950 # - https://github.com/actions/checkout commit_message: '${{ (inputs.amend_commit == true && steps.last_commit_message.outputs.msg) || inputs.commit_message }}' commit_options: "${{ (inputs.amend_commit == true && '--amend --no-edit') || '' }}" push_options: "${{ (inputs.amend_commit == true && '--force-with-lease') || '' }}" skip_fetch: ${{ inputs.amend_commit == true }} - name: Artifact upload id: artifact-upload-step if: ( inputs.destination == 'artifact' || inputs.destination == 'both' ) uses: actions/upload-artifact@v7 with: name: '${{ inputs.artifact_name }}' path: | ${{ join(fromJSON(steps.draw.outputs.artifacts), ' ') }} - name: Check job success if: ${{ !inputs.fail_on_error }} run: | if [ "${{ steps.draw.outcome }}" == "failure" ]; then echo "The draw step failed for some keymaps, please check the logs of that step above!" exit 1 fi