---
version: '3'

vars:
  PRETTIERIGNORE_CONFIG: .config/prettierignore
  SHELLCHECK_EXCLUDE: SC1091

tasks:
  all:
    deps:
      - eslint
      - misc
      - '{{if .REPOSITORY_TYPE eq "packer"}}packer{{else}}:donothing{{end}}'
      - '{{if .REPOSITORY_TYPE eq "python"}}python{{else}}:donothing{{end}}'
      - toml
      - xml
    log:
      start: Running all stable fixers in parallel
    cmds:
      - task: yaml:dashes

  eslint:
    deps:
      - :install:modules:local
      - :install:npm:eslint
      - :install:software:unbuffer
    desc: Fix ESLint errors automatically
    summary: |
      # Fix Errors with ESLint Automatically

      This task is an [`eslint`](https://eslint.org/) task. It will attempt to automatically fix
      `eslint` issues. It will report any issues it was unable to fix. The configuration found in
      `package.json` includes logic for fixing/linting TypeScript, JavaScript, JSON, TOML, and YAML.

      **Example using `eslint` to fix a single JS/TS file:**
      `task fix:js -- single.ts`

      **Example fixing all files in a project:**
      `task fix:js`
    vars:
      ESLINT_LOG:
        sh: mktemp
    log:
      error: ESLint found some issues that need to be fixed manually
      start: Auto-fixing with ESLint
      success: ESLint fixing appears to have corrected all the issues {{if .CLI_ARGS}}in `{{.CLI_ARGS}}`{{else}}in the project{{end}}
    cmds:
      - 'unbuffer eslint -c package.json --no-eslintrc --format {{.ESLINT_FORMATTER}} --cache --cache-location .cache/eslintcache
        --fix {{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}.{{end}} | tee {{.ESLINT_LOG}}'
      - |
        if [ '{{.ESLINT_FIX_RECURSIVE}}' == 'true' ]; then
          COUNT=0
          while [ $COUNT -le 5 ]; do
            COUNT=$((COUNT + 1))
            if grep 'potentially fixable with the `--fix` option' < {{.ESLINT_LOG}} > /dev/null; then
              COUNT="$COUNT"
              unbuffer eslint -c package.json --no-eslintrc --format {{.ESLINT_FORMATTER}} --cache --cache-location .cache/eslintcache \
              --fix {{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}.{{end}} | tee {{.ESLINT_LOG}}
            fi
          done
        fi
      - rm {{.ESLINT_LOG}}

  eslint:staged:
    deps:
      - :install:modules:local
      - :install:npm:eslint
    desc: Auto-fix only modified files with ESLint
    env:
      ESLINT_STAGED_ONLY: on
    log:
      error: Failed to auto-fix modified files with ESLint
      start: Auto-fixing modified files with ESLint
      success: Successfully auto-fixed modified files with ESLint
    cmds:
      - >
        eslint -c package.json --no-eslintrc --format {{.ESLINT_FORMATTER}} --cache
        --cache-location .cache/eslintcache --fix {{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}.{{end}}

  go:
    deps:
      - :install:software:go
      - :install:software:golangci-lint
    desc: Fix Go with all available Go fixers
    summary: |
      # Auto-Fix Go Files

      This task will attempt to auto-fix Go files with
      [golangci-lint](https://golangci-lint.run/) which runs multiple linters all
      defined in `.config/golangci-lint`.

      **Example auto-fixing an individual file:**
      `task fix:go -- path/filename.go`
    log:
      error: Encountered error while autofixing Go files
      start: Auto-fixing Go files..
      success: Successfully auto-fixed Go files
    cmds:
      - golangci-lint run -c .config/golangci.yml --fix{{if .CLI_ARGS}} {{.CLI_ARGS}}{{end}}

  js:
    cmds:
      - task: eslint

  json:
    deps:
      - :install:modules:local
      - :install:npm:eslint
    desc: Format and sort JSON
    summary: |
      # Automatically Alphabetize and Format JSON

      This task will format and organize JSON files based on the configuration stored in
      `.eslintrc.cjs`.

      **Example sorting a single JSON file:**
      `task fix:json -- single.json`

      **Example looping through project:**
      `task fix:json`
    log:
      error: '{{if .CLI_ARGS}}Manual fixing is still required for `{{.CLI_ARGS}}`{{else}}Failed to fix all project JSON issues with ESLint{{end}}'
      start: Linting JSON with ESLint
      success: ESLint has fixed all of the JSON issues {{if .CLI_ARGS}}in `{{.CLI_ARGS}}`{{else}}in the project{{end}}
    cmds:
      - eslint -c package.json --no-eslintrc --cache --cache-location .cache/eslintcache --fix
        --format pretty --ext .json {{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}.{{end}}

  markdown:
    deps:
      - :install:npm:markdown-table-formatter
    desc: Auto-fixes markdown files
    summary: |
      # Auto-Fix Markdown Files

      This task performs various tasks that cleanup markdown files. It currently:

      1. Runs `markdown-table-formatter` which fixes up markdown tables

      You can either pass a file as a CLI argument or run it without a CLI argument to
      auto-fix all the markdown files in the project that are not ignored by .gitignore.

      **Example running against whole project:**
      `task fix:markdown`

      **Example running on single file:**
      `task fix:markdown -- README.md`
    log:
      error: Error encountered while auto-fixing markdown file(s)
      start: Auto-fixing markdown file(s)
      success: Successfully finished auto-fixing markdown file(s)
    cmds:
      - >
        {{if .CLI_ARGS}}
        markdown-table-formatter {{.CLI_ARGS}}
        {{else}}
        find . -type d \( {{.IGNORE_FOLDERS}} \) -prune -o -type f \( -name '*.md' \)
        -print0 | xargs -0 -r -n1 markdown-table-formatter
        {{end}}

  misc:
    deps:
      - :install:pipx:pre-commit-hooks
    summary: |
      # Miscellaneous fixes

      This task allows you to loop through the files in the project and apply miscellaneous
      fixes. You can also specify a single file. The fixes applied are:

      1. Remove UTF-8 BOM
      2. Ensure line endings are LF

      **Example applying fixes to all files:**
      `task fix:misc`

      **Example applying fixes to single file:**
      `task fix:misc -- singlefile.js`
    log:
      error: Encountered an error while performing miscellaneous fixes{{if .CLI_ARGS}} on `{{.CLI_ARGS}}`{{end}}
      start: Performing miscellaneous fixes such as removing BOM and ensuring LF line endings
      success: Finished performing miscellaneous fixes{{if .CLI_ARGS}} on `{{.CLI_ARGS}}`{{end}}
    cmds:
      - |
        PATH="$PATH:$HOME/.local/bin"
        function misc() {
          fix-byte-order-marker "$1"
          mixed-line-ending --fix=lf "$1"
        }
        {{if .CLI_ARGS}}
          misc '{{.CLI_ARGS}}'
        {{else}}
          while read PATHH; do
            misc "$PATHH"
          done < <(find . -type d \( {{.IGNORE_FOLDERS}} \) -prune -o -type f)
        {{end}}

  packer:
    deps:
      - :install:software:packer
    desc: Automatically fix and format Packer templates
    hide: '{{ne .REPOSITORY_TYPE "packer"}}'
    summary: |
      # Automatically fix and format Packer templates

      Packer has the ability to fix old templates that are using outdated methods. This task
      loops through all the files in the root of this repository that end with `template.json`.
      For each template, the task runs `packer fix`.

      **Example applying fixes to all files matching `*template.json`:**
      `task fix:packer`

      **Example applying fixes to single file:**
      `task fix:packer -- mytemplate.json`

      For more information on `packer fix`, see [Packer's website](https://www.packer.io/docs/commands/fix).
    log:
      start: Attempting to fix Packer template{{if .CLI_ARGS}}{{else}}s{{end}}
      stop: Error running `packer fix`
      success: Successfully ran `packer fix` on the template(s)
    cmds:
      - |
        function packerFix() {
          TMP="$(mktemp)"
          packer fix "$1" > "$TMP"
          mv "$TMP" "$1"
          .config/log success "Successfully ran `packer fix` on $1"
        }
        {{if .CLI_ARGS}}
          packerFix '{{.CLI_ARGS}}'
        {{else}}
          for TEMPLATE in *template.json; do
            packerFix "$TEMPLATE"
          done
        {{end}}

  php:
    desc: Fix PHP with all available PHP auto-fixers
    summary: |
      # Auto-Fix PHP Files

      This task will attempt to auto-fix PHP files with the following auto-fixers:

      * [go]()

      If you would like to skip auto-fixing on the whole project and instead auto-fix an
      individual file, then you can do so by passing the file path as a CLI
      parameter like so:

      **Example auto-fixing an individual file:**
      `task fix:php -- path/filename.php`
    cmds:
      - task: :donothing

  prettier:
    deps:
      - :install:modules:local
      - :install:npm:prettier
    desc: Automatically format most files using Prettier
    summary: |
      # Automatically Format Files Using Prettier

      This task will run Prettier on the project. Prettier will automatically fix formatting
      mistakes like inconsistent indent lengths, trailing spaces, and more. It will use the
      configuration specified in the `package.json` file under the `prettier` key.

      If this command is incompatible with a file then you can add the file to the `.prettierignore`
      file.

      **Example of bulk fixing:**
      `task fix:formatting`

      **Example of fixing a single file**
      `task fix:formatting -- path/filename.ext`

      For more information, see [Prettier's website](https://prettier.io/).
    log:
      start: Running Prettier on {{if .CLI_ARGS}}`{{.CLI_ARGS}}`{{else}}project{{end}}
    cmds:
      - cmd: |
          prettier --ignore-path {{.PRETTIERIGNORE_CONFIG}} \
          --write {{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}.{{end}} || EXIT_CODE=$?
          if [ -n "$EXIT_CODE" ]; then
            .config/log warn 'Failed to run `prettier` - falling back to `prettier`'
            prettier --ignore-path {{.PRETTIERIGNORE_CONFIG}} \
            --write {{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}.{{end}}
          fi
        ignore_error: true

  python:
    deps:
      - :install:pipx:add-trailing-comma
      - :install:pipx:isort
      - :install:pipx:pyformat
    desc: Automatically format Python files using Black
    hide: '{{ne .REPOSITORY_TYPE "python"}}'
    summary: |
      # Automatically Format Python Files with Black

      Black is the defacto standard when it comes to autoformatting Python files. This task will
      automatically format files that end with the `.py` extension. It ignores `.py` files that are
      in any of the locations specified in the `IGNORED_FOLDERS` variable in `Taskfile.yml`.

      **Example applying fixes to all Python files:**
      `task fix:python`

      **Example applying fixes to single Python file:**
      `task fix:python -- myfile.py`

      For more information, see [Black's GitHub page](https://github.com/psf/black).
    log:
      error: Error while running `black` auto-fixer
      start: Fixing Python with `black`
      success: Fixed {{if .CLI_ARGS}}`{{.CLI_ARGS}}`{{else}}project{{end}} with `black`
    cmds:
      - |
        PATH="$PATH:$HOME/.local/bin"
        {{if .CLI_ARGS}}
          pyformat -i {{.CLI_ARGS}}
          isort --overwrite-in-place {{.CLI_ARGS}}
          add-trailing-comma --exit-zero-even-if-changed {{.CLI_ARGS}}
        {{else}}
          while read PATHH; do
            pyformat -i "$PATHH"
            isort --overwrite-in-place "$PATHH"
            add-trailing-comma --exit-zero-even-if-changed "$PATHH"
          done < <(find . -type d \( {{.IGNORE_FOLDERS}} \) -prune -o -type f \( -name '*.py' \))
        {{end}}

  shell:
    cmds:
      - task: shellcheck
      - task: prettier

  shellcheck:
    deps:
      - :install:npm:shellcheck
    desc: Automatically apply fixes to `.sh` and `.sh.j2` files using Shellcheck
    summary: |
      # Automatically apply fixes to shell scripts using Shellcheck

      Shellcheck is generally used to lint shell scripts. This task uses Shellcheck and `git apply`
      to automatically apply Shellcheck's recommendations. It only runs on files that are not in the
      `IGNORED_FOLDERS` variable in `Taskfile.yml`.

      This task is experimental. It may work to get rid of the Shellcheck lin.config/log errors but the code
      should still be tested since it might break things. Ideally, you should apply Shellcheck's
      recommendations manually.

      **To see what changes Shellcheck will make to a file named `test.sh`, for example, you can run:**
      `npx shellcheck -f diff test.sh`

      **Example applying fixes to all shell scripts:**
      `task fix:scripts`

      **Example applying fixes to a single shell script:**
      `task fix:scripts -- myfile.sh`

      For more information, see [Shellcheck's GitHub page](https://github.com/koalaman/shellcheck).
    log:
      error: Encountered an error while linting with `shellcheck` on {{if .CLI_ARGS}}`{{.CLI_ARGS}}`{{else}}project{{end}}
      start: Running `shellcheck` auto-fixer
      success: Autofixed {{if .CLI_ARGS}}`{{.CLI_ARGS}}`{{else}}project{{end}} with `shellcheck`
    cmds:
      - |
        .config/log warn 'This is an experimental fix method - please manually inspect and test the scripts'
        {{if .CLI_ARGS}}
          shellcheck -e {{.SHELLCHECK_EXCLUDE}} -f diff {{.CLI_ARGS}} | git apply
        {{else}}
          find . -type d \( {{.IGNORE_FOLDERS}} \) -prune -o -type f \( -name '*.sh' -o -name '*.sh.j2' \) \
            -print0 | xargs -0 shellcheck -e {{.SHELLCHECK_EXCLUDE}} -f diff | git apply
        {{end}}

  spelling:markdown:
    deps:
      - :install:go:misspell
    desc: Auto-fix spelling errors in markdown files using `misspell`
    log:
      error: Encountered error while attempting to auto-correct spelling mistakes with `misspell`
      start: Auto-fixing using the `misspell` spell-check engine
      success: Successfully auto-fixed files using `misspell`
    cmds:
      - >
        {{if .CLI_ARGS}}
        misspell -w {{.CLI_ARGS}}
        {{else}}
        find . -type d \( {{.IGNORE_FOLDERS}} \) -prune -o -type f \( -name '*.md' \)
        -print0 | xargs -0 -r -n1 misspell -w
        {{end}}

  toml:
    deps:
      - :install:pipx:toml-sort
    desc: Format `.toml` files by alphabetizing and flattening
    summary: |
      # Automatically format `.toml` files

      [toml-sort](https://pypi.org/project/toml-sort/) will automatically alphabetize and flatten
      `.toml` files.

      **Example running on whole project:**
      `task fix:toml`

      **Example usage on specific file:**
      `task fix:toml -- pyproject.toml`
    log:
      error: Encountered an error while running `toml-sort`
      start: Running `toml-sort` auto-fixer
      success: Successfully ran the `toml-sort` auto-fixer
    cmds:
      - |
        PATH="$PATH:$HOME/.local/bin"
        {{if .CLI_ARGS}}
        if [ -f '{{.CLI_ARGS}}' ]; then
          toml-sort -i --all {{.CLI_ARGS}}
        fi
        {{else}}
          while read PATHH; do
            toml-sort -i --all "$PATHH"
          done < <(find . -type d \( {{.IGNORE_FOLDERS}} \) -prune -o -type f \( -name '*.toml' \))
        {{end}}

  xml:
    deps:
      - :install:npm:eslint
    summary: |
      # Autofix XML files

      This task will automatically apply updates to XML files. It uses Prettier and the Prettier plugin
      named `@prettier/plugin-xml` for auto-formatting.

      **Example fixing all `.xml` files:**
      `task fix:xml`

      **Example fixing specific `.xml` file(s):**
      `task fix:xml -- 'file_name.xml'`
    log:
      error: Errors were reported by ESLint when auto-fixing XML
      start: Attempting to auto-fix any XML files with ESLint
      success: Any XML files present in the project were successfully fixed/validated by ESLint
    cmds:
      - eslint -c package.json --no-eslintrc --cache --cache-location .cache/eslintcache --fix
        --format pretty --ext .xml {{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}.{{end}}

  yaml:
    desc: Auto-format and sort YML files
    summary: |
      # Auto-Format and Sort YML files

      This task will automatically apply updates to YML files. It currently performs the following
      tasks:

      1. Ensures the first line of the file is `---`
      2. Sorts the file according to the layout specified by `.eslintrc.cjs`

      **Example usage:**
      `task fix:yaml`

      **Example fixing single file:**
      `task fix:yaml -- filename.yml`
    cmds:
      - task: yaml:dashes
      - task: yaml:order

  yaml:dashes:
    summary: |
      # Ensures YML files start with a "---"

      This task will add a `---` as the first line of YML files if it is missing. It only works
      on one YML file at a time. This task is intended to be used with lint-staged (with settings)
      normally found in the `package.json` file. You must specify the path/filename to the YML
      file you wish to fix with this task. If the file does not exist or if the file does not end
      with `.yml` or `yaml` then an error will be reported.

      This task is mainly intended to be used with `lint-staged`.

      **Example usage:**
      `task fix:yaml:dashes`

      **Example usage for one specific file:**
      `task fix:yaml:dashes -- path/filename.yml`
    log:
      error: Error encountered while ensuring YML dashes
      start: Ensuring {{if .CLI_ARGS}}`{{.CLI_ARGS}}`{{else}}project{{end}} has YML files that start with `---`
      success: Successfully ensured {{if .CLI_ARGS}}`{{.CLI_ARGS}}`{{else}}project{{end}} has YML files that start with `---`
    cmds:
      - |
        function yamlDashes() {
          if test -f "$1" && [[ "`head -c 14 "$1"`" != '$ANSIBLE_VAULT' ]] && [[ "`head -c 3 "$1"`" != '---' ]]; then
            TMP=$(mktemp)
            echo '---' | cat - "$1" > "$TMP"
            mv "$TMP" "$1"
          fi
        }
        {{if .CLI_ARGS}}
          yamlDashes '{{.CLI_ARGS}}'
        {{else}}
          while read YML_FILE; do
            yamlDashes "$YML_FILE"
          done < <(find . -type d \( {{.IGNORE_FOLDERS}} \) -prune -o -type f \( -name '*.yml' -o -name '*.yaml' \))
        {{end}}

  yaml:order:
    deps:
      - :install:modules:local
      - :install:npm:eslint
    summary: |
      # Ensure YML files are sorted properly

      This task will sort YML keys alphabetically and also obey the order specified in `.eslintrc.cjs`.
      It uses [ESLint](https://eslint.org/) along with the [`eslint-plugin-yml`](https://ota-meshi.github.io/eslint-plugin-yml/)
      plugin.

      By default, the plugin will automatically fix all YML files but you can also pass in a single file or glob
      that needs to be fixed.

      **Example fixing all `.yml` files:**
      `task fix:yaml:order`

      **Example fixing specific `.yml` file(s):**
      `task fix:yaml:order -- 'file_name.{yml,yaml}'`
    log:
      error: There are still some errors. Try running the command again.
      start: Ensuring YML file(s) are in order specified in configuration
      success: Successfully ensured YML file order
    cmds:
      - eslint -c package.json --no-eslintrc --format pretty --fix --cache
        --cache-location .cache/eslintcache --ext yml,yaml {{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}.{{end}}
