diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml
index 88f6694..5f319b9 100644
--- a/.github/workflows/actionlint.yml
+++ b/.github/workflows/actionlint.yml
@@ -3,10 +3,10 @@ name: "Actionlint"
on: # yamllint disable-line rule:truthy
push:
pull_request:
+ workflow_dispatch:
jobs:
Actionlint:
runs-on: "ubuntu-24.04"
steps:
- uses: "actions/checkout@v4"
- - name: "actionlint"
- uses: "raven-actions/actionlint@v2"
+ - uses: "raven-actions/actionlint@v2"
diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
index 955bf11..a302318 100644
--- a/.github/workflows/audit.yml
+++ b/.github/workflows/audit.yml
@@ -5,20 +5,26 @@ on: # yamllint disable-line rule:truthy
pull_request:
schedule:
- cron: "0 0 * * 1"
+ workflow_dispatch:
jobs:
Audit:
runs-on: "ubuntu-24.04"
+ env:
+ SNYK_TOKEN: "${{ secrets.SNYK_TOKEN }}"
steps:
+ - uses: "actions/checkout@v4"
- uses: actions/setup-go@v5
with:
- go-version: '1.23.3'
- - uses: "actions/checkout@v4"
- - run: "sudo apt-get update"
- - run: "sudo apt-get install -y curl python3-venv"
- - run: "curl -LO https://downloads.snyk.io/cli/stable/snyk-linux"
- - run: "sudo cp snyk-linux /bin/snyk"
- - run: "sudo chmod a+x /bin/snyk"
- - run: "./install"
- - run: "./build audit"
- env:
- SNYK_TOKEN: "${{ secrets.SNYK_TOKEN }}"
+ go-version: "1.24.0"
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+ - uses: actions-rust-lang/setup-rust-toolchain@v1
+ with:
+ toolchain: "1.75.0"
+ - name: "install snyk"
+ run: |
+ curl -LO https://downloads.snyk.io/cli/stable/snyk-linux
+ sudo cp snyk-linux /bin/snyk
+ sudo chmod a+x /bin/snyk
+ - run: "make audit"
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 92a1165..d323bbb 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -3,19 +3,24 @@ name: "Lint"
on: # yamllint disable-line rule:truthy
push:
pull_request:
+ workflow_dispatch:
jobs:
Lint:
runs-on: "ubuntu-24.04"
steps:
+ - uses: "actions/checkout@v4"
- uses: actions/setup-go@v5
with:
- go-version: '1.23.3'
- - run: "echo \"${HOME}/bin\" >> \"$GITHUB_PATH\""
- - uses: "actions/checkout@v4"
- - run: "sudo apt-get update"
- - run: "sudo apt-get install -y bash curl python3-venv shellcheck zsh"
- - run: "mkdir -p \"$HOME/bin\""
- - run: "curl -Lo \"$HOME/bin/kirill\" https://raw.githubusercontent.com/mcandre/kirill/v0.0.1/bin/kirill"
- - run: "chmod +x \"$HOME/bin/kirill\""
- - run: "./install"
- - run: "./build lint"
+ go-version: "1.24.0"
+ - name: "install shell linting accessories"
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y shellcheck zsh
+ - name: "install kirill"
+ run: |
+ echo "${HOME}/bin" >>"$GITHUB_PATH"
+ mkdir -p "$HOME/bin"
+ curl -Lo "$HOME/bin/kirill" https://raw.githubusercontent.com/mcandre/kirill/v0.0.1/bin/kirill
+ chmod +x "$HOME/bin/kirill"
+ - run: "make -f install.mk -j 4"
+ - run: "make lint"
diff --git a/.github/workflows/rubberstamp.yml b/.github/workflows/rubberstamp.yml
index 15064c4..c933088 100644
--- a/.github/workflows/rubberstamp.yml
+++ b/.github/workflows/rubberstamp.yml
@@ -1,6 +1,6 @@
---
#
-# Rubberstamp
+# Rubberstamp v0.0.2
# https://github.com/mcandre/rubberstamp
#
name: "Rubberstamp"
@@ -12,22 +12,28 @@ on: # yamllint disable-line rule:truthy
# - cron: "*/5 * * * *"
# Once a month
- cron: "0 0 1 * *"
+ workflow_dispatch:
jobs:
Audit:
runs-on: "ubuntu-24.04"
+ env:
+ RUBBERSTAMP_GH_REPO: "${{ github.repository }}"
+ SSH_KEY: "${{ secrets.SSH_KEY }}"
steps:
- uses: "actions/checkout@v4"
- - run: "sudo apt-get update"
- - run: "sudo apt-get install -y git"
- - run: "date -u >.rubberstamp"
- - run: "git remote set-url origin \"git@github.com:${RUBBERSTAMP_GH_REPO}.git\""
- env:
- RUBBERSTAMP_GH_REPO: "${{ github.repository }}"
- - run: "git remote get-url origin"
- - run: "git config --global user.email 'rubberstamp@rubberstamp.test'"
- - run: "git config --global user.name 'Rubberstamp'"
- - run: "git add .rubberstamp"
- - run: "git commit -am 'rubberstamp'"
- - run: "echo \"$SSH_KEY\" >/tmp/key && chmod 0600 /tmp/key && git -c 'core.sshCommand=ssh -i /tmp/key' push"
- env:
- SSH_KEY: "${{ secrets.SSH_KEY }}"
+ - name: "provision git"
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y git
+ git config --global user.email 'rubberstamp@rubberstamp.test'
+ git config --global user.name 'Rubberstamp'
+ git config --global core.sshCommand 'ssh -i /tmp/key'
+ echo "$SSH_KEY" >/tmp/key
+ chmod 0600 /tmp/key
+ git remote set-url origin "git@github.com:${RUBBERSTAMP_GH_REPO}.git"
+ - name: "stamp"
+ run: |
+ date -u >.rubberstamp
+ git add .rubberstamp
+ git commit -m 'rubberstamp'
+ git push
diff --git a/.tool-versions b/.tool-versions
index 7b897d1..d866b73 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,3 @@
golang 1.23.3
python 3.12.1
+rust 1.75.0
diff --git a/README.md b/README.md
index 06b432c..f51f925 100755
--- a/README.md
+++ b/README.md
@@ -47,20 +47,23 @@ BSD-2-Clause
# REQUIREMENTS
-* GNU or BSD [findutils](https://en.wikipedia.org/wiki/Find_(Unix))html)
+* [GNU](https://www.gnu.org/)/[BSD](https://en.wikipedia.org/wiki/Berkeley_Software_Distribution) [findutils](https://en.wikipedia.org/wiki/Find_(Unix))
* POSIX compatible [grep](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html)
-* POSIX compatible [sh](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html)
-* [ShellCheck](https://www.shellcheck.net/) 0.10.0+
-* [Go](https://go.dev/) 1.23.3+
+* [Go](https://go.dev/) 1.24.0+
* [kirill](https://github.com/mcandre/kirill) 0.0.1
+* POSIX compatible [make](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/make.html)
* [Python](https://www.python.org/) 3.12.1+
+* [Rust](https://www.rust-lang.org/) 1.75.0+
+* POSIX compatible [sh](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html)
+* [ShellCheck](https://www.shellcheck.net/) 0.10.0+
* [Snyk](https://snyk.io/)
-* Provision additional dev tools with `./install`
+* Provision additional dev tools with `make -f install.mk [-j 4]`
## Recommended
* [ASDF](https://asdf-vm.com/) 0.10 (run `asdf reshim` after provisioning)
* [direnv](https://direnv.net/) 2
+* [GNU](https://www.gnu.org/)/[BSD](https://en.wikipedia.org/wiki/Berkeley_Software_Distribution) make
# TERMINAL COLORS
diff --git a/build b/build
deleted file mode 100755
index a630de6..0000000
--- a/build
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/sh
-unset IFS
-set -euf
-
-DEFAULT_TASK='lint'
-
-audit() {
- snyk
-}
-
-lint() {
- bashate
- funk
- kirill
- shellcheck
- shfmt
- slick
-}
-
-bashate() {
- stank -print0 -exInterp zsh . |
- xargs -0 -n 1 .venv/bin/bashate -i E006
-}
-
-funk() {
- command funk .
-}
-
-kirill() {
- command kirill -print0 . |
- xargs -0 -n 1 -t jq -r input_filename
-}
-
-shellcheck() {
- stank -print0 -exInterp zsh . |
- xargs -0 -n 1 shellcheck
-}
-
-shfmt() {
- stank -print0 -exInterp zsh . |
- xargs -0 -n 1 shfmt -w -i 4
-}
-
-slick() {
- stank -print0 -sh . |
- xargs -0 -n 1 slick
-}
-
-snyk() {
- command snyk test --all-projects --exclude=requirements.txt
- command snyk test --command=.venv/bin/python3
-}
-
-if [ "$#" -eq 0 ]; then
- "$DEFAULT_TASK"
- exit
-fi
-
-for ARG in "$@"; do
- "$ARG"
-done
diff --git a/go.mod b/go.mod
index 93a304b..9da61ed 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,25 @@
module github.com/mcandre/dotfiles
-go 1.23
+go 1.24
+
+tool (
+ github.com/mcandre/slick/cmd/slick
+ github.com/mcandre/stank
+ github.com/mcandre/stank/cmd/funk
+ github.com/mcandre/stank/cmd/stank
+ github.com/mcandre/stank/cmd/stink
+ mvdan.cc/sh/v3/cmd/shfmt
+)
+
+require (
+ github.com/google/renameio/v2 v2.0.0 // indirect
+ github.com/magefile/mage v1.14.0 // indirect
+ github.com/mcandre/mage-extras v0.0.20 // indirect
+ github.com/mcandre/slick v0.0.10 // indirect
+ github.com/mcandre/stank v0.0.31 // indirect
+ github.com/rogpeppe/go-internal v1.13.1 // indirect
+ golang.org/x/sys v0.26.0 // indirect
+ golang.org/x/term v0.25.0 // indirect
+ mvdan.cc/editorconfig v0.3.0 // indirect
+ mvdan.cc/sh/v3 v3.10.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..d7f05ed
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,30 @@
+github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
+github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
+github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
+github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
+github.com/mcandre/mage-extras v0.0.20 h1:hUw33CYkuyByVQW7eNv9E0gjTk2SMZ5Y8AWHdD4aVHM=
+github.com/mcandre/mage-extras v0.0.20/go.mod h1:zr+/cO9v8EtPmjHMVYx7ijYrbT3tfJSNgXc9ze6LJ4s=
+github.com/mcandre/slick v0.0.10 h1:VQaUwecRGcsfgxQR3w6FhZXlc+/wL2GMnl0l6DnjJCo=
+github.com/mcandre/slick v0.0.10/go.mod h1:UC5mrxVDxtKnj9vI3ZloSBKVL8U0clqr7LtoQ9w8W10=
+github.com/mcandre/stank v0.0.31 h1:x2+qkAbDiAPBjKAIy5VZl9CBlpzpVWchHlx2erX2Qww=
+github.com/mcandre/stank v0.0.31/go.mod h1:ilkqyvNlj7W5HORZhdzFuEMTSyNP4chfL9aVUq5KECA=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
+golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
+golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
+golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
+mvdan.cc/editorconfig v0.3.0 h1:D1D2wLYEYGpawWT5SpM5pRivgEgXjtEXwC9MWhEY0gQ=
+mvdan.cc/editorconfig v0.3.0/go.mod h1:NcJHuDtNOTEJ6251indKiWuzK6+VcrMuLzGMLKBFupQ=
+mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
+mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
diff --git a/install b/install
deleted file mode 100755
index 34d995e..0000000
--- a/install
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-unset IFS
-set -euf
-
-go install github.com/mcandre/slick/cmd/slick@v0.0.10
-go install github.com/mcandre/stank/...@v0.0.30
-go install mvdan.cc/sh/v3/cmd/shfmt@v3.7.0
-go mod tidy
-
-python3 -m venv .venv
-.venv/bin/python3 -m pip install --upgrade pip setuptools
-.venv/bin/pip3 install -r requirements.txt
diff --git a/install.mk b/install.mk
new file mode 100644
index 0000000..66a3083
--- /dev/null
+++ b/install.mk
@@ -0,0 +1,20 @@
+.POSIX:
+.SILENT:
+.PHONY: \
+ all \
+ go \
+ python \
+ rust
+
+all: go python rust
+
+go:
+ go install tool
+
+python:
+ python3 -m venv .venv
+ .venv/bin/python3 -m pip install --upgrade pip setuptools
+ .venv/bin/pip3 install -r requirements.txt
+
+rust:
+ cargo install --force unmake@0.0.17
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..3ed7e6d
--- /dev/null
+++ b/makefile
@@ -0,0 +1,56 @@
+.POSIX:
+.SILENT:
+.PHONY: \
+ all \
+ audit \
+ bashate \
+ funk \
+ kirill \
+ lint \
+ shellcheck \
+ shfmt \
+ slick \
+ snyk \
+ unmake
+
+all: lint
+
+audit:
+ snyk
+
+lint: bashate \
+ funk \
+ kirill \
+ shellcheck \
+ shfmt \
+ slick
+
+bashate:
+ stank -print0 -exInterp zsh . | \
+ xargs -0 -n 1 .venv/bin/bashate -i E006
+
+funk:
+ funk .
+
+kirill:
+ kirill -print0 . | \
+ xargs -0 -n 1 -t jq -r input_filename
+
+shellcheck:
+ stank -print0 -exInterp zsh . | \
+ xargs -0 -n 1 shellcheck
+
+shfmt:
+ stank -print0 -exInterp zsh . | \
+ xargs -0 -n 1 shfmt -w -i 4
+
+slick:
+ stank -print0 -sh . | \
+ xargs -0 -n 1 slick
+
+snyk:
+ snyk test --all-projects --exclude=requirements.txt
+ snyk test --command=.venv/bin/python3
+
+unmake:
+ unmake .
diff --git a/vendor/github.com/google/renameio/v2/.golangci.yml b/vendor/github.com/google/renameio/v2/.golangci.yml
new file mode 100644
index 0000000..abfb6ca
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/.golangci.yml
@@ -0,0 +1,5 @@
+linters:
+ disable:
+ - errcheck
+ enable:
+ - gofmt
diff --git a/vendor/github.com/google/renameio/v2/CONTRIBUTING.md b/vendor/github.com/google/renameio/v2/CONTRIBUTING.md
new file mode 100644
index 0000000..939e534
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/CONTRIBUTING.md
@@ -0,0 +1,28 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement. You (or your employer) retain the copyright to your contribution;
+this simply gives us permission to use and redistribute your contributions as
+part of the project. Head over to to see
+your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
+
+## Community Guidelines
+
+This project follows [Google's Open Source Community
+Guidelines](https://opensource.google.com/conduct/).
diff --git a/vendor/github.com/google/renameio/v2/LICENSE b/vendor/github.com/google/renameio/v2/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/google/renameio/v2/README.md b/vendor/github.com/google/renameio/v2/README.md
new file mode 100644
index 0000000..703884c
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/README.md
@@ -0,0 +1,74 @@
+[](https://github.com/google/renameio/actions?query=workflow%3ATest)
+[](https://pkg.go.dev/github.com/google/renameio)
+[](https://goreportcard.com/report/github.com/google/renameio)
+
+The `renameio` Go package provides a way to atomically create or replace a file or
+symbolic link.
+
+## Atomicity vs durability
+
+`renameio` concerns itself *only* with atomicity, i.e. making sure applications
+never see unexpected file content (a half-written file, or a 0-byte file).
+
+As a practical example, consider https://manpages.debian.org/: if there is a
+power outage while the site is updating, we are okay with losing the manpages
+which were being rendered at the time of the power outage. They will be added in
+a later run of the software. We are not okay with having a manpage replaced by a
+0-byte file under any circumstances, though.
+
+## Advantages of this package
+
+There are other packages for atomically replacing files, and sometimes ad-hoc
+implementations can be found in programs.
+
+A naive approach to the problem is to create a temporary file followed by a call
+to `os.Rename()`. However, there are a number of subtleties which make the
+correct sequence of operations hard to identify:
+
+* The temporary file should be removed when an error occurs, but a remove must
+ not be attempted if the rename succeeded, as a new file might have been
+ created with the same name. This renders a throwaway `defer
+ os.Remove(t.Name())` insufficient; state must be kept.
+
+* The temporary file must be created on the same file system (same mount point)
+ for the rename to work, but the TMPDIR environment variable should still be
+ respected, e.g. to direct temporary files into a separate directory outside of
+ the webserver’s document root but on the same file system.
+
+* On POSIX operating systems, the
+ [`fsync`](https://manpages.debian.org/stretch/manpages-dev/fsync.2) system
+ call must be used to ensure that the `os.Rename()` call will not result in a
+ 0-length file.
+
+This package attempts to get all of these details right, provides an intuitive,
+yet flexible API and caters to use-cases where high performance is required.
+
+## Major changes in v2
+
+With major version renameio/v2, `renameio.WriteFile` changes the way that
+permissions are handled. Before version 2, files were created with the
+permissions passed to the function, ignoring the
+[umask](https://en.wikipedia.org/wiki/Umask). From version 2 onwards, these
+permissions are further modified by process' umask (usually the user's
+preferred umask).
+
+If you were relying on the umask being ignored, add the
+`renameio.IgnoreUmask()` option to your `renameio.WriteFile` calls when
+upgrading to v2.
+
+## Windows support
+
+It is [not possible to reliably write files atomically on
+Windows](https://github.com/golang/go/issues/22397#issuecomment-498856679), and
+[`chmod` is not reliably supported by the Go standard library on
+Windows](https://github.com/google/renameio/issues/17).
+
+As it is not possible to provide a correct implementation, this package does not
+export any functions on Windows.
+
+## Disclaimer
+
+This is not an official Google product (experimental or otherwise), it
+is just code that happens to be owned by Google.
+
+This project is not affiliated with the Go project.
diff --git a/vendor/github.com/google/renameio/v2/doc.go b/vendor/github.com/google/renameio/v2/doc.go
new file mode 100644
index 0000000..67416df
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/doc.go
@@ -0,0 +1,21 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package renameio provides a way to atomically create or replace a file or
+// symbolic link.
+//
+// Caveat: this package requires the file system rename(2) implementation to be
+// atomic. Notably, this is not the case when using NFS with multiple clients:
+// https://stackoverflow.com/a/41396801
+package renameio
diff --git a/vendor/github.com/google/renameio/v2/maybe/doc.go b/vendor/github.com/google/renameio/v2/maybe/doc.go
new file mode 100644
index 0000000..2ab10e4
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/maybe/doc.go
@@ -0,0 +1,3 @@
+// Package maybe provides a way to atomically create or replace a file, if
+// technically possible.
+package maybe
diff --git a/vendor/github.com/google/renameio/v2/maybe/maybe_unix.go b/vendor/github.com/google/renameio/v2/maybe/maybe_unix.go
new file mode 100644
index 0000000..781c22a
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/maybe/maybe_unix.go
@@ -0,0 +1,26 @@
+//go:build !windows
+// +build !windows
+
+package maybe
+
+import (
+ "os"
+
+ "github.com/google/renameio/v2"
+)
+
+// WriteFile mirrors ioutil.WriteFile. On Linux it uses renameio.WriteFile to
+// create or replace an existing file with the same name atomically. On Windows
+// files cannot be written atomically, so this function falls back to
+// ioutil.WriteFile, which does not write the file atomically and ignores most
+// permission bits. See https://github.com/google/renameio/issues/1 and
+// https://github.com/golang/go/issues/22397#issuecomment-498856679 for
+// discussion.
+//
+// Prefer using renameio.WriteFile instead so that you get an error if atomic
+// replacement is not possible on the runtime platform. maybe.WriteFile is meant
+// as a convenience wrapper if you are okay with atomic replacement not being
+// supported by the runtime platform.
+func WriteFile(filename string, data []byte, perm os.FileMode) error {
+ return renameio.WriteFile(filename, data, perm)
+}
diff --git a/vendor/github.com/google/renameio/v2/maybe/maybe_windows.go b/vendor/github.com/google/renameio/v2/maybe/maybe_windows.go
new file mode 100644
index 0000000..2cb2a16
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/maybe/maybe_windows.go
@@ -0,0 +1,22 @@
+package maybe
+
+import (
+ "io/ioutil"
+ "os"
+)
+
+// WriteFile mirrors ioutil.WriteFile. On Linux it uses renameio.WriteFile to
+// create or replace an existing file with the same name atomically. On Windows
+// files cannot be written atomically, so this function falls back to
+// ioutil.WriteFile, which does not write the file atomically and ignores most
+// permission bits. See https://github.com/google/renameio/issues/1 and
+// https://github.com/golang/go/issues/22397#issuecomment-498856679 for
+// discussion.
+//
+// Prefer using renameio.WriteFile instead so that you get an error if atomic
+// replacement is not possible on the runtime platform. maybe.WriteFile is meant
+// as a convenience wrapper if you are okay with atomic replacement not being
+// supported by the runtime platform.
+func WriteFile(filename string, data []byte, perm os.FileMode) error {
+ return ioutil.WriteFile(filename, data, perm)
+}
diff --git a/vendor/github.com/google/renameio/v2/option.go b/vendor/github.com/google/renameio/v2/option.go
new file mode 100644
index 0000000..f825f6c
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/option.go
@@ -0,0 +1,79 @@
+// Copyright 2021 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build !windows
+// +build !windows
+
+package renameio
+
+import "os"
+
+// Option is the interface implemented by all configuration function return
+// values.
+type Option interface {
+ apply(*config)
+}
+
+type optionFunc func(*config)
+
+func (fn optionFunc) apply(cfg *config) {
+ fn(cfg)
+}
+
+// WithTempDir configures the directory to use for temporary, uncommitted
+// files. Suitable for using a cached directory from
+// TempDir(filepath.Base(path)).
+func WithTempDir(dir string) Option {
+ return optionFunc(func(cfg *config) {
+ cfg.dir = dir
+ })
+}
+
+// WithPermissions sets the permissions for the target file while respecting
+// the umask(2). Bits set in the umask are removed from the permissions given
+// unless IgnoreUmask is used.
+func WithPermissions(perm os.FileMode) Option {
+ perm &= os.ModePerm
+ return optionFunc(func(cfg *config) {
+ cfg.createPerm = perm
+ })
+}
+
+// IgnoreUmask causes the permissions configured using WithPermissions to be
+// applied directly without applying the umask.
+func IgnoreUmask() Option {
+ return optionFunc(func(cfg *config) {
+ cfg.ignoreUmask = true
+ })
+}
+
+// WithStaticPermissions sets the permissions for the target file ignoring the
+// umask(2). This is equivalent to calling Chmod() on the file handle or using
+// WithPermissions in combination with IgnoreUmask.
+func WithStaticPermissions(perm os.FileMode) Option {
+ perm &= os.ModePerm
+ return optionFunc(func(cfg *config) {
+ cfg.chmod = &perm
+ })
+}
+
+// WithExistingPermissions configures the file creation to try to use the
+// permissions from an already existing target file. If the target file doesn't
+// exist yet or is not a regular file the default permissions are used unless
+// overridden using WithPermissions or WithStaticPermissions.
+func WithExistingPermissions() Option {
+ return optionFunc(func(c *config) {
+ c.attemptPermCopy = true
+ })
+}
diff --git a/vendor/github.com/google/renameio/v2/tempfile.go b/vendor/github.com/google/renameio/v2/tempfile.go
new file mode 100644
index 0000000..edc3e98
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/tempfile.go
@@ -0,0 +1,283 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build !windows
+// +build !windows
+
+package renameio
+
+import (
+ "io/ioutil"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "strconv"
+)
+
+// Default permissions for created files
+const defaultPerm os.FileMode = 0o600
+
+// nextrandom is a function generating a random number.
+var nextrandom = rand.Int63
+
+// openTempFile creates a randomly named file and returns an open handle. It is
+// similar to ioutil.TempFile except that the directory must be given, the file
+// permissions can be controlled and patterns in the name are not supported.
+// The name is always suffixed with a random number.
+func openTempFile(dir, name string, perm os.FileMode) (*os.File, error) {
+ prefix := filepath.Join(dir, name)
+
+ for attempt := 0; ; {
+ // Generate a reasonably random name which is unlikely to already
+ // exist. O_EXCL ensures that existing files generate an error.
+ name := prefix + strconv.FormatInt(nextrandom(), 10)
+
+ f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
+ if !os.IsExist(err) {
+ return f, err
+ }
+
+ if attempt++; attempt > 10000 {
+ return nil, &os.PathError{
+ Op: "tempfile",
+ Path: name,
+ Err: os.ErrExist,
+ }
+ }
+ }
+}
+
+// TempDir checks whether os.TempDir() can be used as a temporary directory for
+// later atomically replacing files within dest. If no (os.TempDir() resides on
+// a different mount point), dest is returned.
+//
+// Note that the returned value ceases to be valid once either os.TempDir()
+// changes (e.g. on Linux, once the TMPDIR environment variable changes) or the
+// file system is unmounted.
+func TempDir(dest string) string {
+ return tempDir("", filepath.Join(dest, "renameio-TempDir"))
+}
+
+func tempDir(dir, dest string) string {
+ if dir != "" {
+ return dir // caller-specified directory always wins
+ }
+
+ // Chose the destination directory as temporary directory so that we
+ // definitely can rename the file, for which both temporary and destination
+ // file need to point to the same mount point.
+ fallback := filepath.Dir(dest)
+
+ // The user might have overridden the os.TempDir() return value by setting
+ // the TMPDIR environment variable.
+ tmpdir := os.TempDir()
+
+ testsrc, err := ioutil.TempFile(tmpdir, "."+filepath.Base(dest))
+ if err != nil {
+ return fallback
+ }
+ cleanup := true
+ defer func() {
+ if cleanup {
+ os.Remove(testsrc.Name())
+ }
+ }()
+ testsrc.Close()
+
+ testdest, err := ioutil.TempFile(filepath.Dir(dest), "."+filepath.Base(dest))
+ if err != nil {
+ return fallback
+ }
+ defer os.Remove(testdest.Name())
+ testdest.Close()
+
+ if err := os.Rename(testsrc.Name(), testdest.Name()); err != nil {
+ return fallback
+ }
+ cleanup = false // testsrc no longer exists
+ return tmpdir
+}
+
+// PendingFile is a pending temporary file, waiting to replace the destination
+// path in a call to CloseAtomicallyReplace.
+type PendingFile struct {
+ *os.File
+
+ path string
+ done bool
+ closed bool
+}
+
+// Cleanup is a no-op if CloseAtomicallyReplace succeeded, and otherwise closes
+// and removes the temporary file.
+//
+// This method is not safe for concurrent use by multiple goroutines.
+func (t *PendingFile) Cleanup() error {
+ if t.done {
+ return nil
+ }
+ // An error occurred. Close and remove the tempfile. Errors are returned for
+ // reporting, there is nothing the caller can recover here.
+ var closeErr error
+ if !t.closed {
+ closeErr = t.Close()
+ }
+ if err := os.Remove(t.Name()); err != nil {
+ return err
+ }
+ t.done = true
+ return closeErr
+}
+
+// CloseAtomicallyReplace closes the temporary file and atomically replaces
+// the destination file with it, i.e., a concurrent open(2) call will either
+// open the file previously located at the destination path (if any), or the
+// just written file, but the file will always be present.
+//
+// This method is not safe for concurrent use by multiple goroutines.
+func (t *PendingFile) CloseAtomicallyReplace() error {
+ // Even on an ordered file system (e.g. ext4 with data=ordered) or file
+ // systems with write barriers, we cannot skip the fsync(2) call as per
+ // Theodore Ts'o (ext2/3/4 lead developer):
+ //
+ // > data=ordered only guarantees the avoidance of stale data (e.g., the previous
+ // > contents of a data block showing up after a crash, where the previous data
+ // > could be someone's love letters, medical records, etc.). Without the fsync(2)
+ // > a zero-length file is a valid and possible outcome after the rename.
+ if err := t.Sync(); err != nil {
+ return err
+ }
+ t.closed = true
+ if err := t.Close(); err != nil {
+ return err
+ }
+ if err := os.Rename(t.Name(), t.path); err != nil {
+ return err
+ }
+ t.done = true
+ return nil
+}
+
+// TempFile creates a temporary file destined to atomically creating or
+// replacing the destination file at path.
+//
+// If dir is the empty string, TempDir(filepath.Base(path)) is used. If you are
+// going to write a large number of files to the same file system, store the
+// result of TempDir(filepath.Base(path)) and pass it instead of the empty
+// string.
+//
+// The file's permissions will be 0600. You can change these by explicitly
+// calling Chmod on the returned PendingFile.
+func TempFile(dir, path string) (*PendingFile, error) {
+ return NewPendingFile(path, WithTempDir(dir), WithStaticPermissions(defaultPerm))
+}
+
+type config struct {
+ dir, path string
+ createPerm os.FileMode
+ attemptPermCopy bool
+ ignoreUmask bool
+ chmod *os.FileMode
+}
+
+// NewPendingFile creates a temporary file destined to atomically creating or
+// replacing the destination file at path.
+//
+// TempDir(filepath.Base(path)) is used to store the temporary file. If you are
+// going to write a large number of files to the same file system, use the
+// result of TempDir(filepath.Base(path)) with the WithTempDir option.
+//
+// The file's permissions will be (0600 & ^umask). Use WithPermissions,
+// IgnoreUmask, WithStaticPermissions and WithExistingPermissions to control
+// them.
+func NewPendingFile(path string, opts ...Option) (*PendingFile, error) {
+ cfg := config{
+ path: path,
+ createPerm: defaultPerm,
+ }
+
+ for _, o := range opts {
+ o.apply(&cfg)
+ }
+
+ if cfg.ignoreUmask && cfg.chmod == nil {
+ cfg.chmod = &cfg.createPerm
+ }
+
+ if cfg.attemptPermCopy {
+ // Try to determine permissions from an existing file.
+ if existing, err := os.Lstat(cfg.path); err == nil && existing.Mode().IsRegular() {
+ perm := existing.Mode() & os.ModePerm
+ cfg.chmod = &perm
+
+ // Try to already create file with desired permissions; at worst
+ // a chmod will be needed afterwards.
+ cfg.createPerm = perm
+ } else if err != nil && !os.IsNotExist(err) {
+ return nil, err
+ }
+ }
+
+ f, err := openTempFile(tempDir(cfg.dir, cfg.path), "."+filepath.Base(cfg.path), cfg.createPerm)
+ if err != nil {
+ return nil, err
+ }
+
+ if cfg.chmod != nil {
+ if fi, err := f.Stat(); err != nil {
+ return nil, err
+ } else if fi.Mode()&os.ModePerm != *cfg.chmod {
+ if err := f.Chmod(*cfg.chmod); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return &PendingFile{File: f, path: cfg.path}, nil
+}
+
+// Symlink wraps os.Symlink, replacing an existing symlink with the same name
+// atomically (os.Symlink fails when newname already exists, at least on Linux).
+func Symlink(oldname, newname string) error {
+ // Fast path: if newname does not exist yet, we can skip the whole dance
+ // below.
+ if err := os.Symlink(oldname, newname); err == nil || !os.IsExist(err) {
+ return err
+ }
+
+ // We need to use ioutil.TempDir, as we cannot overwrite a ioutil.TempFile,
+ // and removing+symlinking creates a TOCTOU race.
+ d, err := ioutil.TempDir(filepath.Dir(newname), "."+filepath.Base(newname))
+ if err != nil {
+ return err
+ }
+ cleanup := true
+ defer func() {
+ if cleanup {
+ os.RemoveAll(d)
+ }
+ }()
+
+ symlink := filepath.Join(d, "tmp.symlink")
+ if err := os.Symlink(oldname, symlink); err != nil {
+ return err
+ }
+
+ if err := os.Rename(symlink, newname); err != nil {
+ return err
+ }
+
+ cleanup = false
+ return os.RemoveAll(d)
+}
diff --git a/vendor/github.com/google/renameio/v2/writefile.go b/vendor/github.com/google/renameio/v2/writefile.go
new file mode 100644
index 0000000..5450421
--- /dev/null
+++ b/vendor/github.com/google/renameio/v2/writefile.go
@@ -0,0 +1,41 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build !windows
+// +build !windows
+
+package renameio
+
+import "os"
+
+// WriteFile mirrors ioutil.WriteFile, replacing an existing file with the same
+// name atomically.
+func WriteFile(filename string, data []byte, perm os.FileMode, opts ...Option) error {
+ opts = append([]Option{
+ WithPermissions(perm),
+ WithExistingPermissions(),
+ }, opts...)
+
+ t, err := NewPendingFile(filename, opts...)
+ if err != nil {
+ return err
+ }
+ defer t.Cleanup()
+
+ if _, err := t.Write(data); err != nil {
+ return err
+ }
+
+ return t.CloseAtomicallyReplace()
+}
diff --git a/vendor/github.com/magefile/mage/LICENSE b/vendor/github.com/magefile/mage/LICENSE
new file mode 100644
index 0000000..d0632bc
--- /dev/null
+++ b/vendor/github.com/magefile/mage/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2017 the Mage authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/magefile/mage/mg/color.go b/vendor/github.com/magefile/mage/mg/color.go
new file mode 100644
index 0000000..3e27103
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/color.go
@@ -0,0 +1,80 @@
+package mg
+
+// Color is ANSI color type
+type Color int
+
+// If you add/change/remove any items in this constant,
+// you will need to run "stringer -type=Color" in this directory again.
+// NOTE: Please keep the list in an alphabetical order.
+const (
+ Black Color = iota
+ Red
+ Green
+ Yellow
+ Blue
+ Magenta
+ Cyan
+ White
+ BrightBlack
+ BrightRed
+ BrightGreen
+ BrightYellow
+ BrightBlue
+ BrightMagenta
+ BrightCyan
+ BrightWhite
+)
+
+// AnsiColor are ANSI color codes for supported terminal colors.
+var ansiColor = map[Color]string{
+ Black: "\u001b[30m",
+ Red: "\u001b[31m",
+ Green: "\u001b[32m",
+ Yellow: "\u001b[33m",
+ Blue: "\u001b[34m",
+ Magenta: "\u001b[35m",
+ Cyan: "\u001b[36m",
+ White: "\u001b[37m",
+ BrightBlack: "\u001b[30;1m",
+ BrightRed: "\u001b[31;1m",
+ BrightGreen: "\u001b[32;1m",
+ BrightYellow: "\u001b[33;1m",
+ BrightBlue: "\u001b[34;1m",
+ BrightMagenta: "\u001b[35;1m",
+ BrightCyan: "\u001b[36;1m",
+ BrightWhite: "\u001b[37;1m",
+}
+
+// AnsiColorReset is an ANSI color code to reset the terminal color.
+const AnsiColorReset = "\033[0m"
+
+// DefaultTargetAnsiColor is a default ANSI color for colorizing targets.
+// It is set to Cyan as an arbitrary color, because it has a neutral meaning
+var DefaultTargetAnsiColor = ansiColor[Cyan]
+
+func toLowerCase(s string) string {
+ // this is a naive implementation
+ // borrowed from https://golang.org/src/strings/strings.go
+ // and only considers alphabetical characters [a-zA-Z]
+ // so that we don't depend on the "strings" package
+ buf := make([]byte, len(s))
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ if 'A' <= c && c <= 'Z' {
+ c += 'a' - 'A'
+ }
+ buf[i] = c
+ }
+ return string(buf)
+}
+
+func getAnsiColor(color string) (string, bool) {
+ colorLower := toLowerCase(color)
+ for k, v := range ansiColor {
+ colorConstLower := toLowerCase(k.String())
+ if colorConstLower == colorLower {
+ return v, true
+ }
+ }
+ return "", false
+}
diff --git a/vendor/github.com/magefile/mage/mg/color_string.go b/vendor/github.com/magefile/mage/mg/color_string.go
new file mode 100644
index 0000000..06debca
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/color_string.go
@@ -0,0 +1,38 @@
+// Code generated by "stringer -type=Color"; DO NOT EDIT.
+
+package mg
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[Black-0]
+ _ = x[Red-1]
+ _ = x[Green-2]
+ _ = x[Yellow-3]
+ _ = x[Blue-4]
+ _ = x[Magenta-5]
+ _ = x[Cyan-6]
+ _ = x[White-7]
+ _ = x[BrightBlack-8]
+ _ = x[BrightRed-9]
+ _ = x[BrightGreen-10]
+ _ = x[BrightYellow-11]
+ _ = x[BrightBlue-12]
+ _ = x[BrightMagenta-13]
+ _ = x[BrightCyan-14]
+ _ = x[BrightWhite-15]
+}
+
+const _Color_name = "BlackRedGreenYellowBlueMagentaCyanWhiteBrightBlackBrightRedBrightGreenBrightYellowBrightBlueBrightMagentaBrightCyanBrightWhite"
+
+var _Color_index = [...]uint8{0, 5, 8, 13, 19, 23, 30, 34, 39, 50, 59, 70, 82, 92, 105, 115, 126}
+
+func (i Color) String() string {
+ if i < 0 || i >= Color(len(_Color_index)-1) {
+ return "Color(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _Color_name[_Color_index[i]:_Color_index[i+1]]
+}
diff --git a/vendor/github.com/magefile/mage/mg/deps.go b/vendor/github.com/magefile/mage/mg/deps.go
new file mode 100644
index 0000000..f0c2509
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/deps.go
@@ -0,0 +1,211 @@
+package mg
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "reflect"
+ "runtime"
+ "strings"
+ "sync"
+)
+
+var logger = log.New(os.Stderr, "", 0)
+
+type onceMap struct {
+ mu *sync.Mutex
+ m map[onceKey]*onceFun
+}
+
+type onceKey struct {
+ Name string
+ ID string
+}
+
+func (o *onceMap) LoadOrStore(f Fn) *onceFun {
+ defer o.mu.Unlock()
+ o.mu.Lock()
+
+ key := onceKey{
+ Name: f.Name(),
+ ID: f.ID(),
+ }
+ existing, ok := o.m[key]
+ if ok {
+ return existing
+ }
+ one := &onceFun{
+ once: &sync.Once{},
+ fn: f,
+ displayName: displayName(f.Name()),
+ }
+ o.m[key] = one
+ return one
+}
+
+var onces = &onceMap{
+ mu: &sync.Mutex{},
+ m: map[onceKey]*onceFun{},
+}
+
+// SerialDeps is like Deps except it runs each dependency serially, instead of
+// in parallel. This can be useful for resource intensive dependencies that
+// shouldn't be run at the same time.
+func SerialDeps(fns ...interface{}) {
+ funcs := checkFns(fns)
+ ctx := context.Background()
+ for i := range fns {
+ runDeps(ctx, funcs[i:i+1])
+ }
+}
+
+// SerialCtxDeps is like CtxDeps except it runs each dependency serially,
+// instead of in parallel. This can be useful for resource intensive
+// dependencies that shouldn't be run at the same time.
+func SerialCtxDeps(ctx context.Context, fns ...interface{}) {
+ funcs := checkFns(fns)
+ for i := range fns {
+ runDeps(ctx, funcs[i:i+1])
+ }
+}
+
+// CtxDeps runs the given functions as dependencies of the calling function.
+// Dependencies must only be of type:
+// func()
+// func() error
+// func(context.Context)
+// func(context.Context) error
+// Or a similar method on a mg.Namespace type.
+// Or an mg.Fn interface.
+//
+// The function calling Deps is guaranteed that all dependent functions will be
+// run exactly once when Deps returns. Dependent functions may in turn declare
+// their own dependencies using Deps. Each dependency is run in their own
+// goroutines. Each function is given the context provided if the function
+// prototype allows for it.
+func CtxDeps(ctx context.Context, fns ...interface{}) {
+ funcs := checkFns(fns)
+ runDeps(ctx, funcs)
+}
+
+// runDeps assumes you've already called checkFns.
+func runDeps(ctx context.Context, fns []Fn) {
+ mu := &sync.Mutex{}
+ var errs []string
+ var exit int
+ wg := &sync.WaitGroup{}
+ for _, f := range fns {
+ fn := onces.LoadOrStore(f)
+ wg.Add(1)
+ go func() {
+ defer func() {
+ if v := recover(); v != nil {
+ mu.Lock()
+ if err, ok := v.(error); ok {
+ exit = changeExit(exit, ExitStatus(err))
+ } else {
+ exit = changeExit(exit, 1)
+ }
+ errs = append(errs, fmt.Sprint(v))
+ mu.Unlock()
+ }
+ wg.Done()
+ }()
+ if err := fn.run(ctx); err != nil {
+ mu.Lock()
+ errs = append(errs, fmt.Sprint(err))
+ exit = changeExit(exit, ExitStatus(err))
+ mu.Unlock()
+ }
+ }()
+ }
+
+ wg.Wait()
+ if len(errs) > 0 {
+ panic(Fatal(exit, strings.Join(errs, "\n")))
+ }
+}
+
+func checkFns(fns []interface{}) []Fn {
+ funcs := make([]Fn, len(fns))
+ for i, f := range fns {
+ if fn, ok := f.(Fn); ok {
+ funcs[i] = fn
+ continue
+ }
+
+ // Check if the target provided is a not function so we can give a clear warning
+ t := reflect.TypeOf(f)
+ if t == nil || t.Kind() != reflect.Func {
+ panic(fmt.Errorf("non-function used as a target dependency: %T. The mg.Deps, mg.SerialDeps and mg.CtxDeps functions accept function names, such as mg.Deps(TargetA, TargetB)", f))
+ }
+
+ funcs[i] = F(f)
+ }
+ return funcs
+}
+
+// Deps runs the given functions in parallel, exactly once. Dependencies must
+// only be of type:
+// func()
+// func() error
+// func(context.Context)
+// func(context.Context) error
+// Or a similar method on a mg.Namespace type.
+// Or an mg.Fn interface.
+//
+// This is a way to build up a tree of dependencies with each dependency
+// defining its own dependencies. Functions must have the same signature as a
+// Mage target, i.e. optional context argument, optional error return.
+func Deps(fns ...interface{}) {
+ CtxDeps(context.Background(), fns...)
+}
+
+func changeExit(old, new int) int {
+ if new == 0 {
+ return old
+ }
+ if old == 0 {
+ return new
+ }
+ if old == new {
+ return old
+ }
+ // both different and both non-zero, just set
+ // exit to 1. Nothing more we can do.
+ return 1
+}
+
+// funcName returns the unique name for the function
+func funcName(i interface{}) string {
+ return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+}
+
+func displayName(name string) string {
+ splitByPackage := strings.Split(name, ".")
+ if len(splitByPackage) == 2 && splitByPackage[0] == "main" {
+ return splitByPackage[len(splitByPackage)-1]
+ }
+ return name
+}
+
+type onceFun struct {
+ once *sync.Once
+ fn Fn
+ err error
+
+ displayName string
+}
+
+// run will run the function exactly once and capture the error output. Further runs simply return
+// the same error output.
+func (o *onceFun) run(ctx context.Context) error {
+ o.once.Do(func() {
+ if Verbose() {
+ logger.Println("Running dependency:", displayName(o.fn.Name()))
+ }
+ o.err = o.fn.Run(ctx)
+ })
+ return o.err
+}
diff --git a/vendor/github.com/magefile/mage/mg/errors.go b/vendor/github.com/magefile/mage/mg/errors.go
new file mode 100644
index 0000000..2dd780f
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/errors.go
@@ -0,0 +1,51 @@
+package mg
+
+import (
+ "errors"
+ "fmt"
+)
+
+type fatalErr struct {
+ code int
+ error
+}
+
+func (f fatalErr) ExitStatus() int {
+ return f.code
+}
+
+type exitStatus interface {
+ ExitStatus() int
+}
+
+// Fatal returns an error that will cause mage to print out the
+// given args and exit with the given exit code.
+func Fatal(code int, args ...interface{}) error {
+ return fatalErr{
+ code: code,
+ error: errors.New(fmt.Sprint(args...)),
+ }
+}
+
+// Fatalf returns an error that will cause mage to print out the
+// given message and exit with the given exit code.
+func Fatalf(code int, format string, args ...interface{}) error {
+ return fatalErr{
+ code: code,
+ error: fmt.Errorf(format, args...),
+ }
+}
+
+// ExitStatus queries the error for an exit status. If the error is nil, it
+// returns 0. If the error does not implement ExitStatus() int, it returns 1.
+// Otherwise it retiurns the value from ExitStatus().
+func ExitStatus(err error) int {
+ if err == nil {
+ return 0
+ }
+ exit, ok := err.(exitStatus)
+ if !ok {
+ return 1
+ }
+ return exit.ExitStatus()
+}
diff --git a/vendor/github.com/magefile/mage/mg/fn.go b/vendor/github.com/magefile/mage/mg/fn.go
new file mode 100644
index 0000000..3856857
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/fn.go
@@ -0,0 +1,192 @@
+package mg
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "time"
+)
+
+// Fn represents a function that can be run with mg.Deps. Package, Name, and ID must combine to
+// uniquely identify a function, while ensuring the "same" function has identical values. These are
+// used as a map key to find and run (or not run) the function.
+type Fn interface {
+ // Name should return the fully qualified name of the function. Usually
+ // it's best to use runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name().
+ Name() string
+
+ // ID should be an additional uniqueness qualifier in case the name is insufficiently unique.
+ // This can be the case for functions that take arguments (mg.F json-encodes an array of the
+ // args).
+ ID() string
+
+ // Run should run the function.
+ Run(ctx context.Context) error
+}
+
+// F takes a function that is compatible as a mage target, and any args that need to be passed to
+// it, and wraps it in an mg.Fn that mg.Deps can run. Args must be passed in the same order as they
+// are declared by the function. Note that you do not need to and should not pass a context.Context
+// to F, even if the target takes a context. Compatible args are int, bool, string, and
+// time.Duration.
+func F(target interface{}, args ...interface{}) Fn {
+ hasContext, isNamespace, err := checkF(target, args)
+ if err != nil {
+ panic(err)
+ }
+ id, err := json.Marshal(args)
+ if err != nil {
+ panic(fmt.Errorf("can't convert args into a mage-compatible id for mg.Deps: %s", err))
+ }
+ return fn{
+ name: funcName(target),
+ id: string(id),
+ f: func(ctx context.Context) error {
+ v := reflect.ValueOf(target)
+ count := len(args)
+ if hasContext {
+ count++
+ }
+ if isNamespace {
+ count++
+ }
+ vargs := make([]reflect.Value, count)
+ x := 0
+ if isNamespace {
+ vargs[0] = reflect.ValueOf(struct{}{})
+ x++
+ }
+ if hasContext {
+ vargs[x] = reflect.ValueOf(ctx)
+ x++
+ }
+ for y := range args {
+ vargs[x+y] = reflect.ValueOf(args[y])
+ }
+ ret := v.Call(vargs)
+ if len(ret) > 0 {
+ // we only allow functions with a single error return, so this should be safe.
+ if ret[0].IsNil() {
+ return nil
+ }
+ return ret[0].Interface().(error)
+ }
+ return nil
+ },
+ }
+}
+
+type fn struct {
+ name string
+ id string
+ f func(ctx context.Context) error
+}
+
+// Name returns the fully qualified name of the function.
+func (f fn) Name() string {
+ return f.name
+}
+
+// ID returns a hash of the argument values passed in
+func (f fn) ID() string {
+ return f.id
+}
+
+// Run runs the function.
+func (f fn) Run(ctx context.Context) error {
+ return f.f(ctx)
+}
+
+func checkF(target interface{}, args []interface{}) (hasContext, isNamespace bool, _ error) {
+ t := reflect.TypeOf(target)
+ if t == nil || t.Kind() != reflect.Func {
+ return false, false, fmt.Errorf("non-function passed to mg.F: %T. The mg.F function accepts function names, such as mg.F(TargetA, \"arg1\", \"arg2\")", target)
+ }
+
+ if t.NumOut() > 1 {
+ return false, false, fmt.Errorf("target has too many return values, must be zero or just an error: %T", target)
+ }
+ if t.NumOut() == 1 && t.Out(0) != errType {
+ return false, false, fmt.Errorf("target's return value is not an error")
+ }
+
+ // more inputs than slots is an error if not variadic
+ if len(args) > t.NumIn() && !t.IsVariadic() {
+ return false, false, fmt.Errorf("too many arguments for target, got %d for %T", len(args), target)
+ }
+
+ if t.NumIn() == 0 {
+ return false, false, nil
+ }
+
+ x := 0
+ inputs := t.NumIn()
+
+ if t.In(0).AssignableTo(emptyType) {
+ // nameSpace func
+ isNamespace = true
+ x++
+ // callers must leave off the namespace value
+ inputs--
+ }
+ if t.NumIn() > x && t.In(x) == ctxType {
+ // callers must leave off the context
+ inputs--
+
+ // let the upper function know it should pass us a context.
+ hasContext = true
+
+ // skip checking the first argument in the below loop if it's a context, since first arg is
+ // special.
+ x++
+ }
+
+ if t.IsVariadic() {
+ if len(args) < inputs-1 {
+ return false, false, fmt.Errorf("too few arguments for target, got %d for %T", len(args), target)
+
+ }
+ } else if len(args) != inputs {
+ return false, false, fmt.Errorf("wrong number of arguments for target, got %d for %T", len(args), target)
+ }
+
+ for _, arg := range args {
+ argT := t.In(x)
+ if t.IsVariadic() && x == t.NumIn()-1 {
+ // For the variadic argument, use the slice element type.
+ argT = argT.Elem()
+ }
+ if !argTypes[argT] {
+ return false, false, fmt.Errorf("argument %d (%s), is not a supported argument type", x, argT)
+ }
+ passedT := reflect.TypeOf(arg)
+ if argT != passedT {
+ return false, false, fmt.Errorf("argument %d expected to be %s, but is %s", x, argT, passedT)
+ }
+ if x < t.NumIn()-1 {
+ x++
+ }
+ }
+ return hasContext, isNamespace, nil
+}
+
+// Here we define the types that are supported as arguments/returns
+var (
+ ctxType = reflect.TypeOf(func(context.Context) {}).In(0)
+ errType = reflect.TypeOf(func() error { return nil }).Out(0)
+ emptyType = reflect.TypeOf(struct{}{})
+
+ intType = reflect.TypeOf(int(0))
+ stringType = reflect.TypeOf(string(""))
+ boolType = reflect.TypeOf(bool(false))
+ durType = reflect.TypeOf(time.Second)
+
+ // don't put ctx in here, this is for non-context types
+ argTypes = map[reflect.Type]bool{
+ intType: true,
+ boolType: true,
+ stringType: true,
+ durType: true,
+ }
+)
diff --git a/vendor/github.com/magefile/mage/mg/runtime.go b/vendor/github.com/magefile/mage/mg/runtime.go
new file mode 100644
index 0000000..9a8de12
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/runtime.go
@@ -0,0 +1,136 @@
+package mg
+
+import (
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+)
+
+// CacheEnv is the environment variable that users may set to change the
+// location where mage stores its compiled binaries.
+const CacheEnv = "MAGEFILE_CACHE"
+
+// VerboseEnv is the environment variable that indicates the user requested
+// verbose mode when running a magefile.
+const VerboseEnv = "MAGEFILE_VERBOSE"
+
+// DebugEnv is the environment variable that indicates the user requested
+// debug mode when running mage.
+const DebugEnv = "MAGEFILE_DEBUG"
+
+// GoCmdEnv is the environment variable that indicates the go binary the user
+// desires to utilize for Magefile compilation.
+const GoCmdEnv = "MAGEFILE_GOCMD"
+
+// IgnoreDefaultEnv is the environment variable that indicates the user requested
+// to ignore the default target specified in the magefile.
+const IgnoreDefaultEnv = "MAGEFILE_IGNOREDEFAULT"
+
+// HashFastEnv is the environment variable that indicates the user requested to
+// use a quick hash of magefiles to determine whether or not the magefile binary
+// needs to be rebuilt. This results in faster runtimes, but means that mage
+// will fail to rebuild if a dependency has changed. To force a rebuild, run
+// mage with the -f flag.
+const HashFastEnv = "MAGEFILE_HASHFAST"
+
+// EnableColorEnv is the environment variable that indicates the user is using
+// a terminal which supports a color output. The default is false for backwards
+// compatibility. When the value is true and the detected terminal does support colors
+// then the list of mage targets will be displayed in ANSI color. When the value
+// is true but the detected terminal does not support colors, then the list of
+// mage targets will be displayed in the default colors (e.g. black and white).
+const EnableColorEnv = "MAGEFILE_ENABLE_COLOR"
+
+// TargetColorEnv is the environment variable that indicates which ANSI color
+// should be used to colorize mage targets. This is only applicable when
+// the MAGEFILE_ENABLE_COLOR environment variable is true.
+// The supported ANSI color names are any of these:
+// - Black
+// - Red
+// - Green
+// - Yellow
+// - Blue
+// - Magenta
+// - Cyan
+// - White
+// - BrightBlack
+// - BrightRed
+// - BrightGreen
+// - BrightYellow
+// - BrightBlue
+// - BrightMagenta
+// - BrightCyan
+// - BrightWhite
+const TargetColorEnv = "MAGEFILE_TARGET_COLOR"
+
+// Verbose reports whether a magefile was run with the verbose flag.
+func Verbose() bool {
+ b, _ := strconv.ParseBool(os.Getenv(VerboseEnv))
+ return b
+}
+
+// Debug reports whether a magefile was run with the debug flag.
+func Debug() bool {
+ b, _ := strconv.ParseBool(os.Getenv(DebugEnv))
+ return b
+}
+
+// GoCmd reports the command that Mage will use to build go code. By default mage runs
+// the "go" binary in the PATH.
+func GoCmd() string {
+ if cmd := os.Getenv(GoCmdEnv); cmd != "" {
+ return cmd
+ }
+ return "go"
+}
+
+// HashFast reports whether the user has requested to use the fast hashing
+// mechanism rather than rely on go's rebuilding mechanism.
+func HashFast() bool {
+ b, _ := strconv.ParseBool(os.Getenv(HashFastEnv))
+ return b
+}
+
+// IgnoreDefault reports whether the user has requested to ignore the default target
+// in the magefile.
+func IgnoreDefault() bool {
+ b, _ := strconv.ParseBool(os.Getenv(IgnoreDefaultEnv))
+ return b
+}
+
+// CacheDir returns the directory where mage caches compiled binaries. It
+// defaults to $HOME/.magefile, but may be overridden by the MAGEFILE_CACHE
+// environment variable.
+func CacheDir() string {
+ d := os.Getenv(CacheEnv)
+ if d != "" {
+ return d
+ }
+ switch runtime.GOOS {
+ case "windows":
+ return filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"), "magefile")
+ default:
+ return filepath.Join(os.Getenv("HOME"), ".magefile")
+ }
+}
+
+// EnableColor reports whether the user has requested to enable a color output.
+func EnableColor() bool {
+ b, _ := strconv.ParseBool(os.Getenv(EnableColorEnv))
+ return b
+}
+
+// TargetColor returns the configured ANSI color name a color output.
+func TargetColor() string {
+ s, exists := os.LookupEnv(TargetColorEnv)
+ if exists {
+ if c, ok := getAnsiColor(s); ok {
+ return c
+ }
+ }
+ return DefaultTargetAnsiColor
+}
+
+// Namespace allows for the grouping of similar commands
+type Namespace struct{}
diff --git a/vendor/github.com/mcandre/mage-extras/.editorconfig b/vendor/github.com/mcandre/mage-extras/.editorconfig
new file mode 100644
index 0000000..8a8f2ef
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/.editorconfig
@@ -0,0 +1,63 @@
+# Most text files
+[*]
+charset = utf-8
+max_line_length = none
+trim_trailing_whitespace = true
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4 # bash8
+
+# Machine-generated files
+[*.{scpt,plist,dot,db}]
+trim_trailing_whitespace = false
+insert_final_newline = none
+indent_size = none
+
+# Freeform text
+[*.{txt,md,cob,emacs,el,lisp,lsp,scm,setup,meta,clj,rkt,ecl,sbcl,abcl,eclrc,sbclrc,asd,snusp}]
+indent_size = none
+
+# Fix cmake
+[CMakeLists.txt]
+indent_size = 4 # bash8
+
+# Align keys
+[*.{yaml,yml,reek,.yamllint}]
+indent_size = 2
+
+# Placeholder files
+[*.{gitkeep,__init__.py}]
+insert_final_newline = none
+
+#
+# Windows junk
+#
+[*.{cmd,bat,reg,ps1,vbs,cs,fs,fsx,ahk,psenvrc}]
+end_of_line = crlf
+insert_final_newline = false
+[settings.json]
+end_of_line = crlf
+insert_final_newline = false
+
+# Lexer restrictions
+[{makefile,Makefile,GNUmakefile,BSDmakefile}]
+indent_style = tab
+[*.{makefile,Makefile,mk,GNUmakefile,BSDmakefile,go,gitmodules}]
+indent_style = tab
+
+# Natural HTML nesting
+[*.js]
+quote_type = single
+
+# Compressed artifacts
+[*.min.*]
+insert_final_newline = false
+[*-min.*]
+insert_final_newline = false
+
+# Potential mixed indentations
+[*.patch]
+trim_trailing_whitespace = false
+indent_style = none
+indent_size = none
diff --git a/vendor/github.com/mcandre/mage-extras/.gitignore b/vendor/github.com/mcandre/mage-extras/.gitignore
new file mode 100644
index 0000000..151adb9
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/.gitignore
@@ -0,0 +1,19 @@
+cover.html
+# Created by https://www.gitignore.io/api/go
+
+### Go ###
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+
+# End of https://www.gitignore.io/api/go
diff --git a/vendor/github.com/mcandre/mage-extras/.rubberstamp b/vendor/github.com/mcandre/mage-extras/.rubberstamp
new file mode 100644
index 0000000..2ccf2e2
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/.rubberstamp
@@ -0,0 +1 @@
+Tue Oct 1 00:06:23 UTC 2024
diff --git a/vendor/github.com/mcandre/mage-extras/.tool-versions b/vendor/github.com/mcandre/mage-extras/.tool-versions
new file mode 100644
index 0000000..766e4b5
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/.tool-versions
@@ -0,0 +1,2 @@
+golang 1.23.2
+rust 1.75.0
diff --git a/vendor/github.com/mcandre/mage-extras/DEVELOPMENT.md b/vendor/github.com/mcandre/mage-extras/DEVELOPMENT.md
new file mode 100644
index 0000000..e39d2ae
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/DEVELOPMENT.md
@@ -0,0 +1,50 @@
+# BUILDTIME REQUIREMENTS
+
+* [Go](https://go.dev/) 1.23.2+
+* POSIX compatible [make](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html)
+* [Rust](https://www.rust-lang.org/) 1.75.0+
+* [Snyk](https://snyk.io/)
+* Provision additional dev tools with `make`
+
+## Recommended
+
+* [ASDF](https://asdf-vm.com/) 0.10 (run `asdf reshim` after provisioning)
+* [direnv](https://direnv.net/) 2
+* macOS [open](https://ss64.com/mac/open.html) or equivalent alias
+
+Non-UNIX environments may produce subtle adverse effects when linting or generating application ports.
+
+## Windows
+
+Apply a user environment variable `GODEBUG=modcacheunzipinplace=1` per [access denied resolution](https://github.com/golang/go/wiki/Modules/e93463d3e853031af84204dc5d3e2a9a710a7607#go-115), for native Windows development environments (Command Prompt / PowerShell, not WLS, not Cygwin, not MSYS2, not MinGW, not msysGit, not Git Bash, not etc).
+
+# AUDIT
+
+```console
+$ mage audit
+```
+
+# UNIT TEST
+
+```console
+$ mage test
+```
+
+# COVERAGE
+
+```console
+$ mage coverageHTML
+$ open cover.html
+```
+
+# LINT
+
+```console
+$ mage lint
+```
+
+# CLEAN
+
+```console
+$ mage clean
+```
diff --git a/vendor/github.com/mcandre/mage-extras/LICENSE.md b/vendor/github.com/mcandre/mage-extras/LICENSE.md
new file mode 100644
index 0000000..bff0a40
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/LICENSE.md
@@ -0,0 +1,26 @@
+Copyright (c) 2018, Andrew Pennebaker
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OR MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of the FreeBSD project.
diff --git a/vendor/github.com/mcandre/mage-extras/README.md b/vendor/github.com/mcandre/mage-extras/README.md
new file mode 100644
index 0000000..ff3e43a
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/README.md
@@ -0,0 +1,71 @@
+# mage-extras: some predefined tasks for common mage workflows
+
+# EXAMPLE
+
+```console
+$ mage noVendor
+/Users/andrew/go/src/github.com/mcandre/mage-extras/test.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/mageextras_test.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/errcheck.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/gofmt.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/sources.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/gox.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/nakedret.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/vet.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/goimports.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/mageextras.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/packages.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/pathseparator.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/binaries_test.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/sources_test.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/binaries.go
+/Users/andrew/go/src/github.com/mcandre/mage-extras/install.go
+```
+
+# ABOUT
+
+mage-extras defines some reusable task predicates for common workflows, in a platform-agnostic way:
+
+* security audits
+* checking that Go source code actually compiles
+* running unit tests
+* generating code coverage reports
+* linting with assorted Go linting tools
+* formatting Go code
+* installing and uninstall Go applications
+* collecting Go source file paths
+* obtaining the GOPATH/bin directory
+* referencing all local Go packages
+* referencing all local Go commands
+* cross-compiling applications with factorio, gox, goxcart, and xgo
+* archiving artifacts
+* manipulating the path separator as a string
+
+Mage is highly agnostic about workflows. mage-extras is a little more opinionated, introducing some useful conventions on top, such as reliably obtaining a list of non-vendored Go files paths, while allowing developers to customize builds to suit their project needs.
+
+# DOCUMENTATION
+
+https://pkg.go.dev/github.com/mcandre/mage-extras
+
+# LICENSE
+
+BSD-2-Clause
+
+# RUNTIME REQUIREMENTS
+
+* [Go](https://go.dev/) 1.23.2+
+* [Mage](https://magefile.org/) (e.g., `go install github.com/magefile/mage`)
+
+## Recommended
+
+* POSIX compatible [tar](https://pubs.opengroup.org/onlinepubs/7908799/xcu/tar.html)
+* [tree](https://linux.die.net/man/1/tree)
+* a UNIX environment, such as macOS, Linux, BSD, [WSL](https://learn.microsoft.com/en-us/windows/wsl/), etc.
+
+tar is a portable archiver suitable for creating `*.tgz` tarball archives. Users can then download the tarball and extract the executable relevant to their platform. Tarballs are especially well suited for use in Docker containers, as the tar command is more likely to be installed than unzip.
+
+Note that non-UNIX file systems may not preserve crucial chmod acl bits during port generation. This can corrupt downstream artifacts, such as compressed archives and installation procedures.
+
+# CONTRIBUTING
+
+For more details on developing mage-extras itself, see [DEVELOPMENT.md](DEVELOPMENT.md).
diff --git a/vendor/github.com/mcandre/mage-extras/archive.go b/vendor/github.com/mcandre/mage-extras/archive.go
new file mode 100644
index 0000000..f944357
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/archive.go
@@ -0,0 +1,18 @@
+package mageextras
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+)
+
+// Archive compresses build artifacts.
+func Archive(portBasename string, artifactsPath string) error {
+ archiveFilename := fmt.Sprintf("%s.tgz", portBasename)
+ cmd := exec.Command("tar")
+ cmd.Args = append(cmd.Args, "czf", archiveFilename, portBasename)
+ cmd.Dir = artifactsPath
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/binaries.go b/vendor/github.com/mcandre/mage-extras/binaries.go
new file mode 100644
index 0000000..8dbd2c7
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/binaries.go
@@ -0,0 +1,30 @@
+package mageextras
+
+import (
+ "os"
+ "os/user"
+ "path"
+)
+
+// LoadedGoBinariesPath denotes the path to the Go binaries directory.
+// Populated with LoadGoBinariesPath().
+var LoadedGoBinariesPath = ""
+
+// LoadGoBinariesPath populates LoadedGoBinariesPath.
+func LoadGoBinariesPath() error {
+ goPath := os.Getenv("GOPATH")
+
+ if goPath == "" {
+ user, err := user.Current()
+
+ if err != nil {
+ return err
+ }
+
+ goPath = path.Join(user.HomeDir, "go")
+ }
+
+ LoadedGoBinariesPath = path.Join(goPath, "bin")
+
+ return nil
+}
diff --git a/vendor/github.com/mcandre/mage-extras/compile.go b/vendor/github.com/mcandre/mage-extras/compile.go
new file mode 100644
index 0000000..5cdf028
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/compile.go
@@ -0,0 +1,17 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// Compile runs go build recursively.
+func Compile(args ...string) error {
+ cmd := exec.Command("go", "build")
+ cmd.Args = append(cmd.Args, "build")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Args = append(cmd.Args, AllPackagesPath)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/coverage.go b/vendor/github.com/mcandre/mage-extras/coverage.go
new file mode 100644
index 0000000..7fe6dda
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/coverage.go
@@ -0,0 +1,34 @@
+package mageextras
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+)
+
+// CoverageHTML generates HTML formatted coverage data.
+func CoverageHTML(htmlFilename string, profileFilename string) error {
+ cmd := exec.Command(
+ "go",
+ "tool",
+ "cover",
+ fmt.Sprintf("-html=%s", profileFilename),
+ "-o",
+ htmlFilename,
+ )
+ cmd.Stdin = os.Stdin
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+// CoverageProfile generates raw coverage data.
+func CoverageProfile(profileFilename string) error {
+ cmd := exec.Command(
+ "go",
+ "test",
+ fmt.Sprintf("-coverprofile=%s", profileFilename),
+ )
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/deadcode.go b/vendor/github.com/mcandre/mage-extras/deadcode.go
new file mode 100644
index 0000000..e936705
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/deadcode.go
@@ -0,0 +1,15 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// Deadcode runs deadcode.
+func Deadcode(args ...string) error {
+ cmd := exec.Command("deadcode")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/dockerscout.go b/vendor/github.com/mcandre/mage-extras/dockerscout.go
new file mode 100644
index 0000000..9f4b3bc
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/dockerscout.go
@@ -0,0 +1,17 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// DockerScout executes a docker security audit.
+func DockerScout(args ...string) error {
+ cmd := exec.Command("docker")
+ cmd.Args = append(cmd.Args, "scout")
+ cmd.Args = append(cmd.Args, "cves")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/errcheck.go b/vendor/github.com/mcandre/mage-extras/errcheck.go
new file mode 100644
index 0000000..256b2cb
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/errcheck.go
@@ -0,0 +1,15 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// Errcheck runs errcheck.
+func Errcheck(args ...string) error {
+ cmd := exec.Command("errcheck")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/factorio.go b/vendor/github.com/mcandre/mage-extras/factorio.go
new file mode 100644
index 0000000..7403386
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/factorio.go
@@ -0,0 +1,18 @@
+package mageextras
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+)
+
+// Factorio cross-compiles Go binaries for a multitude of platforms.
+func Factorio(banner string, args ...string) error {
+ cmd := exec.Command("factorio")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Env = os.Environ()
+ cmd.Env = append(cmd.Env, fmt.Sprintf("FACTORIO_BANNER=%s", banner))
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/gofmt.go b/vendor/github.com/mcandre/mage-extras/gofmt.go
new file mode 100644
index 0000000..f038778
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/gofmt.go
@@ -0,0 +1,27 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+
+ "github.com/magefile/mage/mg"
+)
+
+// GoFmt runs gofmt.
+func GoFmt(args ...string) error {
+ mg.Deps(CollectGoFiles)
+
+ for pth := range CollectedGoFiles {
+ cmd := exec.Command("gofmt")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Args = append(cmd.Args, pth)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ if err := cmd.Run(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mcandre/mage-extras/goimports.go b/vendor/github.com/mcandre/mage-extras/goimports.go
new file mode 100644
index 0000000..e154737
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/goimports.go
@@ -0,0 +1,27 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+
+ "github.com/magefile/mage/mg"
+)
+
+// GoImports runs goimports.
+func GoImports(args ...string) error {
+ mg.Deps(CollectGoFiles)
+
+ for pth := range CollectedGoFiles {
+ cmd := exec.Command("goimports")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Args = append(cmd.Args, pth)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ if err := cmd.Run(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mcandre/mage-extras/golint.go b/vendor/github.com/mcandre/mage-extras/golint.go
new file mode 100644
index 0000000..6fd7bd5
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/golint.go
@@ -0,0 +1,32 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+
+ "github.com/magefile/mage/mg"
+)
+
+// GoLint runs golint.
+func GoLint(args ...string) error {
+ mg.Deps(CollectGoFiles)
+
+ cmdName := "golint"
+
+ for pth := range CollectedGoFiles {
+ cmdParameters := []string{cmdName}
+ cmdParameters = append(cmdParameters, args...)
+ cmdParameters = append(cmdParameters, pth)
+
+ cmd := exec.Command(cmdName)
+ cmd.Args = cmdParameters
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ if err := cmd.Run(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mcandre/mage-extras/govulncheck.go b/vendor/github.com/mcandre/mage-extras/govulncheck.go
new file mode 100644
index 0000000..52fb58c
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/govulncheck.go
@@ -0,0 +1,15 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// Govulncheck runs govulncheck.
+func Govulncheck(args ...string) error {
+ cmd := exec.Command("govulncheck")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/install.go b/vendor/github.com/mcandre/mage-extras/install.go
new file mode 100644
index 0000000..c79441c
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/install.go
@@ -0,0 +1,33 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+ "path"
+
+ "github.com/magefile/mage/mg"
+)
+
+// Install builds and installs Go applications.
+func Install(args ...string) error {
+ cmd := exec.Command("go")
+ cmd.Args = append(cmd.Args, "install")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Args = append(cmd.Args, AllPackagesPath)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+// Uninstall deletes installed Go applications.
+func Uninstall(applications ...string) error {
+ mg.Deps(LoadGoBinariesPath)
+
+ for _, application := range applications {
+ if err := os.RemoveAll(path.Join(LoadedGoBinariesPath, application)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mcandre/mage-extras/mageextras.go b/vendor/github.com/mcandre/mage-extras/mageextras.go
new file mode 100644
index 0000000..88f78e6
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/mageextras.go
@@ -0,0 +1,2 @@
+// Package mageextras provides premade tasks for common mage workflows.
+package mageextras
diff --git a/vendor/github.com/mcandre/mage-extras/magefile.go b/vendor/github.com/mcandre/mage-extras/magefile.go
new file mode 100644
index 0000000..f7a3c16
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/magefile.go
@@ -0,0 +1,115 @@
+//go:build mage
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/magefile/mage/mg"
+ mageextras "github.com/mcandre/mage-extras"
+)
+
+// Default references the default build task.
+var Default = CoverageHTML
+
+// CoverHTML denotes the HTML formatted coverage filename.
+var CoverHTML = "cover.html"
+
+// CoverProfile denotes the raw coverage data filename.
+var CoverProfile = "cover.out"
+
+// Govulncheck runs govulncheck.
+func Govulncheck() error { return mageextras.Govulncheck("-scan", "package", "./...") }
+
+// Snyk runs Snyk SCA.
+func Snyk() error { return mageextras.SnykTest() }
+
+// Audit runs a security audit.
+func Audit() error {
+ mg.Deps(Govulncheck)
+ return Snyk()
+}
+
+// CoverageHTML generates HTML formatted coverage data.
+func CoverageHTML() error {
+ mg.Deps(CoverageProfile)
+ return mageextras.CoverageHTML(CoverHTML, CoverProfile)
+}
+
+// CoverageProfile generates raw coverage data.
+func CoverageProfile() error { return mageextras.CoverageProfile(CoverProfile) }
+
+// Test executes the unit test suite.
+func Test() error { return mageextras.UnitTest() }
+
+// Gofmt runs gofmt.
+func GoFmt() error { return mageextras.GoFmt("-s", "-w") }
+
+// GoImports runs goimports.
+func GoImports() error { return mageextras.GoImports("-w") }
+
+// GoVet runs default go vet analyzers.
+func GoVet() error { return mageextras.GoVet() }
+
+// Errcheck runs errcheck.
+func Errcheck() error { return mageextras.Errcheck("-blank") }
+
+// Nakedret runs nakedret.
+func Nakedret() error { return mageextras.Nakedret("-l", "0") }
+
+// Revive runs revive.
+func Revive() error { return mageextras.Revive() }
+
+// Shadow runs go vet with shadow checks enabled.
+func Shadow() error { return mageextras.GoVetShadow() }
+
+// Staticcheck runs staticcheck.
+func Staticcheck() error { return mageextras.Staticcheck() }
+
+// Unmake runs unmake.
+func Unmake() error {
+ err := mageextras.Unmake(".")
+
+ if err != nil {
+ return err
+ }
+
+ return mageextras.Unmake("-n", ".")
+}
+
+// Lint runs the lint suite.
+func Lint() error {
+ mg.Deps(GoFmt)
+ mg.Deps(GoImports)
+ mg.Deps(GoVet)
+ mg.Deps(Errcheck)
+ mg.Deps(Nakedret)
+ mg.Deps(Revive)
+ mg.Deps(Shadow)
+ mg.Deps(Staticcheck)
+ mg.Deps(Unmake)
+ return nil
+}
+
+// NoVendor lists non-vendored Go source files.
+func NoVendor() error {
+ mg.Deps(mageextras.CollectGoFiles)
+
+ for pth, _ := range mageextras.CollectedGoFiles {
+ fmt.Println(pth)
+ }
+
+ return nil
+}
+
+// CleanCoverage deletes coverage data.
+func CleanCoverage() error {
+ if err := os.RemoveAll(CoverHTML); err != nil {
+ return err
+ }
+
+ return os.RemoveAll(CoverProfile)
+}
+
+// Clean deletes build artifacts.
+func Clean() error { mg.Deps(CleanCoverage); return nil }
diff --git a/vendor/github.com/mcandre/mage-extras/makefile b/vendor/github.com/mcandre/mage-extras/makefile
new file mode 100644
index 0000000..906df2b
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/makefile
@@ -0,0 +1,16 @@
+.POSIX:
+.SILENT:
+.PHONY: all
+
+all:
+ cargo install --force unmake@0.0.17
+
+ go install github.com/alexkohler/nakedret@v1.0.1
+ go install github.com/kisielk/errcheck@v1.7.0
+ go install github.com/magefile/mage@v1.14.0
+ go install github.com/mgechev/revive@v1.4.0
+ go install golang.org/x/tools/cmd/goimports@latest
+ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
+ go install golang.org/x/vuln/cmd/govulncheck@latest
+ go install honnef.co/go/tools/cmd/staticcheck@2024.1
+ go mod tidy
diff --git a/vendor/github.com/mcandre/mage-extras/nakedret.go b/vendor/github.com/mcandre/mage-extras/nakedret.go
new file mode 100644
index 0000000..e9922e2
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/nakedret.go
@@ -0,0 +1,27 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+
+ "github.com/magefile/mage/mg"
+)
+
+// Nakedret runs nakedret.
+func Nakedret(args ...string) error {
+ mg.Deps(CollectGoFiles)
+
+ for pth := range CollectedGoFiles {
+ cmd := exec.Command("nakedret")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Args = append(cmd.Args, pth)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ if err := cmd.Run(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mcandre/mage-extras/packages.go b/vendor/github.com/mcandre/mage-extras/packages.go
new file mode 100644
index 0000000..2a1741b
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/packages.go
@@ -0,0 +1,11 @@
+package mageextras
+
+import (
+ "strings"
+)
+
+// AllPackagesPath denotes all Go packages in a project.
+var AllPackagesPath = strings.Join([]string{".", "..."}, PathSeparatorString)
+
+// AllCommandsPath denotes all Go application packages in this project.
+var AllCommandsPath = strings.Join([]string{".", "cmd", "..."}, PathSeparatorString)
diff --git a/vendor/github.com/mcandre/mage-extras/pathseparator.go b/vendor/github.com/mcandre/mage-extras/pathseparator.go
new file mode 100644
index 0000000..0243dd4
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/pathseparator.go
@@ -0,0 +1,8 @@
+package mageextras
+
+import (
+ "os"
+)
+
+// PathSeparatorString models the os.PathSeparator as a string.
+var PathSeparatorString = string(os.PathSeparator)
diff --git a/vendor/github.com/mcandre/mage-extras/revive.go b/vendor/github.com/mcandre/mage-extras/revive.go
new file mode 100644
index 0000000..aa153b9
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/revive.go
@@ -0,0 +1,17 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// Revive runs revive.
+func Revive(args ...string) error {
+ cmd := exec.Command("revive")
+ cmd.Args = append(cmd.Args, "-exclude", "vendor/...")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Args = append(cmd.Args, "./...")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/snyk.go b/vendor/github.com/mcandre/mage-extras/snyk.go
new file mode 100644
index 0000000..6ecc62b
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/snyk.go
@@ -0,0 +1,16 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// SnykTest executes a snyk security audit.
+func SnykTest(args ...string) error {
+ cmd := exec.Command("snyk")
+ cmd.Args = append(cmd.Args, "test")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/sources.go b/vendor/github.com/mcandre/mage-extras/sources.go
new file mode 100644
index 0000000..9c053c2
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/sources.go
@@ -0,0 +1,84 @@
+package mageextras
+
+import (
+ "bufio"
+ "bytes"
+ "os"
+ "os/exec"
+)
+
+// GoListSourceFilesTemplate provides a standard Go template for querying
+// a project's Go source file paths.
+var GoListSourceFilesTemplate = "{{$p := .}}{{range $f := .GoFiles}}{{$p.Dir}}/{{$f}}\n{{end}}"
+
+// GoListTestFilesTemplate provides a standard Go template for querying
+// a project's Go test file paths.
+var GoListTestFilesTemplate = "{{$p := .}}{{range $f := .XTestGoFiles}}{{$p.Dir}}/{{$f}}\n{{end}}"
+
+// CollectedGoFiles represents source and test Go files in a project.
+// Populdated with CollectGoFiles().
+var CollectedGoFiles = make(map[string]bool)
+
+// CollectedGoSourceFiles represents the set of Go source files in a project.
+// Populated with CollectGoFiles().
+var CollectedGoSourceFiles = make(map[string]bool)
+
+// CollectedGoTestFiles represents the set of Go test files in a project.
+// Populdated with CollectGoFiles().
+var CollectedGoTestFiles = make(map[string]bool)
+
+// CollectGoFiles populates CollectedGoFiles, CollectedGoSourceFiles, and CollectedGoTestFiles.
+//
+// Vendored files are ignored.
+func CollectGoFiles() error {
+ var sourceOut bytes.Buffer
+ var testOut bytes.Buffer
+
+ cmdSource := exec.Command(
+ "go",
+ "list",
+ "-f",
+ GoListSourceFilesTemplate,
+ AllPackagesPath,
+ )
+ cmdSource.Stdout = &sourceOut
+ cmdSource.Stderr = os.Stderr
+
+ if err := cmdSource.Run(); err != nil {
+ return err
+ }
+
+ scannerSource := bufio.NewScanner(&sourceOut)
+
+ for scannerSource.Scan() {
+ pth := scannerSource.Text()
+
+ CollectedGoFiles[pth] = true
+ CollectedGoSourceFiles[pth] = true
+ }
+
+ cmdTest := exec.Command(
+ "go",
+ "list",
+ "-f",
+ GoListTestFilesTemplate,
+ AllPackagesPath,
+ )
+ cmdTest.Stdout = &testOut
+ cmdTest.Stderr = os.Stderr
+
+ if err := cmdTest.Run(); err != nil {
+ return err
+ }
+
+ scannerTest := bufio.NewScanner(&testOut)
+
+ for scannerTest.Scan() {
+ pth := scannerTest.Text()
+
+ CollectedGoFiles[pth] = true
+ CollectedGoTestFiles[pth] = true
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mcandre/mage-extras/staticcheck.go b/vendor/github.com/mcandre/mage-extras/staticcheck.go
new file mode 100644
index 0000000..5ca4f42
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/staticcheck.go
@@ -0,0 +1,15 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// Staticcheck runs staticcheck.
+func Staticcheck(args ...string) error {
+ cmd := exec.Command("staticcheck")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/test.go b/vendor/github.com/mcandre/mage-extras/test.go
new file mode 100644
index 0000000..8cc3043
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/test.go
@@ -0,0 +1,16 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// UnitTest executes the Go unit test suite.
+func UnitTest(args ...string) error {
+ cmd := exec.Command("go")
+ cmd.Args = append(cmd.Args, "test")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/unmake.go b/vendor/github.com/mcandre/mage-extras/unmake.go
new file mode 100644
index 0000000..42b397a
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/unmake.go
@@ -0,0 +1,15 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// Unmake runs unmake.
+func Unmake(args ...string) error {
+ cmd := exec.Command("unmake")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/version.go b/vendor/github.com/mcandre/mage-extras/version.go
new file mode 100644
index 0000000..03e434b
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/version.go
@@ -0,0 +1,4 @@
+package mageextras
+
+// Version is semver.
+var Version = "0.0.20"
diff --git a/vendor/github.com/mcandre/mage-extras/vet.go b/vendor/github.com/mcandre/mage-extras/vet.go
new file mode 100644
index 0000000..f20e61a
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/vet.go
@@ -0,0 +1,32 @@
+package mageextras
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+)
+
+// GoVetShadow runs go vet against all Go packages in a project,
+// with variable shadow checking enabled.
+//
+// Depends on golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
+func GoVetShadow() error {
+ shadowPath, err := exec.LookPath("shadow")
+
+ if err != nil {
+ return err
+ }
+
+ return GoVet(fmt.Sprintf("-vettool=%s", shadowPath))
+}
+
+// GoVet runs go vet against all Go packages in a project.
+func GoVet(args ...string) error {
+ cmd := exec.Command("go")
+ cmd.Args = append(cmd.Args, "vet")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Args = append(cmd.Args, AllPackagesPath)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/xgo.go b/vendor/github.com/mcandre/mage-extras/xgo.go
new file mode 100644
index 0000000..029d1ee
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/xgo.go
@@ -0,0 +1,26 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// Xgo cross-compiles (c)Go binaries with additional targets enabled.
+func Xgo(outputPath string, args ...string) error {
+ if err := os.MkdirAll(outputPath, os.ModeDir|0775); err != nil {
+ return err
+ }
+
+ var xgoParts []string
+ xgoParts = append(xgoParts, "-dest")
+ xgoParts = append(xgoParts, outputPath)
+ xgoParts = append(xgoParts, args...)
+
+ cmd := exec.Command(
+ "xgo",
+ xgoParts...,
+ )
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/mage-extras/yamllint.go b/vendor/github.com/mcandre/mage-extras/yamllint.go
new file mode 100644
index 0000000..d8f1212
--- /dev/null
+++ b/vendor/github.com/mcandre/mage-extras/yamllint.go
@@ -0,0 +1,15 @@
+package mageextras
+
+import (
+ "os"
+ "os/exec"
+)
+
+// Yamllint runs yamllint.
+func Yamllint(args ...string) error {
+ cmd := exec.Command("yamllint")
+ cmd.Args = append(cmd.Args, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/vendor/github.com/mcandre/slick/.editorconfig b/vendor/github.com/mcandre/slick/.editorconfig
new file mode 100644
index 0000000..8a8f2ef
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/.editorconfig
@@ -0,0 +1,63 @@
+# Most text files
+[*]
+charset = utf-8
+max_line_length = none
+trim_trailing_whitespace = true
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4 # bash8
+
+# Machine-generated files
+[*.{scpt,plist,dot,db}]
+trim_trailing_whitespace = false
+insert_final_newline = none
+indent_size = none
+
+# Freeform text
+[*.{txt,md,cob,emacs,el,lisp,lsp,scm,setup,meta,clj,rkt,ecl,sbcl,abcl,eclrc,sbclrc,asd,snusp}]
+indent_size = none
+
+# Fix cmake
+[CMakeLists.txt]
+indent_size = 4 # bash8
+
+# Align keys
+[*.{yaml,yml,reek,.yamllint}]
+indent_size = 2
+
+# Placeholder files
+[*.{gitkeep,__init__.py}]
+insert_final_newline = none
+
+#
+# Windows junk
+#
+[*.{cmd,bat,reg,ps1,vbs,cs,fs,fsx,ahk,psenvrc}]
+end_of_line = crlf
+insert_final_newline = false
+[settings.json]
+end_of_line = crlf
+insert_final_newline = false
+
+# Lexer restrictions
+[{makefile,Makefile,GNUmakefile,BSDmakefile}]
+indent_style = tab
+[*.{makefile,Makefile,mk,GNUmakefile,BSDmakefile,go,gitmodules}]
+indent_style = tab
+
+# Natural HTML nesting
+[*.js]
+quote_type = single
+
+# Compressed artifacts
+[*.min.*]
+insert_final_newline = false
+[*-min.*]
+insert_final_newline = false
+
+# Potential mixed indentations
+[*.patch]
+trim_trailing_whitespace = false
+indent_style = none
+indent_size = none
diff --git a/vendor/github.com/mcandre/slick/.gitignore b/vendor/github.com/mcandre/slick/.gitignore
new file mode 100644
index 0000000..fd5ea87
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/.gitignore
@@ -0,0 +1,20 @@
+cover.html
+bin/
+# Created by https://www.gitignore.io/api/go
+
+### Go ###
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+
+# End of https://www.gitignore.io/api/go
diff --git a/vendor/github.com/mcandre/slick/.rubberstamp b/vendor/github.com/mcandre/slick/.rubberstamp
new file mode 100644
index 0000000..6ae5f01
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/.rubberstamp
@@ -0,0 +1 @@
+Tue Oct 1 00:23:42 UTC 2024
diff --git a/vendor/github.com/mcandre/slick/.tool-versions b/vendor/github.com/mcandre/slick/.tool-versions
new file mode 100644
index 0000000..766e4b5
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/.tool-versions
@@ -0,0 +1,2 @@
+golang 1.23.2
+rust 1.75.0
diff --git a/vendor/github.com/mcandre/slick/DEVELOPMENT.md b/vendor/github.com/mcandre/slick/DEVELOPMENT.md
new file mode 100644
index 0000000..d2e9f6e
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/DEVELOPMENT.md
@@ -0,0 +1,56 @@
+# BUILDTIME REQUIREMENTS
+
+* [Go](https://go.dev/) 1.23.2+
+* POSIX compatible [make](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html)
+* [Rust](https://www.rust-lang.org/) 1.75.0+
+* [Snyk](https://snyk.io/)
+* POSIX compatible [tar](https://pubs.opengroup.org/onlinepubs/7908799/xcu/tar.html)
+* Provision additional dev tools with `make`
+
+## Recommended
+
+* [ASDF](https://asdf-vm.com/) 0.10 (run `asdf reshim` after provisioning)
+* [direnv](https://direnv.net/) 2
+* a UNIX environment, such as macOS, Linux, BSD, [WSL](https://learn.microsoft.com/en-us/windows/wsl/), etc.
+
+Non-UNIX environments may produce subtle adverse effects when linting or generating application ports.
+
+## Windows
+
+Apply a user environment variable `GODEBUG=modcacheunzipinplace=1` per [access denied resolution](https://github.com/golang/go/wiki/Modules/e93463d3e853031af84204dc5d3e2a9a710a7607#go-115), for native Windows development environments (Command Prompt / PowerShell, not WLS, not Cygwin, not MSYS2, not MinGW, not msysGit, not Git Bash, not etc).
+
+# AUDIT
+
+```console
+$ mage audit
+```
+
+# INSTALL
+
+```console
+$ mage install
+```
+
+# UNINSTALL
+
+```console
+$ mage uninstall
+```
+
+# LINT
+
+```console
+$ mage lint
+```
+
+# TEST
+
+```console
+$ mage test
+```
+
+# PORT
+
+```console
+$ mage port
+```
diff --git a/vendor/github.com/mcandre/slick/LICENSE.md b/vendor/github.com/mcandre/slick/LICENSE.md
new file mode 100644
index 0000000..51f4741
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/LICENSE.md
@@ -0,0 +1,26 @@
+Copyright (c) 2017, Andrew Pennebaker
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OR MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of the FreeBSD project.
diff --git a/vendor/github.com/mcandre/slick/README.md b/vendor/github.com/mcandre/slick/README.md
new file mode 100644
index 0000000..876eca3
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/README.md
@@ -0,0 +1,51 @@
+# slick: a CI-ready shell language syntax checker
+
+# EXAMPLES
+
+```console
+$ slick -n examples; echo "$?"
+2017/09/14 17:46:09 examples/apples.bash:2:8: arrays are a bash feature
+2017/09/14 17:46:09 examples/hello.sh:2:6: reached EOF without closing quote '
+1
+
+$ slick -help
+ -help
+ Show usage information
+ -n Validate syntax
+ -version
+ Show version information
+```
+
+# ABOUT
+
+slick provides an alternative to `sh -n`, which is problematic for a number of minor reasons:
+
+* `sh` is hardly ever a bare bones POSIX sh interpreter on most UNIX systems, but usually soft linked to `bash`, `ksh`, `ash`, or even stranger things. So anyone genuinely interested in vetting their `#!/bin/sh` scripts for compliance risks getting false negative scans for scripts that actually contain bashisms, kshisms, and so on. By contrast, `slick` guarantees pure POSIX parsing, so that scripts are scanned consistently regardless of the particular environment configuration.
+* `sh` is difficult to obtain in Windows. Cygwin-like environments are themselves difficult to setup. Should a unix, Linux, Windows, or other system desire syntax checking, `slick` is easy to obtain by gox ports, or through the wonderfully cross-platform Go toolchain.
+
+# DOWNLOAD
+
+https://github.com/mcandre/slick/releases
+
+# INSTALL FROM SOURCE
+
+```console
+$ go install github.com/mcandre/slick/cmd/slick@latest
+```
+
+# LICENSE
+
+BSD-2-Clause
+
+# RUNTIME REQUIREMENTS
+
+(None)
+
+# CONTRIBUTING
+
+For more information on developing slick itself, see [DEVELOPMENT.md](DEVELOPMENT.md).
+
+# SEE ALSO
+
+* [mvdan/sh](https://github.com/mvdan/sh) for POSIX sh parsing
+* [stank](https://github.com/mcandre/stank) for more capable validation
diff --git a/vendor/github.com/mcandre/slick/cmd/slick/main.go b/vendor/github.com/mcandre/slick/cmd/slick/main.go
new file mode 100644
index 0000000..d5bcc46
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/cmd/slick/main.go
@@ -0,0 +1,91 @@
+package main
+
+import (
+ "github.com/mcandre/slick"
+ "mvdan.cc/sh/v3/syntax"
+
+ "bufio"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+var flagSyntaxCheck = flag.Bool("n", false, "Validate syntax")
+var flagVersion = flag.Bool("version", false, "Show version information")
+var flagHelp = flag.Bool("help", false, "Show usage information")
+
+type slicker struct {
+ Parser *syntax.Parser // /!\ Nonconcurrent /!\
+ FoundError bool
+}
+
+func newSlicker() slicker {
+ return slicker{
+ Parser: syntax.NewParser(syntax.Variant(syntax.LangPOSIX)),
+ }
+}
+
+func (o *slicker) Walk(pth string, info os.FileInfo, err error) error {
+ fi, er := os.Stat(pth)
+
+ if er != nil {
+ log.Print(er)
+ o.FoundError = true
+ return nil
+ }
+
+ // Skip trying to parse directory paths
+ if fi.Mode().IsDir() {
+ return nil
+ }
+
+ // Attempt to short-circuit for Emacs and vi swap files
+ if strings.HasSuffix(pth, "~") || strings.HasSuffix(pth, ".swp") {
+ return nil
+ }
+
+ fd, er := os.Open(pth)
+
+ if er != nil {
+ log.Print(er)
+ o.FoundError = true
+ return nil
+ }
+
+ br := bufio.NewReader(fd)
+
+ _, er = o.Parser.Parse(br, pth)
+
+ if er != nil {
+ log.Print(er)
+ o.FoundError = true
+ }
+
+ return nil
+}
+
+func main() {
+ flag.Parse()
+
+ s := newSlicker()
+
+ switch {
+ case *flagSyntaxCheck:
+ paths := flag.Args()
+
+ for _, pth := range paths {
+ filepath.Walk(pth, s.Walk)
+ }
+
+ if s.FoundError {
+ os.Exit(1)
+ }
+ case *flagVersion:
+ fmt.Println(slick.Version)
+ case *flagHelp:
+ flag.PrintDefaults()
+ }
+}
diff --git a/vendor/github.com/mcandre/slick/magefile.go b/vendor/github.com/mcandre/slick/magefile.go
new file mode 100644
index 0000000..ced6482
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/magefile.go
@@ -0,0 +1,147 @@
+//go:build mage
+package main
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+
+ "github.com/magefile/mage/mg"
+ mageextras "github.com/mcandre/mage-extras"
+ "github.com/mcandre/slick"
+)
+
+// artifactsPath describes where artifacts are produced.
+var artifactsPath = "bin"
+
+// Default references the default build task.
+var Default = Test
+
+// Govulncheck runs govulncheck.
+func Govulncheck() error { return mageextras.Govulncheck("-scan", "package", "./...") }
+
+// Snyk runs Snyk SCA.
+func Snyk() error { return mageextras.SnykTest() }
+
+// Audit runs a security audit.
+func Audit() error {
+ mg.Deps(Govulncheck)
+ return Snyk()
+}
+
+// UnitTests runs the unit test suite.
+func UnitTest() error { return mageextras.UnitTest() }
+
+// IntegrationTest executes the integration test suite.
+func IntegrationTest() error {
+ mg.Deps(Install)
+
+ cmd := exec.Command("slick", "-n", "examples")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err := cmd.Run()
+
+ if err == nil {
+ return errors.New("Expected an exit status error")
+ }
+
+ return nil
+}
+
+// Test runs unit and integration tests.
+func Test() error { mg.Deps(UnitTest); mg.Deps(IntegrationTest); return nil }
+
+// CoverHTML denotes the HTML formatted coverage filename.
+var CoverHTML = "cover.html"
+
+// CoverProfile denotes the raw coverage data filename.
+var CoverProfile = "cover.out"
+
+// CoverageHTML generates HTML formatted coverage data.
+func CoverageHTML() error {
+ mg.Deps(CoverageProfile)
+ return mageextras.CoverageHTML(CoverHTML, CoverProfile)
+}
+
+// CoverageProfile generates raw coverage data.
+func CoverageProfile() error { return mageextras.CoverageProfile(CoverProfile) }
+
+// Deadcode runs deadcode.
+func Deadcode() error { return mageextras.Deadcode("./...") }
+
+// Gofmt runs gofmt.
+func GoFmt() error { return mageextras.GoFmt("-s", "-w") }
+
+// GoImports runs goimports.
+func GoImports() error { return mageextras.GoImports("-w") }
+
+// GoVet runs default go vet analyzers.
+func GoVet() error { return mageextras.GoVet() }
+
+// Errcheck runs errcheck.
+func Errcheck() error { return mageextras.Errcheck("-blank") }
+
+// Nakedret runs nakedret.
+func Nakedret() error { return mageextras.Nakedret("-l", "0") }
+
+// Shadow runs go vet with shadow checks enabled.
+func Shadow() error { return mageextras.GoVetShadow() }
+
+// Staticcheck runs staticcheck.
+func Staticcheck() error { return mageextras.Staticcheck() }
+
+// Unmake runs unmake.
+func Unmake() error {
+ err := mageextras.Unmake(".")
+
+ if err != nil {
+ return err
+ }
+
+ return mageextras.Unmake("-n", ".")
+}
+
+// Lint runs the lint suite.
+func Lint() error {
+ mg.Deps(Deadcode)
+ mg.Deps(GoFmt)
+ mg.Deps(GoImports)
+ mg.Deps(GoVet)
+ mg.Deps(Errcheck)
+ mg.Deps(Nakedret)
+ mg.Deps(Shadow)
+ mg.Deps(Staticcheck)
+ mg.Deps(Unmake)
+ return nil
+}
+
+// portBasename labels the artifact basename.
+var portBasename = fmt.Sprintf("slick-%s", slick.Version)
+
+// repoNamespace identifies the Go namespace for this project.
+var repoNamespace = "github.com/mcandre/slick"
+
+// Factorio cross-compiles Go binaries for a multitude of platforms.
+func Factorio() error { return mageextras.Factorio(portBasename) }
+
+// Port builds and compresses artifacts.
+func Port() error { mg.Deps(Factorio); return mageextras.Archive(portBasename, artifactsPath) }
+
+// Install builds and installs Go applications.
+func Install() error { return mageextras.Install() }
+
+// Uninstall deletes installed Go applications.
+func Uninstall() error { return mageextras.Uninstall("slick") }
+
+// CleanCoverage deletes coverage data.
+func CleanCoverage() error {
+ if err := os.RemoveAll(CoverHTML); err != nil {
+ return err
+ }
+
+ return os.RemoveAll(CoverProfile)
+}
+
+// Clean deletes artifacts.
+func Clean() error { mg.Deps(CleanCoverage); return os.RemoveAll(artifactsPath) }
diff --git a/vendor/github.com/mcandre/slick/makefile b/vendor/github.com/mcandre/slick/makefile
new file mode 100644
index 0000000..929a32e
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/makefile
@@ -0,0 +1,17 @@
+.POSIX:
+.SILENT:
+.PHONY: all
+
+all:
+ cargo install --force unmake@0.0.17
+
+ go install github.com/alexkohler/nakedret@v1.0.1
+ go install github.com/kisielk/errcheck@v1.7.0
+ go install github.com/magefile/mage@v1.14.0
+ go install github.com/mcandre/factorio/cmd/factorio@v0.0.6
+ go install golang.org/x/tools/cmd/deadcode@latest
+ go install golang.org/x/tools/cmd/goimports@latest
+ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
+ go install golang.org/x/vuln/cmd/govulncheck@latest
+ go install honnef.co/go/tools/cmd/staticcheck@2024.1
+ go mod tidy
diff --git a/vendor/github.com/mcandre/slick/version.go b/vendor/github.com/mcandre/slick/version.go
new file mode 100644
index 0000000..d14bc74
--- /dev/null
+++ b/vendor/github.com/mcandre/slick/version.go
@@ -0,0 +1,4 @@
+package slick
+
+// Version is semver.
+var Version = "0.0.10"
diff --git a/vendor/github.com/mcandre/stank/.editorconfig b/vendor/github.com/mcandre/stank/.editorconfig
new file mode 100644
index 0000000..8a8f2ef
--- /dev/null
+++ b/vendor/github.com/mcandre/stank/.editorconfig
@@ -0,0 +1,63 @@
+# Most text files
+[*]
+charset = utf-8
+max_line_length = none
+trim_trailing_whitespace = true
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4 # bash8
+
+# Machine-generated files
+[*.{scpt,plist,dot,db}]
+trim_trailing_whitespace = false
+insert_final_newline = none
+indent_size = none
+
+# Freeform text
+[*.{txt,md,cob,emacs,el,lisp,lsp,scm,setup,meta,clj,rkt,ecl,sbcl,abcl,eclrc,sbclrc,asd,snusp}]
+indent_size = none
+
+# Fix cmake
+[CMakeLists.txt]
+indent_size = 4 # bash8
+
+# Align keys
+[*.{yaml,yml,reek,.yamllint}]
+indent_size = 2
+
+# Placeholder files
+[*.{gitkeep,__init__.py}]
+insert_final_newline = none
+
+#
+# Windows junk
+#
+[*.{cmd,bat,reg,ps1,vbs,cs,fs,fsx,ahk,psenvrc}]
+end_of_line = crlf
+insert_final_newline = false
+[settings.json]
+end_of_line = crlf
+insert_final_newline = false
+
+# Lexer restrictions
+[{makefile,Makefile,GNUmakefile,BSDmakefile}]
+indent_style = tab
+[*.{makefile,Makefile,mk,GNUmakefile,BSDmakefile,go,gitmodules}]
+indent_style = tab
+
+# Natural HTML nesting
+[*.js]
+quote_type = single
+
+# Compressed artifacts
+[*.min.*]
+insert_final_newline = false
+[*-min.*]
+insert_final_newline = false
+
+# Potential mixed indentations
+[*.patch]
+trim_trailing_whitespace = false
+indent_style = none
+indent_size = none
diff --git a/vendor/github.com/mcandre/stank/.gitignore b/vendor/github.com/mcandre/stank/.gitignore
new file mode 100644
index 0000000..f508727
--- /dev/null
+++ b/vendor/github.com/mcandre/stank/.gitignore
@@ -0,0 +1,27 @@
+cover.html
+bin
+# Created by https://www.toptal.com/developers/gitignore/api/go
+# Edit at https://www.toptal.com/developers/gitignore?templates=go
+
+### Go ###
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+### Go Patch ###
+# /vendor/
+/Godeps/
+
+# End of https://www.toptal.com/developers/gitignore/api/go
diff --git a/vendor/github.com/mcandre/stank/.rubberstamp b/vendor/github.com/mcandre/stank/.rubberstamp
new file mode 100644
index 0000000..ee76a9c
--- /dev/null
+++ b/vendor/github.com/mcandre/stank/.rubberstamp
@@ -0,0 +1 @@
+Tue Oct 1 00:32:49 UTC 2024
diff --git a/vendor/github.com/mcandre/stank/.tool-versions b/vendor/github.com/mcandre/stank/.tool-versions
new file mode 100644
index 0000000..766e4b5
--- /dev/null
+++ b/vendor/github.com/mcandre/stank/.tool-versions
@@ -0,0 +1,2 @@
+golang 1.23.2
+rust 1.75.0
diff --git a/vendor/github.com/mcandre/stank/DEVELOPMENT.md b/vendor/github.com/mcandre/stank/DEVELOPMENT.md
new file mode 100644
index 0000000..daa5892
--- /dev/null
+++ b/vendor/github.com/mcandre/stank/DEVELOPMENT.md
@@ -0,0 +1,58 @@
+# BUILDTIME REQUIREMENTS
+
+* GNU or BSD [findutils](https://en.wikipedia.org/wiki/Find_(Unix))
+* [Go](https://go.dev/) 1.23.2+
+* [jq](https://jqlang.github.io/jq/)
+* POSIX compatible [make](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html)
+* [Rust](https://www.rust-lang.org/) 1.75.0+
+* [Snyk](https://snyk.io/)
+* POSIX compatible [tar](https://pubs.opengroup.org/onlinepubs/7908799/xcu/tar.html)
+* Provision additional dev tools with `make`
+
+## Recommended
+
+* [ASDF](https://asdf-vm.com/) 0.10 (run `asdf reshim` after provisioning)
+* [direnv](https://direnv.net/) 2
+* a UNIX environment, such as macOS, Linux, BSD, [WSL](https://learn.microsoft.com/en-us/windows/wsl/), etc.
+
+Non-UNIX environments may produce subtle adverse effects when linting or generating application ports.
+
+## Windows
+
+Apply a user environment variable `GODEBUG=modcacheunzipinplace=1` per [access denied resolution](https://github.com/golang/go/wiki/Modules/e93463d3e853031af84204dc5d3e2a9a710a7607#go-115), for native Windows development environments (Command Prompt / PowerShell, not WLS, not Cygwin, not MSYS2, not MinGW, not msysGit, not Git Bash, not etc).
+
+# AUDIT
+
+```console
+$ mage audit
+```
+
+# INSTALL
+
+```console
+$ mage install
+```
+
+# UNINSTALL
+
+```console
+$ mage uninstall
+```
+
+# TEST: UNIT + INTEGRATION
+
+```console
+$ mage test
+```
+
+# LINT
+
+```console
+$ mage lint
+```
+
+# PORT
+
+```console
+$ mage port
+```
diff --git a/vendor/github.com/mcandre/stank/LICENSE.md b/vendor/github.com/mcandre/stank/LICENSE.md
new file mode 100644
index 0000000..51f4741
--- /dev/null
+++ b/vendor/github.com/mcandre/stank/LICENSE.md
@@ -0,0 +1,26 @@
+Copyright (c) 2017, Andrew Pennebaker
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OR MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of the FreeBSD project.
diff --git a/vendor/github.com/mcandre/stank/README.md b/vendor/github.com/mcandre/stank/README.md
new file mode 100644
index 0000000..c872782
--- /dev/null
+++ b/vendor/github.com/mcandre/stank/README.md
@@ -0,0 +1,277 @@
+# stank: analyzers for determining whether files smell like rotten POSIX shell scripts, or faintly rosy like Ruby and Python scripts
+
+# ABOUT
+
+stank is a library and collection of command line utilities for sniffing files to identify shell scripts like bash, sh, zsh, ksh and so on, those funky farmfresh gobs of garbaggio; versus other more palatable files like rb, py, pl.
+
+Believe it or not, shell scripts are notoriously difficult to write well, so it behooves a developer to either write shell scripts in safer languages, or else wargame your scripts with an armada of linters. Trouble is, in large projects one can never be too sure which files are honest to dog POSIX compliant shell scripts, and which are pretenders. csh, tcsh, fish, ion, rc, and most other nonderivatives of bash tend to be NOT POSIX compatible. If you're geeky enough to have followed thus far, let's get crackalackin with some fruity examples dammit!
+
+# EXAMPLES
+
+The stank system includes the stank Go library as well as several command line utilities for convenience. The `stank` application scans directories and files for POSIX-derived shell scripts and prints their paths, designed as a convenient standalone filter for linting large collections of source code.
+
+```console
+$ cd examples
+
+$ stank .
+.profile
+.shrc
+.zlogin
+...
+```
+
+The `stank` command line utility searches file paths for shell scripts that may warrant linting.
+
+stank integrates with external linters, helping to feed them a more focused set of file paths to analyze within larger project directories.
+
+```console
+$ stank -print0 . | xargs -0 -n 1 shellcheck
+In welcome.sh line 1:
+#!bash
+^----^ SC2239 (error): Ensure the shebang uses an absolute path to the interpreter.
+
+For more information:
+ https://www.shellcheck.net/wiki/SC2239 -- Ensure the shebang uses an absolu...
+```
+
+Machine-generated files, including git hook default `*.sample` files, are automatically skipped.
+
+See `stank -help` for additional options.
+
+# DOWNLOADS
+
+https://github.com/mcandre/stank/releases
+
+# INSTALL FROM SOURCE
+
+```console
+$ go install github.com/mcandre/stank/...@latest
+```
+
+# DOCUMENTATION
+
+https://pkg.go.dev/github.com/mcandre/stank
+
+# MORE EXAMPLES
+
+The `funk` linter reports strange odors emanating from scripts, such as improper line endings, the presence of Byte Order Marker's in some Unicode scripts.
+
+```console
+$ funk examples
+Ambiguous launch style. Either feature a file extensions, or else feature executable bits: examples/.shrc
+Tokenize like `unset IFS` at the top of executable scripts: examples/.shrc
+Control program flow like `set -euf` at the top of executable scripts: examples/.shrc
+Tokenize like `unset IFS` at the top of executable scripts: examples/badconfigs/zprofile
+Control program flow like `set -euf` at the top of executable scripts: examples/badconfigs/zprofile
+Missing shebang: examples/blank.bash
+Traps may reset in subshells: examples/cleanup.sh
+Missing shebang: examples/goodbye.sh
+Missing shebang: examples/greetings.bash
+Control program flow like `set -euf` at the top of executable scripts: examples/hello-commented
+
+$ funk -modulino examples
+Configuration features shebang: examples/badconfigs/.bash_profile
+Configuration features executable permissions: examples/badconfigs/zprofile
+Missing final end of line sequence: examples/blank.bash
+Missing shebang: examples/blank.bash
+Interpreter mismatch between shebang and extension: examples/derp.zsh
+Missing shebang: examples/greetings.bash
+Missing final end of line sequence: examples/hello-crlf.sh
+CR/CRLF line ending detected: examples/hello-crlf.sh
+Modulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/hello-crlf.sh
+Modulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/howdy
+Missing shebang: examples/howdy.zsh
+Missing shebang: examples/just-eol.bash
+Modulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/lo
+Missing final end of line sequence: examples/lo-cr.csh
+CR/CRLF line ending detected: examples/lo-cr.csh
+Modulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/pipefail
+Modulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/shout.sh
+Modulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/wednesday
+Modulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/wednesday-bom
+Leading BOM reduces portability: examples/wednesday-bom
+Modulino ambiguity. Either have owner executable permissions with no extension, or else remove executable bits and use an extension like .lib.sh: examples/welcome
+
+$ funk -help
+ -cr
+ Report presence/absence of final end of line sequence (default true)
+ -eol
+ Report presence/absence of final end of line sequence (default true)
+ -help
+ Show usage information
+ -modulino
+ Enforce strict separation of application scripts vs. library scripts
+ -version
+ Show version information
+```
+
+Both `stank` and `funk` have the ability to select low level, nonPOSIX scripts as well, such as csh/tcsh scripts used in FreeBSD.
+
+Note that funk cannot reliably warn for missing shebangs if the extension is also missing; typically, script authors use one or the other to mark files as shell scripts. Lacking both a shebang and a file extension, means that a file could contain code for many languages, making it difficult to determine the POSIXy nature of the code. Even if an exhaustive set of ASTs are applied to test the file contents for syntactical validity across the dozens of available shell languages, there is a strong possibility in shorter files that the contents are merely incidentally valid script syntax, though the intent of the file is not to operate as a POSIX shell script. Short, nonPOSIX scripts such as for csh/tcsh could easily trigger a "POSIX" syntax match. In any case, know that the shebang is requisite for ensuring your scripts are properly interpreted.
+
+Note that funk may fail to present permissions warnings if the scripts are housed on non-UNIX file systems such as NTFS, where executable bits are often missing from the file metadata altogether. When storing shell scripts, be sure to set the appropriate file permissions, and transfer files as a bundle in a tarball or similar to safeguard against dropped permissions.
+
+Note that funk may warn of interpreter mismatches for scripts with extraneous dots in the filename. Rather than `.envrc.sample`, name the file `sample.envrc`. Rather than `wget-google.com`, name the file `wget-google-com`. Appending `.sh` is also an option, so `update.es.cluster` renames to `update.es.cluster.sh`.
+
+The optional `-modulino` flag to funk enables strict separation of script duties, into distinct application scripts vs. library scripts. Application scripts are generally executed by invoking the path, such as `./hello` or `~/bin/hello` or simply `hello` when `$PATH` is appropriately modified. Application scripts feature owner executable permissions, and perhaps group and other as well depending on system configuration needs. In contrast, library scripts are intended to be imported with dot (`.`) or `source` into user shells or other scripts, and should feature a file extension like `.lib.sh`, `.sh`, `.bash`, etc. By using separate naming conventions, we more quickly communicate to downstream users how to interact with a shell script. In particular, by dropping file extensions for shell script applications, we encourage authors to choose more meaningful script names. Instead of the generic `build.sh`, choose `build-docker`. Instead of `kafka.sh`, choose `start-kafka`, `kafka-entrypoint`, etc.
+
+Finally, `stink` prints a record of each file's POSIXyness, including any interesting fields it identified along the way. Note that some fields may be zero valued if the stench of POSIX or rosy waft of nonPOSIX is overwhelming, short-circuiting analysis. This short-circuiting feature dramatically speeds up how `stank` searches large projects.
+
+Note that permissions are relayed as decimals, due to constraints on JSON integer formatting (we didn't want to use a custom octal string field). Use `echo 'obase=8; | bc` to display these values in octal.
+
+Note that legacy systems, packages, and shell scripts referencing "sh" may refer to a plethora of pre-POSIX shells. Modern systems rename "sh" to "lksh", "tsh", "etsh", etc. to avoid confusion. In general, the stank suite will assume that the majority of scripts being scanned are targeting post-1971 technology, so use your human intuition and context to note any legacy Thompson UNIX v6 "sh", etc. scripts. Most modern linters will neither be able to parse such scripts of any complexity, nor will they recognize them for the legacy scripts that they are, unless the scripts' shebangs are rendered with the modern retro interpreters "lksh", "tsh", "etsh", etc. for deployment on modern UNIX systems. One could almost use the fs stats for modification/change to try to identify these legacy outliers, but this is a practically unrealistic assumption except for the most obsessive archaeologist, diligently ensuring their legacy scripts continue to present 1970's metadata even after experimental content modifications. So the stank system will simply punt and assume sh -> POSIX sh, ksh -> ksh88 / ksh93 for the sake of modernity and balance.
+
+Similarly, the old Bourne shell AKA "sh" AKA "bsh" presents language identification difficulties. Old Bourne shell scripts are most likely to present themselves with "sh" shebangs, which is okay as Bourne sh and ksh88/pdksh/ksh served as the bases for the POSIX sh standard. Some modern systems may present a Bourne shell as a "sh" or "bsh" binary. The former presents few problems for stank identification, though "bsh" is tricky, as the majority of its uses today are not associated with the Bourne shell but with the Java BeanShell. So stank may default to treating `bsh` scripts as non-POSIXy, and any such Bourne shell scripts are advised to feature either `bash` or `sh` shebangs, and perhaps `.sh` or `.bash` extensions, in order to self-identify as modern, POSIX compliant scripts.
+
+```console
+$ stink examples/hello
+{"Path":"examples/hello","Filename":"hello","Basename":"hello","Extension":"","Shebang":"#!/bin/sh","Interpreter":"sh","LineEnding":"\n","FinalEOL":false,"ContainsCR":false
+,"Permissions":509,"Directory":false,"OwnerExecutable":true,"BOM":false,"POSIXy":true,"AltShellScript":false}
+
+$ stink -pp examples/hello
+{
+ "Path": "examples/hello",
+ "Filename": "hello",
+ "Basename": "hello",
+ "Extension": "",
+ "Shebang": "#!/bin/sh",
+ "Interpreter": "sh",
+ "LineEnding": "\n",
+ "FinalEOL": false,
+ "ContainsCR": false,
+ "Permissions": 509,
+ "Directory": false,
+ "OwnerExecutable": true,
+ "BOM": false,
+ "POSIXy": true,
+ "AltShellScript": false
+}
+
+$ stink -pp examples/hello.py
+{
+ "Path": "examples/hello.py",
+ "Filename": "hello.py",
+ "Basename": "hello.py",
+ "Extension": ".py",
+ "Shebang": "#!/usr/bin/env python",
+ "Interpreter": "python",
+ "LineEnding": "\n",
+ "FinalEOL": false,
+ "ContainsCR": false,
+ "Permissions": 420,
+ "Directory": false,
+ "OwnerExecutable": false,
+ "BOM": false,
+ "POSIXy": false,
+ "AltShellScript": false
+}
+
+$ stink -help
+ -cr
+ Report presence/absence of any CR/CRLF's
+ -eol
+ Report presence/absence of final end of line sequence
+ -help
+ Show usage information
+ -pp
+ Prettyprint smell records
+ -version
+ Show version information
+```
+
+The included `examples/` directory demonstrates many edge cases, such as empty scripts, shebang-less scripts, extensioned and extensionless scripts, and various Hello World applications in across many programming languages. Some files, such as `examples/goodbye` may contain 100% valid POSIX shell script content, but fail to self-identify with either shebangs or relevant file extensions. In a large project, such files may be mistakenly treated as whoknowswhat format, or simply plain text. Perhaps statistical methods could help identify POSIX grammars, but even an empty file is technically POSIX, which is unhelpful from a reliable classification standpoint. In any case, `examples/` hopefully covers the more common edge cases.
+
+One way to think of `stank` is a bounty hunter for shell scripts.
+
+Given that shell tends to be more fragile than higher level programming languages, then it is a good idea to rewrite shell code as dedicated applications. Go and Rust are especially good choices for application languages.
+
+The Rust programming language has best in class performance, reliability, and security. The Go programming language has comparable performance, reliability, and security in most contexts. Both Rust and Go support cross-compilation and static executables, so that it's much easier to develop, test, package, and distribute Rust/Go applications compared to flaky shell scripts. Most shell coders neglect to consider subtle vendor locking problems with shell syntax and the flags used for individual commands. Rust has a steeper learning curve than some coders are willing to devote time for. Often, Go can serve as a compromise. Being compiled languages, both Rust and Go are protected from many runtime pitfalls that shells and other interpreted languages invite.
+
+Regardless, the particular programming language is a less important, concern, as long as it is not shell. Notoriously hazardous programming languages like JavaScript and Perl, are still safer than shell. Shell (any flavor) is a trash fire waiting for a spark.
+
+Fortunately, the list of shell scripts that `stank` emits, can help engineers to identify program candidates to rewrite in more mature programming languages.
+
+# LICENSE
+
+BSD-2-Clause
+
+# RUNTIME REQUIREMENTS
+
+(None)
+
+## Recommended
+
+* GNU or BSD [findutils](https://en.wikipedia.org/wiki/Find_(Unix))
+* [jq](https://jqlang.github.io/jq/)
+
+# CONTRIBUTING
+
+For more details on developing stank itself, see [DEVELOPMENT.md](DEVELOPMENT.md).
+
+# WARNING ON FALSE NEGATIVES
+
+Note that very many software components have a bad habit of encouraging embedded, inline shell script snippets into non-shell script files. For example, CI/CD job configurations, Dockerfile RUN steps, Kubernetes resources, and make. Most linter tools (for shell scripts and other languages) have very limited or nonexistent support for linting inline shell script snippets.
+
+Accordingly, move shell script snippets to a dedicated shell script file. And then have the software component execute the shell script. Then you will be able to lint the shell code with more tools, and thereby raise the quality level of your system.
+
+# WARNING ON FALSE POSITIVES
+
+Some rather obscure files, such as Common Lisp source code with multiline, polyglot shebangs and no file extension, may falsely trigger the stank library, and the stink and stank applications, which short-circuit on the first line of the hacky shebang. Such files may be falsely identified as "POSIX" code, which is actually the intended behavior! This is because the polyglot shebang is a hack to work around limitations in the Common Lisp language, which ordinarily does not accept POSIX shebang comments, in order to get Common Lisp scripts to be dot-slashable in bash. For this situation, it is best to supply a proper file extension to such files.
+
+```console
+$ head examples/i-should-have-an-extension
+#!/usr/bin/env sh
+#|
+exec clisp -q -q $0 $0 ${1+"$@"}
+|#
+
+(defun hello-main (args)
+ (format t "Hello from main!~%"))
+
+;;; With help from Francois-Rene Rideau
+;;; http://tinyurl.com/cli-args
+
+$ stink -pp examples/i-should-have-an-extension
+{
+ "Path": "examples/i-should-have-an-extension",
+ "Filename": "i-should-have-an-extension",
+ "Basename": "i-should-have-an-extension",
+ "Extension": "",
+ "BOM": false,
+ "Shebang": "#!/usr/bin/env sh",
+ "Interpreter": "sh",
+ "LineEnding": "\n",
+ "POSIXy": true
+}
+```
+
+Perhaps append a `.lisp` extension to such files. Or separate the modulino into clear library vs. command line modules. Or extract the shell interaction into a dedicated script. Or convince the language maintainers to treat shebangs as comments. Write your congressman. However you resolve this, know that the current situation is far outside the norm, and likely to break in a suitably arcane and dramatic fashion. With wyverns and flaming seas and portents of all ill manner.
+
+# Shell script linters
+
+These bad bois help to shore up ur shell scripts. Though they're designed to work on individual files, so be sure to stank-ify larger projects and pipe the results to `xargs [-0] [-n ... shellcheck`.
+
+* [bashate](https://pypi.python.org/pypi/bashate)
+* [shlint](https://rubygems.org/gems/shlint)
+* [ShellCheck](https://hackage.haskell.org/package/ShellCheck)
+* [editorconfig-cli](https://github.com/amyboyd/editorconfig-cli)
+* [shfmt](https://github.com/mvdan/sh)
+* [astyle](http://astyle.sourceforge.net)
+
+## Honorable mentions
+
+[ack](https://beyondgrep.com) offers `--shell [-f]` flags that act similarly to `stank`, with the caveat that ack includes nonPOSIX shells like csh, tcsh, and fish in these results; but as of this writing fails to include POSIX shells like ash, dash, posh, pdksh, ksh93, and mksh. ack also depends on Perl, making it more heavyweight for Docker microservices and other constrained platforms.
+
+[kirill](https://github.com/mcandre/kirill) identifies JSON documents.
+
+[linguist](https://github.com/github/linguist), GitHub's extraordinary effort to identify which language each of its millions of repositories are written in. While this stanky Go project does not employ linguist in automated analysis, it's worth mentioning for forensic purposes, if you ever come across a strange, unidentified (or misidentified!) source code file.
+
+[linters](https://github.com/mcandre/linters), a wiki of common programming language linters and SAST tools.
+
+[periscope](https://github.com/mcandre/periscope) warns on unscoped NPM packages.
+
+[sail](https://github.com/mcandre/sail) identifies C/C++ source code files.
+
+[slick](https://github.com/mcandre/slick) offers `sh -n` syntax checking against pure POSIX syntax, whereas actual `sh` on most systems symlinks to bash.
+
+[unmake](https://github.com/mcandre/unmake), a linter for makefiles.
diff --git a/vendor/github.com/mcandre/stank/cmd/funk/main.go b/vendor/github.com/mcandre/stank/cmd/funk/main.go
new file mode 100644
index 0000000..2f8bfc9
--- /dev/null
+++ b/vendor/github.com/mcandre/stank/cmd/funk/main.go
@@ -0,0 +1,540 @@
+// Package main implements a shell script linter CLI application,
+// with unique checks on fundamental portability, safety, and security concerns.
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "github.com/mcandre/stank"
+)
+
+var flagEOL = flag.Bool("eol", true, "Report presence/absence of final end of line sequence")
+var flagCR = flag.Bool("cr", true, "Report presence/absence of final end of line sequence")
+var flagModulino = flag.Bool("modulino", false, "Enforce strict separation of application scripts vs. library scripts")
+var flagHelp = flag.Bool("help", false, "Show usage information")
+var flagVersion = flag.Bool("version", false, "Show version information")
+
+// Funk holds configuration for a funky walk.
+type Funk struct {
+ EOLCheck bool
+ CRCheck bool
+ ModulinoCheck bool
+ FoundOdor bool
+}
+
+// CheckEOL analyzes POSIXy scripts for the presence/absence of a final end of line sequence such as \n at the end of a file, \r\n, etc.
+func CheckEOL(smell stank.Smell) bool {
+ if smell.FinalEOL != nil && !(*smell.FinalEOL) {
+ fmt.Printf("Missing final end of line sequence: %s\n", smell.Path)
+ return true
+ }
+
+ return false
+}
+
+// CheckCR analyzes POSIXy scripts for the presence/absence of a CR/CRLF line ending sequence.
+func CheckCR(smell stank.Smell) bool {
+ if smell.ContainsCR {
+ fmt.Printf("CR/CRLF line ending detected: %s\n", smell.Path)
+ return true
+ }
+
+ return false
+}
+
+// CheckBOMs analyzes POSIXy scripts for byte order markers. If a BOM is found, CheckBOMs prints a warning and returns true.
+// Otherwise, CheckBOMs returns false.
+func CheckBOMs(smell stank.Smell) bool {
+ if smell.BOM {
+ fmt.Printf("Leading BOM reduces portability: %s\n", smell.Path)
+
+ return true
+ }
+
+ return false
+}
+
+// CheckShebangs analyzes POSIXy scripts for some shebang oddities. If an oddity is found, CheckShebangs prints a warning and returns true.
+// Otherwise, CheckShebangs returns false.
+//
+// Note: While shell safety flags are risky when placed in shebangs,
+// Unfortunately many non-POSIXy languages unfortunately require such flags:
+// sed, awk, Emacs Lisp, Fourth, Octave, Mathematica, ...
+// Therefore, CheckShebangs may trigger unactionable warnings when run on non-POSIXy files.
+func CheckShebangs(smell stank.Smell) bool {
+ if stank.LOWEREXTENSIONS2CONFIG[strings.ToLower(smell.Extension)] || stank.LOWERFILENAMES2CONFIG[strings.ToLower(smell.Filename)] {
+ return false
+ }
+
+ if smell.Shebang == "" {
+ fmt.Printf("Missing shebang: %s\n", smell.Path)
+ return true
+ }
+
+ if !strings.HasPrefix(smell.Shebang, "#!") {
+ fmt.Printf("Shebang appears to be flipped: %v\n", smell.Path)
+ return true
+ }
+
+ if !strings.HasPrefix(smell.Shebang, "#!/") {
+ fmt.Printf("Shebang application should be absolute and non-nested: %v\n", smell.Path)
+ return true
+ }
+
+ if strings.Contains(smell.Shebang[2:], "#") {
+ fmt.Printf("Commented shebangs may be unparsable: %v\n", smell.Path)
+ return true
+ }
+
+ if len(smell.InterpreterFlags) != 0 {
+ fmt.Printf("Risk of parse error for interpreter space / secondary argument. Any safety flags will be ignored on `%v