diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..fd7388a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# See: https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#about-the-dependabotyml-file +version: 2 + +updates: + # Configure check for outdated GitHub Actions actions in workflows. + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/dependabot/README.md + # See: https://docs.github.com/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot + - package-ecosystem: github-actions + directory: / # Check the repository's workflows under /.github/workflows/ + schedule: + interval: daily + labels: + - "topic: infrastructure" diff --git a/.github/workflows/check-go-dependencies-task.yml b/.github/workflows/check-go-dependencies-task.yml new file mode 100644 index 0000000..6cb341d --- /dev/null +++ b/.github/workflows/check-go-dependencies-task.yml @@ -0,0 +1,141 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-go-dependencies-task.md +name: Check Go Dependencies + +env: + # See: https://github.com/actions/setup-go/tree/v3#readme + GO_VERSION: "1.13" + +# See: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows +on: + create: + push: + paths: + - ".github/workflows/check-go-dependencies-task.ya?ml" + - ".licenses/**" + - ".licensed.json" + - ".licensed.ya?ml" + - "Taskfile.ya?ml" + - "**/.gitmodules" + - "**/go.mod" + - "**/go.sum" + pull_request: + paths: + - ".github/workflows/check-go-dependencies-task.ya?ml" + - ".licenses/**" + - ".licensed.json" + - ".licensed.ya?ml" + - "Taskfile.ya?ml" + - "**/.gitmodules" + - "**/go.mod" + - "**/go.sum" + schedule: + # Run periodically to catch breakage caused by external changes. + - cron: "0 8 * * WED" + workflow_dispatch: + repository_dispatch: + +jobs: + run-determination: + runs-on: ubuntu-latest + outputs: + result: ${{ steps.determination.outputs.result }} + steps: + - name: Determine if the rest of the workflow should run + id: determination + run: | + RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x" + # The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead. + if [[ + "${{ github.event_name }}" != "create" || + "${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX + ]]; then + # Run the other jobs. + RESULT="true" + else + # There is no need to run the other jobs. + RESULT="false" + fi + + echo "::set-output name=result::$RESULT" + + check-cache: + needs: run-determination + if: needs.run-determination.outputs.result == 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install licensed + uses: jonabc/setup-licensed@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Install Task + uses: arduino/setup-task@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Update dependencies license metadata cache + run: task --silent general:cache-dep-licenses + + - name: Check for outdated cache + id: diff + run: | + git add . + if ! git diff --cached --color --exit-code; then + echo + echo "::error::Dependency license metadata out of sync. See: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-go-dependencies-task.md#metadata-cache" + exit 1 + fi + + # Some might find it convenient to have CI generate the cache rather than setting up for it locally + - name: Upload cache to workflow artifact + if: failure() && steps.diff.outcome == 'failure' + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + include-hidden-files: true + name: dep-licenses-cache + path: .licenses/ + + check-deps: + needs: run-determination + if: needs.run-determination.outputs.result == 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install licensed + uses: jonabc/setup-licensed@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Install Task + uses: arduino/setup-task@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Check for dependencies with unapproved licenses + run: task --silent general:check-dep-licenses diff --git a/.github/workflows/check-license.yml b/.github/workflows/check-license.yml new file mode 100644 index 0000000..9fcd4cf --- /dev/null +++ b/.github/workflows/check-license.yml @@ -0,0 +1,96 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-license.md +name: Check License + +env: + EXPECTED_LICENSE_FILENAME: LICENSE + # SPDX identifier: https://spdx.org/licenses/ + EXPECTED_LICENSE_TYPE: GPL-2.0 + +# See: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows +on: + create: + push: + paths: + - ".github/workflows/check-license.ya?ml" + # See: https://github.com/licensee/licensee/blob/master/docs/what-we-look-at.md#detecting-the-license-file + - "[cC][oO][pP][yY][iI][nN][gG]*" + - "[cC][oO][pP][yY][rR][iI][gG][hH][tH]*" + - "[lL][iI][cC][eE][nN][cCsS][eE]*" + - "[oO][fF][lL]*" + - "[pP][aA][tT][eE][nN][tT][sS]*" + pull_request: + paths: + - ".github/workflows/check-license.ya?ml" + - "[cC][oO][pP][yY][iI][nN][gG]*" + - "[cC][oO][pP][yY][rR][iI][gG][hH][tH]*" + - "[lL][iI][cC][eE][nN][cCsS][eE]*" + - "[oO][fF][lL]*" + - "[pP][aA][tT][eE][nN][tT][sS]*" + schedule: + # Run periodically to catch breakage caused by external changes. + - cron: "0 6 * * WED" + workflow_dispatch: + repository_dispatch: + +jobs: + run-determination: + runs-on: ubuntu-latest + outputs: + result: ${{ steps.determination.outputs.result }} + steps: + - name: Determine if the rest of the workflow should run + id: determination + run: | + RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x" + # The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead. + if [[ + "${{ github.event_name }}" != "create" || + "${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX + ]]; then + # Run the other jobs. + RESULT="true" + else + # There is no need to run the other jobs. + RESULT="false" + fi + + echo "::set-output name=result::$RESULT" + + check-license: + needs: run-determination + if: needs.run-determination.outputs.result == 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ruby # Install latest version + + - name: Install licensee + run: gem install licensee + + - name: Check license file + run: | + EXIT_STATUS=0 + # See: https://github.com/licensee/licensee + LICENSEE_OUTPUT="$(licensee detect --json --confidence=100)" + + DETECTED_LICENSE_FILE="$(echo "$LICENSEE_OUTPUT" | jq .matched_files[0].filename | tr --delete '\r')" + echo "Detected license file: $DETECTED_LICENSE_FILE" + if [ "$DETECTED_LICENSE_FILE" != "\"${EXPECTED_LICENSE_FILENAME}\"" ]; then + echo "::error file=${DETECTED_LICENSE_FILE}::detected license file $DETECTED_LICENSE_FILE doesn't match expected: $EXPECTED_LICENSE_FILENAME" + EXIT_STATUS=1 + fi + + DETECTED_LICENSE_TYPE="$(echo "$LICENSEE_OUTPUT" | jq .matched_files[0].matched_license | tr --delete '\r')" + echo "Detected license type: $DETECTED_LICENSE_TYPE" + if [ "$DETECTED_LICENSE_TYPE" != "\"${EXPECTED_LICENSE_TYPE}\"" ]; then + echo "::error file=${DETECTED_LICENSE_FILE}::detected license type $DETECTED_LICENSE_TYPE doesn't match expected \"${EXPECTED_LICENSE_TYPE}\"" + EXIT_STATUS=1 + fi + + exit $EXIT_STATUS diff --git a/.github/workflows/check-markdown-task.yml b/.github/workflows/check-markdown-task.yml new file mode 100644 index 0000000..5f2a772 --- /dev/null +++ b/.github/workflows/check-markdown-task.yml @@ -0,0 +1,112 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-markdown-task.md +name: Check Markdown + +env: + # See: https://github.com/actions/setup-node/#readme + NODE_VERSION: 16.x + +# See: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows +on: + create: + push: + paths: + - ".github/workflows/check-markdown-task.ya?ml" + - ".markdown-link-check.json" + - "package.json" + - "package-lock.json" + - "Taskfile.ya?ml" + - "**/.markdownlint*" + - "**.mdx?" + - "**.mkdn" + - "**.mdown" + - "**.markdown" + pull_request: + paths: + - ".github/workflows/check-markdown-task.ya?ml" + - ".markdown-link-check.json" + - "package.json" + - "package-lock.json" + - "Taskfile.ya?ml" + - "**/.markdownlint*" + - "**.mdx?" + - "**.mkdn" + - "**.mdown" + - "**.markdown" + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage caused by external changes. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + run-determination: + runs-on: ubuntu-latest + outputs: + result: ${{ steps.determination.outputs.result }} + steps: + - name: Determine if the rest of the workflow should run + id: determination + run: | + RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x" + # The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead. + if [[ + "${{ github.event_name }}" != "create" || + "${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX + ]]; then + # Run the other jobs. + RESULT="true" + else + # There is no need to run the other jobs. + RESULT="false" + fi + + echo "::set-output name=result::$RESULT" + + lint: + needs: run-determination + if: needs.run-determination.outputs.result == 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Initialize markdownlint-cli problem matcher + uses: xt0rted/markdownlint-problem-matcher@v3 + + - name: Install Task + uses: arduino/setup-task@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Lint + run: task markdown:lint + + links: + needs: run-determination + if: needs.run-determination.outputs.result == 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install Task + uses: arduino/setup-task@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Check links + run: task --silent markdown:check-links diff --git a/.github/workflows/check-taskfiles.yml b/.github/workflows/check-taskfiles.yml new file mode 100644 index 0000000..d369f69 --- /dev/null +++ b/.github/workflows/check-taskfiles.yml @@ -0,0 +1,100 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-taskfiles.md +name: Check Taskfiles + +env: + # See: https://github.com/actions/setup-node/#readme + NODE_VERSION: 16.x + +# See: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows +on: + create: + push: + paths: + - ".github/workflows/check-taskfiles.ya?ml" + - "package.json" + - "package-lock.json" + - "**/Taskfile.ya?ml" + pull_request: + paths: + - ".github/workflows/check-taskfiles.ya?ml" + - "package.json" + - "package-lock.json" + - "**/Taskfile.ya?ml" + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage resulting from changes to the JSON schema. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + run-determination: + runs-on: ubuntu-latest + permissions: {} + outputs: + result: ${{ steps.determination.outputs.result }} + steps: + - name: Determine if the rest of the workflow should run + id: determination + run: | + RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x" + # The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead. + if [[ + "${{ github.event_name }}" != "create" || + "${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX + ]]; then + # Run the other jobs. + RESULT="true" + else + # There is no need to run the other jobs. + RESULT="false" + fi + + echo "result=$RESULT" >> $GITHUB_OUTPUT + + validate: + name: Validate ${{ matrix.file }} + needs: run-determination + if: needs.run-determination.outputs.result == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + + strategy: + fail-fast: false + + matrix: + file: + - ./**/Taskfile.yml + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Download JSON schema for Taskfiles + id: download-schema + uses: carlosperate/download-file-action@v2 + with: + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/taskfile.json + file-url: https://taskfile.dev/schema.json + location: ${{ runner.temp }}/taskfile-schema + + - name: Install JSON schema validator + run: npm install + + - name: Validate ${{ matrix.file }} + run: | + # See: https://github.com/ajv-validator/ajv-cli#readme + npx \ + --package=ajv-cli \ + --package=ajv-formats \ + ajv validate \ + --all-errors \ + --strict=false \ + -c ajv-formats \ + -s "${{ steps.download-schema.outputs.file-path }}" \ + -d "${{ matrix.file }}" diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..78723a3 --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,140 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md +name: Sync Labels + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + pull_request: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + schedule: + # Run daily at 8 AM UTC to sync with changes to shared label configurations. + - cron: "0 8 * * *" + workflow_dispatch: + repository_dispatch: + +env: + CONFIGURATIONS_FOLDER: .github/label-configuration-files + CONFIGURATIONS_ARTIFACT_PREFIX: label-configuration-file- + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download JSON schema for labels configuration file + id: download-schema + uses: carlosperate/download-file-action@v2 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json + location: ${{ runner.temp }}/label-configuration-schema + + - name: Install JSON schema validator + run: | + sudo npm install \ + --global \ + ajv-cli \ + ajv-formats + + - name: Validate local labels configuration + run: | + # See: https://github.com/ajv-validator/ajv-cli#readme + ajv validate \ + --all-errors \ + -c ajv-formats \ + -s "${{ steps.download-schema.outputs.file-path }}" \ + -d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}" + + download: + needs: check + runs-on: ubuntu-latest + + strategy: + matrix: + filename: + # Filenames of the shared configurations to apply to the repository in addition to the local configuration. + # https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels + - universal.yml + - tooling.yml + + steps: + - name: Download + uses: carlosperate/download-file-action@v2 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} + + - name: Pass configuration files to next job via workflow artifact + uses: actions/upload-artifact@v4 + with: + path: | + *.yaml + *.yml + if-no-files-found: error + name: ${{ env.CONFIGURATIONS_ARTIFACT_PREFIX }}${{ matrix.filename }} + + sync: + needs: download + runs-on: ubuntu-latest + + steps: + - name: Set environment variables + run: | + # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV" + + - name: Determine whether to dry run + id: dry-run + if: > + github.event_name == 'pull_request' || + ( + ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' + ) && + github.ref != format('refs/heads/{0}', github.event.repository.default_branch) + ) + run: | + # Use of this flag in the github-label-sync command will cause it to only check the validity of the + # configuration. + echo "::set-output name=flag::--dry-run" + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download configuration file artifacts + uses: actions/download-artifact@v4 + with: + merge-multiple: true + pattern: ${{ env.CONFIGURATIONS_ARTIFACT_PREFIX }}* + path: ${{ env.CONFIGURATIONS_FOLDER }} + + - name: Remove unneeded artifacts + uses: geekyeggo/delete-artifact@v5 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT_PREFIX }}* + + - name: Merge label configuration files + run: | + # Merge all configuration files + shopt -s extglob + cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}" + + - name: Install github-label-sync + run: sudo npm install --global github-label-sync + + - name: Sync labels + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # See: https://github.com/Financial-Times/github-label-sync + github-label-sync \ + --labels "${{ env.MERGED_CONFIGURATION_PATH }}" \ + ${{ steps.dry-run.outputs.flag }} \ + ${{ github.repository }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..096746c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules/ \ No newline at end of file diff --git a/.licensed.yml b/.licensed.yml new file mode 100644 index 0000000..9289441 --- /dev/null +++ b/.licensed.yml @@ -0,0 +1,76 @@ +# See: https://github.com/github/licensed/blob/master/docs/configuration.md +sources: + go: true + +apps: + - source_path: ./ + +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-dependencies/GPL-2.0/.licensed.yml +allowed: + # The following are based on: https://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses + - gpl-1.0-or-later + - gpl-1.0+ # Deprecated ID for `gpl-1.0-or-later` + - gpl-2.0-only + - gpl-2.0 # Deprecated ID for `gpl-2.0-only` + - gpl-2.0-or-later + - gpl-2.0+ # Deprecated ID for `gpl-2.0-or-later` + - lgpl-2.0-or-later + - lgpl-2.0+ # Deprecated ID for `lgpl-2.0-or-later` + - lgpl-2.1-only + - lgpl-2.1 # Deprecated ID for `lgpl-2.1-only` + - lgpl-2.1-or-later + - lgpl-2.1+ # Deprecated ID for `lgpl-2.1-or-later` + - fsfap + - artistic-2.0 + - clartistic + - sleepycat + - bsl-1.0 + - bsd-3-clause + - cecill-2.0 + - bsd-3-clause-clear + # "Cryptix General License" - no SPDX ID (https://github.com/spdx/license-list-XML/issues/456) + - ecos-2.0 + - efl-2.0 + - eudatagrid + - mit + - bsd-2-clause # Subsumed by `bsd-2-clause-views` + - bsd-2-clause-netbsd # Deprecated ID for `bsd-2-clause` + - bsd-2-clause-views # This is the version linked from https://www.gnu.org/licenses/license-list.html#FreeBSD + - bsd-2-clause-freebsd # Deprecated ID for `bsd-2-clause-views` + - hpnd + - imatix + - imlib2 + - ijg + # "Informal license" - this is a general class of license + - intel + - isc + - mpl-2.0 + - ncsa + # "License of Netscape JavaScript" - no SPDX ID + - oldap-2.7 + # "License of Perl 5 and below" - possibly `Artistic-1.0-Perl` ? + - cc0-1.0 + - cc-pddc + - psf-2.0 + - ruby + - sgi-b-2.0 + - smlnj + - standardml-nj # Deprecated ID for `smlnj` + - unicode-dfs-2015 + - upl-1.0 + - unlicense + - vim + - w3c + - wtfpl + - lgpl-2.0-or-later with wxwindows-exception-3.1 + - wxwindows # Deprecated ID for `lgpl-2.0-or-later with wxwindows-exception-3.1` + - x11 + - zlib + - zpl-2.0 + - zpl-2.1 + # The following are based on individual license text + - eupl-1.0 + - eupl-1.1 + - eupl-1.2 + - liliq-r-1.1 + - liliq-rplus-1.1 diff --git a/.licenses/go-properties-orderedmap/go/github.com/arduino/go-paths-helper.dep.yml b/.licenses/go-properties-orderedmap/go/github.com/arduino/go-paths-helper.dep.yml new file mode 100644 index 0000000..651a930 --- /dev/null +++ b/.licenses/go-properties-orderedmap/go/github.com/arduino/go-paths-helper.dep.yml @@ -0,0 +1,350 @@ +--- +name: github.com/arduino/go-paths-helper +version: v1.0.1 +type: go +summary: +homepage: https://pkg.go.dev/github.com/arduino/go-paths-helper +license: gpl-2.0-or-later +licenses: +- sources: LICENSE + text: |2 + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your + freedom to share and change it. By contrast, the GNU General Public + License is intended to guarantee your freedom to share and change free + software--to make sure the software is free for all its users. This + General Public License applies to most of the Free Software + Foundation's software and to any other program whose authors commit to + using it. (Some other Free Software Foundation software is covered by + the GNU Lesser General Public License instead.) You can apply it to + your programs, too. + + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + this service if you wish), that you receive source code or can get it + if you want it, that you can change the software or use pieces of it + in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid + anyone to deny you these rights or to ask you to surrender the rights. + These restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether + gratis or for a fee, you must give the recipients all the rights that + you have. You must make sure that they, too, receive or can get the + source code. And you must show them these terms so they know their + rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software + patents. We wish to avoid the danger that redistributors of a free + program will individually obtain patent licenses, in effect making the + program proprietary. To prevent this, we have made it clear that any + patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains + a notice placed by the copyright holder saying it may be distributed + under the terms of this General Public License. The "Program", below, + refers to any such program or work, and a "work based on the Program" + means either the Program or any derivative work under copyright law: + that is to say, a work containing the Program or a portion of it, + either verbatim or with modifications and/or translated into another + language. (Hereinafter, translation is included without limitation in + the term "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of + running the Program is not restricted, and the output from the Program + is covered only if its contents constitute a work based on the + Program (independent of having been made by running the Program). + Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's + source code as you receive it, in any medium, provided that you + conspicuously and appropriately publish on each copy an appropriate + copyright notice and disclaimer of warranty; keep intact all the + notices that refer to this License and to the absence of any warranty; + and give any other recipients of the Program a copy of this License + along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion + of it, thus forming a work based on the Program, and copy and + distribute such modifications or work under the terms of Section 1 + above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, + and can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based + on the Program, the distribution of the whole must be on the terms of + this License, whose permissions for other licensees extend to the + entire whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of + a storage or distribution medium does not bring the other work under + the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source + code means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to + control compilation and installation of the executable. However, as a + special exception, the source code distributed need not include + anything that is normally distributed (in either source or binary + form) with the major components (compiler, kernel, and so on) of the + operating system on which the executable runs, unless that component + itself accompanies the executable. + + If distribution of executable or object code is made by offering + access to copy from a designated place, then offering equivalent + access to copy the source code from the same place counts as + distribution of the source code, even though third parties are not + compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt + otherwise to copy, modify, sublicense or distribute the Program is + void, and will automatically terminate your rights under this License. + However, parties who have received copies, or rights, from you under + this License will not have their licenses terminated so long as such + parties remain in full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and + all its terms and conditions for copying, distributing or modifying + the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted herein. + You are not responsible for enforcing compliance by third parties to + this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Program at all. For example, if a patent + license would not permit royalty-free redistribution of the Program by + all those who receive copies directly or indirectly through you, then + the only way you could satisfy both it and this License would be to + refrain entirely from distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is + implemented by public license practices. Many people have made + generous contributions to the wide range of software distributed + through that system in reliance on consistent application of that + system; it is up to the author/donor to decide if he or she is willing + to distribute software through any other system and a licensee cannot + impose that choice. + + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License + may add an explicit geographical distribution limitation excluding + those countries, so that distribution is permitted only in or among + countries not thus excluded. In such case, this License incorporates + the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions + of the General Public License from time to time. Such new versions will + be similar in spirit to the present version, but may differ in detail to + address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and conditions + either of that version or of any later version published by the Free + Software Foundation. If the Program does not specify a version number of + this License, you may choose any version ever published by the Free Software + Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the author + to ask for permission. For software which is copyrighted by the Free + Software Foundation, write to the Free Software Foundation; we sometimes + make exceptions for this. Our decision will be guided by the two goals + of preserving the free status of all derivatives of our free software and + of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY + FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN + OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES + PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED + OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS + TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE + PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, + REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR + REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, + INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING + OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED + TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY + YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER + PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest + to attach them to the start of each source file to most effectively + convey the exclusion of warranty; and each file should have at least + the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the appropriate + parts of the General Public License. Of course, the commands you use may + be called something other than `show w' and `show c'; they could even be + mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program into + proprietary programs. If your program is a subroutine library, you may + consider it more useful to permit linking proprietary applications with the + library. If this is what you want to do, use the GNU Lesser General + Public License instead of this License. +notices: [] diff --git a/.markdown-link-check.json b/.markdown-link-check.json new file mode 100644 index 0000000..c5e178d --- /dev/null +++ b/.markdown-link-check.json @@ -0,0 +1,13 @@ +{ + "httpHeaders": [ + { + "urls": ["https://docs.github.com/"], + "headers": { + "Accept-Encoding": "gzip, deflate, br" + } + } + ], + "retryOn429": true, + "retryCount": 3, + "aliveStatusCodes": [200, 206] +} diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..65b6ef7 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,62 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-markdown/.markdownlint.yml +# See: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md +# The code style defined in this file is the official standardized style to be used in all Arduino projects and should +# not be modified. +# Note: Rules disabled solely because they are redundant to Prettier are marked with a "Prettier" comment. + +default: false +MD001: false +MD002: false +MD003: false # Prettier +MD004: false # Prettier +MD005: false # Prettier +MD006: false # Prettier +MD007: false # Prettier +MD008: false # Prettier +MD009: + br_spaces: 0 + strict: true + list_item_empty_lines: false # Prettier +MD010: false # Prettier +MD011: true +MD012: false # Prettier +MD013: false +MD014: false +MD018: true +MD019: false # Prettier +MD020: true +MD021: false # Prettier +MD022: false # Prettier +MD023: false # Prettier +MD024: false +MD025: + level: 1 + front_matter_title: '^\s*"?title"?\s*[:=]' +MD026: false +MD027: false # Prettier +MD028: false +MD029: + style: one +MD030: + ul_single: 1 + ol_single: 1 + ul_multi: 1 + ol_multi: 1 +MD031: false # Prettier +MD032: false # Prettier +MD033: false +MD034: false +MD035: false # Prettier +MD036: false +MD037: true +MD038: true +MD039: true +MD040: false +MD041: false +MD042: true +MD043: false +MD044: false +MD045: true +MD046: + style: fenced +MD047: false # Prettier diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 0000000..0022748 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,4 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-markdown/.markdownlintignore +.licenses/ +__pycache__/ +node_modules/ diff --git a/README.md b/README.md index a51bc83..138c690 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ [![GoDoc](https://godoc.org/github.com/arduino/go-properties-orderedmap?status.svg)](https://godoc.org/github.com/arduino/go-properties-orderedmap) +[![Check License status](https://github.com/arduino/go-properties-orderedmap/actions/workflows/check-license.yml/badge.svg)](https://github.com/arduino/go-properties-orderedmap/actions/workflows/check-license.yml) +[![Check Markdown status](https://github.com/arduino/go-properties-orderedmap/actions/workflows/check-markdown-task.yml/badge.svg)](https://github.com/arduino/go-properties-orderedmap/actions/workflows/check-markdown-task.yml) +[![Check Taskfiles status](https://github.com/arduino/go-properties-orderedmap/actions/workflows/check-taskfiles.yml/badge.svg)](https://github.com/arduino/go-properties-orderedmap/actions/workflows/check-taskfiles.yml) -Package `properties` is a library for handling maps of hierarchical properties. +Package `properties` is a library for handling maps of hierarchical properties. This library is mainly used in the Arduino platform software to handle configurations made of key/value pairs stored in files with an INI like diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..4e545dc --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,112 @@ +# See: https://taskfile.dev/#/usage +version: "3" + +tasks: + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-dependencies-task/Taskfile.yml + general:cache-dep-licenses: + desc: Cache dependency license metadata + cmds: + - | + if ! which licensed &>/dev/null; then + if [[ {{OS}} == "windows" ]]; then + echo "Licensed does not have Windows support." + echo "Please use Linux/macOS or download the dependencies cache from the GitHub Actions workflow artifact." + else + echo "licensed not found or not in PATH. Please install: https://github.com/github/licensed#as-an-executable" + fi + exit 1 + fi + - licensed cache + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-dependencies-task/Taskfile.yml + general:check-dep-licenses: + desc: Check for unapproved dependency licenses + deps: + - task: general:cache-dep-licenses + cmds: + - licensed status + + docs:generate: + desc: Create all generated documentation content + # This is an "umbrella" task used to call any documentation generation processes the project has. + # It can be left empty if there are none. + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-markdown-task/Taskfile.yml + markdown:check-links: + desc: Check for broken links + deps: + - task: docs:generate + - task: npm:install-deps + cmds: + - | + if [[ "{{.OS}}" == "Windows_NT" ]]; then + # npx --call uses the native shell, which makes it too difficult to use npx for this application on Windows, + # so the Windows user is required to have markdown-link-check installed and in PATH. + if ! which markdown-link-check &>/dev/null; then + echo "markdown-link-check not found or not in PATH. Please install: https://github.com/tcort/markdown-link-check#readme" + exit 1 + fi + # Default behavior of the task on Windows is to exit the task when the first broken link causes a non-zero + # exit status, but it's better to check all links before exiting. + set +o errexit + STATUS=0 + # Using -regex instead of -name to avoid Task's behavior of globbing even when quoted on Windows + # The odd method for escaping . in the regex is required for windows compatibility because mvdan.cc/sh gives + # \ characters special treatment on Windows in an attempt to support them as path separators. + for file in $( + find . \ + -type d -name '.git' -prune -o \ + -type d -name '.licenses' -prune -o \ + -type d -name '__pycache__' -prune -o \ + -type d -name 'node_modules' -prune -o \ + -regex ".*[.]md" -print + ); do + markdown-link-check \ + --quiet \ + --config "./.markdown-link-check.json" \ + "$file" + STATUS=$(( $STATUS + $? )) + done + exit $STATUS + else + npx --package=markdown-link-check --call=' + STATUS=0 + for file in $( + find . \ + -type d -name '.git' -prune -o \ + -type d -name '.licenses' -prune -o \ + -type d -name '__pycache__' -prune -o \ + -type d -name 'node_modules' -prune -o \ + -regex ".*[.]md" -print + ); do + markdown-link-check \ + --quiet \ + --config "./.markdown-link-check.json" \ + "$file" + STATUS=$(( $STATUS + $? )) + done + exit $STATUS + ' + fi + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-markdown-task/Taskfile.yml + markdown:fix: + desc: Automatically correct linting violations in Markdown files where possible + deps: + - task: npm:install-deps + cmds: + - npx markdownlint-cli --fix "**/*.md" + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-markdown-task/Taskfile.yml + markdown:lint: + desc: Check for problems in Markdown files + deps: + - task: npm:install-deps + cmds: + - npx markdownlint-cli "**/*.md" + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/npm-task/Taskfile.yml + npm:install-deps: + desc: Install dependencies managed by npm + cmds: + - npm install diff --git a/debug.go b/debug.go index 10ae626..af2e61d 100644 --- a/debug.go +++ b/debug.go @@ -34,7 +34,7 @@ import ( "strings" ) -// DebugExpandPropsInString output the substitutions made by +// DebugExpandPropsInString outputs the substitutions made by // ExpandPropsInString for debugging purposes. func (m *Map) DebugExpandPropsInString(str string) string { return m.expandProps(str, true) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b690fd6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1719 @@ +{ + "name": "go-properties-orderedmap", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "devDependencies": { + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", + "markdown-link-check": "^3.10.2", + "markdownlint-cli": "^0.32.1" + } + }, + "node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ajv-cli/-/ajv-cli-5.0.0.tgz", + "integrity": "sha512-LY4m6dUv44HTyhV+u2z5uX4EhPYTM38Iv1jdgDJJJCyOOuqB8KtZEGjPZ2T+sh5ZIJrXUfgErYx/j3gLd3+PlQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0", + "fast-json-patch": "^2.0.0", + "glob": "^7.1.0", + "js-yaml": "^3.14.0", + "json-schema-migrate": "^2.0.0", + "json5": "^2.1.3", + "minimist": "^1.2.0" + }, + "bin": { + "ajv": "dist/index.js" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/ajv-cli/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/ajv-cli/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/ajv-cli/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ajv-cli/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/ajv-cli/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.1.tgz", + "integrity": "sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-patch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz", + "integrity": "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^2.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fast-json-patch/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/html-link-extractor": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-link-extractor/-/html-link-extractor-1.0.5.tgz", + "integrity": "sha512-ADd49pudM157uWHwHQPUSX4ssMsvR/yHIswOR5CUfBdK9g9ZYGMhVSE6KZVHJ6kCkR0gH4htsfzU6zECDNVwyw==", + "dev": true, + "dependencies": { + "cheerio": "^1.0.0-rc.10" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.0.tgz", + "integrity": "sha512-TxYQaeNW/N8ymDvwAxPyRbhMBtnEwuvaTYpOQkFx1nSeusgezHniEc/l35Vo4iCq/mMiTJbpD7oYxN98hFlfmw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-relative-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-relative-url/-/is-relative-url-3.0.0.tgz", + "integrity": "sha512-U1iSYRlY2GIMGuZx7gezlB5dp1Kheaym7zKzO1PV06mOihiWTXejLwm4poEJysPyXF+HtK/BEd0DVlcCh30pEA==", + "dev": true, + "dependencies": { + "is-absolute-url": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "dev": true, + "dependencies": { + "punycode": "2.x.x" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-migrate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", + "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==", + "dev": true + }, + "node_modules/link-check": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/link-check/-/link-check-5.1.0.tgz", + "integrity": "sha512-FHq/9tVnIE/3EVEPb91GcbD+K/Pv5K5DYqb7vXi3TTKIViMYPOWxYFVVENZ0rq63zfaGXGvLgPT9U6jOFc5JBw==", + "dev": true, + "dependencies": { + "is-relative-url": "^3.0.0", + "isemail": "^3.2.0", + "ms": "^2.1.3", + "needle": "^3.0.0" + } + }, + "node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/markdown-link-check": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/markdown-link-check/-/markdown-link-check-3.10.2.tgz", + "integrity": "sha512-5yQEVtjLxAjxWy82+iTgxrekr1tuD4sKGgwXiyLrCep8RERFH3yCdpZdeA12em2S2SEwXGxp6qHI73jVhuScKA==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.1.2", + "commander": "^6.2.0", + "link-check": "^5.1.0", + "lodash": "^4.17.21", + "markdown-link-extractor": "^3.0.2", + "needle": "^3.1.0", + "progress": "^2.0.3" + }, + "bin": { + "markdown-link-check": "markdown-link-check" + } + }, + "node_modules/markdown-link-extractor": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/markdown-link-extractor/-/markdown-link-extractor-3.0.2.tgz", + "integrity": "sha512-vmTTAWSa49Lqojr6L4ALGLV0TLz4+1movDb6saDS6c6FLGGbPFSkhjevpXsQTXEYY9lCWYcVQqb7l41WEZsM7Q==", + "dev": true, + "dependencies": { + "html-link-extractor": "^1.0.3", + "marked": "^4.0.15" + } + }, + "node_modules/markdownlint": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.26.2.tgz", + "integrity": "sha512-2Am42YX2Ex5SQhRq35HxYWDfz1NLEOZWWN25nqd2h3AHRKsGRE+Qg1gt1++exW792eXTrR4jCNHfShfWk9Nz8w==", + "dev": true, + "dependencies": { + "markdown-it": "13.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/markdownlint-cli": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.32.1.tgz", + "integrity": "sha512-hVLQ+72b5esQd7I+IqzBEB4x/4C+wJaxS2M6nqaGoDwrtNY6gydGf5CIUJtQcXtqsM615++a8TZPsvEtH6H4gw==", + "dev": true, + "dependencies": { + "commander": "~9.4.0", + "get-stdin": "~9.0.0", + "glob": "~8.0.3", + "ignore": "~5.2.0", + "js-yaml": "^4.1.0", + "jsonc-parser": "~3.1.0", + "markdownlint": "~0.26.1", + "markdownlint-rule-helpers": "~0.17.1", + "minimatch": "~5.1.0", + "run-con": "~1.2.11" + }, + "bin": { + "markdownlint": "markdownlint.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/markdownlint-cli/node_modules/commander": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", + "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/markdownlint-rule-helpers": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.17.2.tgz", + "integrity": "sha512-XaeoW2NYSlWxMCZM2B3H7YTG6nlaLfkEZWMBhr4hSPlq9MuY2sy83+Xr89jXOqZMZYjvi5nBCGoFh7hHoPKZmA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/marked": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.18.tgz", + "integrity": "sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/needle": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.1.0.tgz", + "integrity": "sha512-gCE9weDhjVGCRqS8dwDR/D3GTAeyXLXuqp7I8EzH6DllZGXSUyxuqqLh+YX9rMAWaaTFyVAg6rHGL25dqvczKw==", + "dev": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse5": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", + "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "dev": true, + "dependencies": { + "entities": "^4.3.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/run-con": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.2.11.tgz", + "integrity": "sha512-NEMGsUT+cglWkzEr4IFK21P4Jca45HqiAbIIZIBdX5+UZTB24Mb/21iNGgz9xZa8tL6vbW7CXmq7MFN42+VjNQ==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~3.0.0", + "minimist": "^1.2.6", + "strip-json-comments": "~3.1.1" + }, + "bin": { + "run-con": "cli.js" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ajv-cli/-/ajv-cli-5.0.0.tgz", + "integrity": "sha512-LY4m6dUv44HTyhV+u2z5uX4EhPYTM38Iv1jdgDJJJCyOOuqB8KtZEGjPZ2T+sh5ZIJrXUfgErYx/j3gLd3+PlQ==", + "dev": true, + "requires": { + "ajv": "^8.0.0", + "fast-json-patch": "^2.0.0", + "glob": "^7.1.0", + "js-yaml": "^3.14.0", + "json-schema-migrate": "^2.0.0", + "json5": "^2.1.3", + "minimist": "^1.2.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dev": true, + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + } + }, + "entities": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.1.tgz", + "integrity": "sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-patch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz", + "integrity": "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1" + }, + "dependencies": { + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "dev": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "dev": true + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "html-link-extractor": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-link-extractor/-/html-link-extractor-1.0.5.tgz", + "integrity": "sha512-ADd49pudM157uWHwHQPUSX4ssMsvR/yHIswOR5CUfBdK9g9ZYGMhVSE6KZVHJ6kCkR0gH4htsfzU6zECDNVwyw==", + "dev": true, + "requires": { + "cheerio": "^1.0.0-rc.10" + } + }, + "htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.0.tgz", + "integrity": "sha512-TxYQaeNW/N8ymDvwAxPyRbhMBtnEwuvaTYpOQkFx1nSeusgezHniEc/l35Vo4iCq/mMiTJbpD7oYxN98hFlfmw==", + "dev": true + }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true + }, + "is-relative-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-relative-url/-/is-relative-url-3.0.0.tgz", + "integrity": "sha512-U1iSYRlY2GIMGuZx7gezlB5dp1Kheaym7zKzO1PV06mOihiWTXejLwm4poEJysPyXF+HtK/BEd0DVlcCh30pEA==", + "dev": true, + "requires": { + "is-absolute-url": "^3.0.0" + } + }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "dev": true, + "requires": { + "punycode": "2.x.x" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-migrate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", + "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==", + "dev": true + }, + "link-check": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/link-check/-/link-check-5.1.0.tgz", + "integrity": "sha512-FHq/9tVnIE/3EVEPb91GcbD+K/Pv5K5DYqb7vXi3TTKIViMYPOWxYFVVENZ0rq63zfaGXGvLgPT9U6jOFc5JBw==", + "dev": true, + "requires": { + "is-relative-url": "^3.0.0", + "isemail": "^3.2.0", + "ms": "^2.1.3", + "needle": "^3.0.0" + } + }, + "linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true + } + } + }, + "markdown-link-check": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/markdown-link-check/-/markdown-link-check-3.10.2.tgz", + "integrity": "sha512-5yQEVtjLxAjxWy82+iTgxrekr1tuD4sKGgwXiyLrCep8RERFH3yCdpZdeA12em2S2SEwXGxp6qHI73jVhuScKA==", + "dev": true, + "requires": { + "async": "^3.2.3", + "chalk": "^4.1.2", + "commander": "^6.2.0", + "link-check": "^5.1.0", + "lodash": "^4.17.21", + "markdown-link-extractor": "^3.0.2", + "needle": "^3.1.0", + "progress": "^2.0.3" + } + }, + "markdown-link-extractor": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/markdown-link-extractor/-/markdown-link-extractor-3.0.2.tgz", + "integrity": "sha512-vmTTAWSa49Lqojr6L4ALGLV0TLz4+1movDb6saDS6c6FLGGbPFSkhjevpXsQTXEYY9lCWYcVQqb7l41WEZsM7Q==", + "dev": true, + "requires": { + "html-link-extractor": "^1.0.3", + "marked": "^4.0.15" + } + }, + "markdownlint": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.26.2.tgz", + "integrity": "sha512-2Am42YX2Ex5SQhRq35HxYWDfz1NLEOZWWN25nqd2h3AHRKsGRE+Qg1gt1++exW792eXTrR4jCNHfShfWk9Nz8w==", + "dev": true, + "requires": { + "markdown-it": "13.0.1" + } + }, + "markdownlint-cli": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.32.1.tgz", + "integrity": "sha512-hVLQ+72b5esQd7I+IqzBEB4x/4C+wJaxS2M6nqaGoDwrtNY6gydGf5CIUJtQcXtqsM615++a8TZPsvEtH6H4gw==", + "dev": true, + "requires": { + "commander": "~9.4.0", + "get-stdin": "~9.0.0", + "glob": "~8.0.3", + "ignore": "~5.2.0", + "js-yaml": "^4.1.0", + "jsonc-parser": "~3.1.0", + "markdownlint": "~0.26.1", + "markdownlint-rule-helpers": "~0.17.1", + "minimatch": "~5.1.0", + "run-con": "~1.2.11" + }, + "dependencies": { + "commander": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", + "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", + "dev": true + } + } + }, + "markdownlint-rule-helpers": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.17.2.tgz", + "integrity": "sha512-XaeoW2NYSlWxMCZM2B3H7YTG6nlaLfkEZWMBhr4hSPlq9MuY2sy83+Xr89jXOqZMZYjvi5nBCGoFh7hHoPKZmA==", + "dev": true + }, + "marked": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.18.tgz", + "integrity": "sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw==", + "dev": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "needle": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.1.0.tgz", + "integrity": "sha512-gCE9weDhjVGCRqS8dwDR/D3GTAeyXLXuqp7I8EzH6DllZGXSUyxuqqLh+YX9rMAWaaTFyVAg6rHGL25dqvczKw==", + "dev": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + } + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "parse5": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", + "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "dev": true, + "requires": { + "entities": "^4.3.0" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "run-con": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.2.11.tgz", + "integrity": "sha512-NEMGsUT+cglWkzEr4IFK21P4Jca45HqiAbIIZIBdX5+UZTB24Mb/21iNGgz9xZa8tL6vbW7CXmq7MFN42+VjNQ==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~3.0.0", + "minimist": "^1.2.6", + "strip-json-comments": "~3.1.1" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..303a36b --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "devDependencies": { + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", + "markdown-link-check": "^3.10.2", + "markdownlint-cli": "^0.32.1" + } +} diff --git a/properties.go b/properties.go index 4527c88..216fdee 100644 --- a/properties.go +++ b/properties.go @@ -33,33 +33,33 @@ This library is mainly used in the Arduino platform software to handle configurations made of key/value pairs stored in files with an INI like syntax, for example: - ... - uno.name=Arduino/Genuino Uno - uno.upload.tool=avrdude - uno.upload.protocol=arduino - uno.upload.maximum_size=32256 - uno.upload.maximum_data_size=2048 - uno.upload.speed=115200 - uno.build.mcu=atmega328p - uno.build.f_cpu=16000000L - uno.build.board=AVR_UNO - uno.build.core=arduino - uno.build.variant=standard - diecimila.name=Arduino Duemilanove or Diecimila - diecimila.upload.tool=avrdude - diecimila.upload.protocol=arduino - diecimila.build.f_cpu=16000000L - diecimila.build.board=AVR_DUEMILANOVE - diecimila.build.core=arduino - diecimila.build.variant=standard - ... - -This library has methods to parse this kind of files into a Map object. + ... + uno.name=Arduino/Genuino Uno + uno.upload.tool=avrdude + uno.upload.protocol=arduino + uno.upload.maximum_size=32256 + uno.upload.maximum_data_size=2048 + uno.upload.speed=115200 + uno.build.mcu=atmega328p + uno.build.f_cpu=16000000L + uno.build.board=AVR_UNO + uno.build.core=arduino + uno.build.variant=standard + diecimila.name=Arduino Duemilanove or Diecimila + diecimila.upload.tool=avrdude + diecimila.upload.protocol=arduino + diecimila.build.f_cpu=16000000L + diecimila.build.board=AVR_DUEMILANOVE + diecimila.build.core=arduino + diecimila.build.variant=standard + ... + +This library has methods to parse this kind of file into a Map object. The Map internally keeps the insertion order so it can be retrieved later when cycling through the key sets. -The Map object has many helper methods to accomplish some common operation +The Map object has many helper methods to accomplish some common operations on this kind of data like cloning, merging, comparing and also extracting a submap or generating a map-of-submaps from the first "level" of the hierarchy. @@ -76,7 +76,10 @@ import ( "reflect" "regexp" "runtime" + "sort" + "strconv" "strings" + "unicode/utf8" "github.com/arduino/go-paths-helper" ) @@ -106,7 +109,7 @@ func GetOSSuffix() string { return osSuffix } -// SetOSSuffix force the OS suffix to the given value +// SetOSSuffix forces the OS suffix to the given value func SetOSSuffix(suffix string) { osSuffix = suffix } @@ -129,9 +132,23 @@ func NewFromHashmap(orig map[string]string) *Map { return res } +func toUtf8(iso8859_1_buf []byte) string { + buf := make([]rune, len(iso8859_1_buf)) + for i, b := range iso8859_1_buf { + buf[i] = rune(b) + } + return string(buf) +} + // LoadFromBytes reads properties data and makes a Map out of it. func LoadFromBytes(bytes []byte) (*Map, error) { - text := string(bytes) + var text string + if utf8.Valid(bytes) { + text = string(bytes) + } else { + // Assume ISO8859-1 encoding and convert to UTF-8 + text = toUtf8(bytes) + } text = strings.Replace(text, "\r\n", "\n", -1) text = strings.Replace(text, "\r", "\n", -1) @@ -139,7 +156,7 @@ func LoadFromBytes(bytes []byte) (*Map, error) { for lineNum, line := range strings.Split(text, "\n") { if err := properties.parseLine(line); err != nil { - return nil, fmt.Errorf("Error parsing data at line %d: %s", lineNum, err) + return nil, fmt.Errorf("error parsing data at line %d: %s", lineNum, err) } } @@ -150,12 +167,12 @@ func LoadFromBytes(bytes []byte) (*Map, error) { func Load(filepath string) (*Map, error) { bytes, err := ioutil.ReadFile(filepath) if err != nil { - return nil, fmt.Errorf("Error reading file: %s", err) + return nil, fmt.Errorf("error reading file: %s", err) } res, err := LoadFromBytes(bytes) if err != nil { - return nil, fmt.Errorf("Error reading file: %s", err) + return nil, fmt.Errorf("error reading file: %s", err) } return res, nil } @@ -172,7 +189,7 @@ func LoadFromSlice(lines []string) (*Map, error) { for lineNum, line := range lines { if err := properties.parseLine(line); err != nil { - return nil, fmt.Errorf("Error reading from slice (index:%d): %s", lineNum, err) + return nil, fmt.Errorf("error reading from slice (index:%d): %s", lineNum, err) } } @@ -189,7 +206,7 @@ func (m *Map) parseLine(line string) error { lineParts := strings.SplitN(line, "=", 2) if len(lineParts) != 2 { - return fmt.Errorf("Invalid line format, should be 'key=value'") + return fmt.Errorf("invalid line format, should be 'key=value'") } key := strings.TrimSpace(lineParts[0]) value := strings.TrimSpace(lineParts[1]) @@ -201,13 +218,13 @@ func (m *Map) parseLine(line string) error { } // SafeLoadFromPath is like LoadFromPath, except that it returns an empty Map if -// the specified file doesn't exists +// the specified file doesn't exist func SafeLoadFromPath(path *paths.Path) (*Map, error) { return SafeLoad(path.String()) } // SafeLoad is like Load, except that it returns an empty Map if the specified -// file doesn't exists +// file doesn't exist func SafeLoad(filepath string) (*Map, error) { _, err := os.Stat(filepath) if os.IsNotExist(err) { @@ -221,12 +238,12 @@ func SafeLoad(filepath string) (*Map, error) { return properties, nil } -// Get retrieve the value corresponding to key +// Get retrieves the value corresponding to key func (m *Map) Get(key string) string { return m.kv[key] } -// GetOk retrieve the value corresponding to key and return a true/false indicator +// GetOk retrieves the value corresponding to key and returns a true/false indicator // to check if the key is present in the map (true if the key is present) func (m *Map) GetOk(key string) (string, bool) { v, ok := m.kv[key] @@ -258,7 +275,7 @@ func (m *Map) Set(key, value string) { m.o = append(m.o, key) } -// Size return the number of elements in the map +// Size returns the number of elements in the map func (m *Map) Size() int { return len(m.kv) } @@ -277,39 +294,39 @@ func (m *Map) Remove(key string) { // FirstLevelOf generates a map-of-Maps using the first level of the hierarchy // of the current Map. For example the following Map: // -// properties.Map{ -// "uno.name": "Arduino/Genuino Uno", -// "uno.upload.tool": "avrdude", -// "uno.upload.protocol": "arduino", -// "uno.upload.maximum_size": "32256", -// "diecimila.name": "Arduino Duemilanove or Diecimila", -// "diecimila.upload.tool": "avrdude", -// "diecimila.upload.protocol": "arduino", -// "diecimila.bootloader.tool": "avrdude", -// "diecimila.bootloader.low_fuses": "0xFF", -// } +// properties.Map{ +// "uno.name": "Arduino/Genuino Uno", +// "uno.upload.tool": "avrdude", +// "uno.upload.protocol": "arduino", +// "uno.upload.maximum_size": "32256", +// "diecimila.name": "Arduino Duemilanove or Diecimila", +// "diecimila.upload.tool": "avrdude", +// "diecimila.upload.protocol": "arduino", +// "diecimila.bootloader.tool": "avrdude", +// "diecimila.bootloader.low_fuses": "0xFF", +// } // // is transformed into the following map-of-Maps: // -// map[string]Map{ -// "uno" : properties.Map{ -// "name": "Arduino/Genuino Uno", -// "upload.tool": "avrdude", -// "upload.protocol": "arduino", -// "upload.maximum_size": "32256", -// }, -// "diecimila" : properties.Map{ -// "name=Arduino Duemilanove or Diecimila -// "upload.tool": "avrdude", -// "upload.protocol": "arduino", -// "bootloader.tool": "avrdude", -// "bootloader.low_fuses": "0xFF", -// } -// } +// map[string]Map{ +// "uno" : properties.Map{ +// "name": "Arduino/Genuino Uno", +// "upload.tool": "avrdude", +// "upload.protocol": "arduino", +// "upload.maximum_size": "32256", +// }, +// "diecimila" : properties.Map{ +// "name": "Arduino Duemilanove or Diecimila", +// "upload.tool": "avrdude", +// "upload.protocol": "arduino", +// "bootloader.tool": "avrdude", +// "bootloader.low_fuses": "0xFF", +// } +// } func (m *Map) FirstLevelOf() map[string]*Map { newMap := make(map[string]*Map) for _, key := range m.o { - if strings.Index(key, ".") == -1 { + if !strings.Contains(key, ".") { continue } keyParts := strings.SplitN(key, ".", 2) @@ -325,24 +342,24 @@ func (m *Map) FirstLevelOf() map[string]*Map { // FirstLevelKeys returns the keys in the first level of the hierarchy // of the current Map. For example the following Map: // -// properties.Map{ -// "uno.name": "Arduino/Genuino Uno", -// "uno.upload.tool": "avrdude", -// "uno.upload.protocol": "arduino", -// "uno.upload.maximum_size": "32256", -// "diecimila.name": "Arduino Duemilanove or Diecimila", -// "diecimila.upload.tool": "avrdude", -// "diecimila.upload.protocol": "arduino", -// "diecimila.bootloader.tool": "avrdude", -// "diecimila.bootloader.low_fuses": "0xFF", -// } +// properties.Map{ +// "uno.name": "Arduino/Genuino Uno", +// "uno.upload.tool": "avrdude", +// "uno.upload.protocol": "arduino", +// "uno.upload.maximum_size": "32256", +// "diecimila.name": "Arduino Duemilanove or Diecimila", +// "diecimila.upload.tool": "avrdude", +// "diecimila.upload.protocol": "arduino", +// "diecimila.bootloader.tool": "avrdude", +// "diecimila.bootloader.low_fuses": "0xFF", +// } // // will produce the following result: // -// []string{ -// "uno", -// "diecimila", -// } +// []string{ +// "uno", +// "diecimila", +// } // // the order of the original map is preserved func (m *Map) FirstLevelKeys() []string { @@ -363,26 +380,26 @@ func (m *Map) FirstLevelKeys() []string { // of the keys hierarchy as selector. // For example the following Map: // -// properties.Map{ -// "uno.name": "Arduino/Genuino Uno", -// "uno.upload.tool": "avrdude", -// "uno.upload.protocol": "arduino", -// "uno.upload.maximum_size": "32256", -// "diecimila.name": "Arduino Duemilanove or Diecimila", -// "diecimila.upload.tool": "avrdude", -// "diecimila.upload.protocol": "arduino", -// "diecimila.bootloader.tool": "avrdude", -// "diecimila.bootloader.low_fuses": "0xFF", -// } +// properties.Map{ +// "uno.name": "Arduino/Genuino Uno", +// "uno.upload.tool": "avrdude", +// "uno.upload.protocol": "arduino", +// "uno.upload.maximum_size": "32256", +// "diecimila.name": "Arduino Duemilanove or Diecimila", +// "diecimila.upload.tool": "avrdude", +// "diecimila.upload.protocol": "arduino", +// "diecimila.bootloader.tool": "avrdude", +// "diecimila.bootloader.low_fuses": "0xFF", +// } // -// after calling SubTree("uno") will be transformed in: +// after calling SubTree("uno") will be transformed into: // -// properties.Map{ -// "name": "Arduino/Genuino Uno", -// "upload.tool": "avrdude", -// "upload.protocol": "arduino", -// "upload.maximum_size": "32256", -// }, +// properties.Map{ +// "name": "Arduino/Genuino Uno", +// "upload.tool": "avrdude", +// "upload.protocol": "arduino", +// "upload.maximum_size": "32256", +// }, func (m *Map) SubTree(rootKey string) *Map { rootKey += "." newMap := NewMap() @@ -396,13 +413,13 @@ func (m *Map) SubTree(rootKey string) *Map { return newMap } -// ExpandPropsInString use the Map to replace values into a format string. +// ExpandPropsInString uses the Map to replace values into a format string. // The format string should contains markers between braces, for example: // -// "The selected upload protocol is {upload.protocol}." +// "The selected upload protocol is {upload.protocol}." // // Each marker is replaced by the corresponding value of the Map. -// The values in the Map may contains other markers, they are evaluated +// The values in the Map may contain other markers, they are evaluated // recursively up to 10 times. func (m *Map) ExpandPropsInString(str string) string { return m.expandProps(str, false) @@ -470,12 +487,22 @@ func (m *Map) Values() []string { return values } -// AsMap return the underlying map[string]string. This is useful if you need to +// AsMap returns the underlying map[string]string. This is useful if you need to // for ... range but without the requirement of the ordered elements. func (m *Map) AsMap() map[string]string { return m.kv } +// AsSlice returns the underlying map[string]string as a slice of +// strings with the pattern `{key}={value}`, maintaining the insertion order of the keys. +func (m *Map) AsSlice() []string { + properties := make([]string, len(m.o)) + for i, key := range m.o { + properties[i] = strings.Join([]string{key, m.kv[key]}, "=") + } + return properties +} + // Clone makes a copy of the Map func (m *Map) Clone() *Map { clone := NewMap() @@ -495,7 +522,7 @@ func (m *Map) EqualsWithOrder(other *Map) bool { return reflect.DeepEqual(m.o, other.o) && reflect.DeepEqual(m.kv, other.kv) } -// MergeMapsOfProperties merge the map-of-Maps (obtained from the method FirstLevelOf()) into the +// MergeMapsOfProperties merges the map-of-Maps (obtained from the method FirstLevelOf()) into the // target map-of-Maps. func MergeMapsOfProperties(target map[string]*Map, sources ...map[string]*Map) map[string]*Map { for _, source := range sources { @@ -509,7 +536,7 @@ func MergeMapsOfProperties(target map[string]*Map, sources ...map[string]*Map) m // DeleteUnexpandedPropsFromString removes all the brace markers "{xxx}" that are not expanded // into a value using the Map.ExpandPropsInString() method. func DeleteUnexpandedPropsFromString(str string) string { - rxp := regexp.MustCompile("\\{.+?\\}") + rxp := regexp.MustCompile(`\{.+?\}`) return rxp.ReplaceAllString(str, "") } @@ -517,50 +544,50 @@ func DeleteUnexpandedPropsFromString(str string) string { // `root.N.xxx...` as separate subsets. // For example the following Map: // -// properties.Map{ -// "uno.upload_port.vid": "0x1000", -// "uno.upload_port.pid": "0x2000", -// "due.upload_port.0.vid": "0x1000", -// "due.upload_port.0.pid": "0x2000", -// "due.upload_port.1.vid": "0x1001", -// "due.upload_port.1.pid": "0x2001", -// "tre.upload_port.1.vid": "0x1001", -// "tre.upload_port.1.pid": "0x2001", -// "tre.upload_port.2.vid": "0x1002", -// "tre.upload_port.2.pid": "0x2002", -// } +// properties.Map{ +// "uno.upload_port.vid": "0x1000", +// "uno.upload_port.pid": "0x2000", +// "due.upload_port.0.vid": "0x1000", +// "due.upload_port.0.pid": "0x2000", +// "due.upload_port.1.vid": "0x1001", +// "due.upload_port.1.pid": "0x2001", +// "tre.upload_port.1.vid": "0x1001", +// "tre.upload_port.1.pid": "0x2001", +// "tre.upload_port.2.vid": "0x1002", +// "tre.upload_port.2.pid": "0x2002", +// } // // calling ExtractSubIndexSets("uno.upload_port") returns the array: // -// [ properties.Map{ -// "vid": "0x1000", -// "pid": "0x2000", -// }, -// ] +// [ properties.Map{ +// "vid": "0x1000", +// "pid": "0x2000", +// }, +// ] // // calling ExtractSubIndexSets("due.upload_port") returns the array: // -// [ properties.Map{ -// "vid": "0x1000", -// "pid": "0x2000", -// }, -// properties.Map{ -// "vid": "0x1001", -// "pid": "0x2001", -// }, -// ] +// [ properties.Map{ +// "vid": "0x1000", +// "pid": "0x2000", +// }, +// properties.Map{ +// "vid": "0x1001", +// "pid": "0x2001", +// }, +// ] // // the sub-index may start with .1 too, so calling ExtractSubIndexSets("tre.upload_port") returns: // -// [ properties.Map{ -// "vid": "0x1001", -// "pid": "0x2001", -// }, -// properties.Map{ -// "vid": "0x1002", -// "pid": "0x2002", -// }, -// ] +// [ properties.Map{ +// "vid": "0x1001", +// "pid": "0x2001", +// }, +// properties.Map{ +// "vid": "0x1002", +// "pid": "0x2002", +// }, +// ] // // Numeric subindex cannot be mixed with non-numeric, in that case only the numeric sub // index sets will be returned. @@ -597,45 +624,70 @@ func (m *Map) ExtractSubIndexSets(root string) []*Map { // ExtractSubIndexLists extracts a list of arguments from a root `root.N=...`. // For example the following Map: // -// properties.Map{ -// "uno.discovery.required": "item", -// "due.discovery.required.0": "item1", -// "due.discovery.required.1": "item2", -// "due.discovery.required.2": "item3", -// "tre.discovery.required.1": "itemA", -// "tre.discovery.required.2": "itemB", -// "tre.discovery.required.3": "itemC", -// } +// properties.Map{ +// "uno.discovery.required": "item", +// "due.discovery.required.0": "item1", +// "due.discovery.required.1": "item2", +// "due.discovery.required.2": "item3", +// "tre.discovery.required.1": "itemA", +// "tre.discovery.required.2": "itemB", +// "tre.discovery.required.3": "itemC", +// "quattro.discovery.required.1": "itemA", +// "quattro.discovery.required.4": "itemB", +// "quattro.discovery.required.5": "itemC", +// } // // calling ExtractSubIndexLists("uno.discovery.required") returns the array: // -// [ "item" ] +// [ "item" ] // // calling ExtractSubIndexLists("due.discovery.required") returns the array: // -// [ "item1", "item2", "item3" ] +// [ "item1", "item2", "item3" ] // // the sub-index may start with .1 too, so calling ExtractSubIndexLists("tre.discovery.required") returns: // -// [ "itemA", "itemB", "itemC" ] +// [ "itemA", "itemB", "itemC" ] +// +// also the list may contains holes, so calling ExtractSubIndexLists("quattro.discovery.required") returns: +// +// [ "itemA", "itemB", "itemC" ] // // Numeric subindex cannot be mixed with non-numeric, in that case only the numeric sub // index sets will be returned. func (m *Map) ExtractSubIndexLists(root string) []string { - // First check the properties with numeric sub index "root.N.xxx" + isNotDigit := func(in string) bool { + for _, r := range in { + if r < '0' || r > '9' { + return true + } + } + return false + } + + // Extract numeric keys + subProps := m.SubTree(root) + indexes := []int{} + for _, key := range subProps.o { + if isNotDigit(key) { + continue + } + if idx, err := strconv.Atoi(key); err == nil { + indexes = append(indexes, idx) + } + } + sort.Ints(indexes) + res := []string{} - portIDPropsSet := m.SubTree(root) - idx := 0 haveIndexedProperties := false - for { - k := fmt.Sprintf("%d", idx) - idx++ - if v, ok := portIDPropsSet.GetOk(k); ok { + for i, idx := range indexes { + if i > 0 && idx == indexes[i-1] { + // de-duplicate cases like "05" and "5" + continue + } + if v, ok := subProps.GetOk(strconv.Itoa(idx)); ok { haveIndexedProperties = true res = append(res, v) - } else if idx > 1 { - // Always check sub-id 0 and 1 (https://github.com/arduino/arduino-cli/issues/456) - break } } diff --git a/properties_test.go b/properties_test.go index 46f2c76..16a4c01 100644 --- a/properties_test.go +++ b/properties_test.go @@ -357,30 +357,46 @@ func TestExtractSubIndexLists(t *testing.T) { "quattro.discovery.required.1": "itemB", "quattro.discovery.required.2": "itemC", "cinque.discovery.something": "itemX", + "sei.discovery.something.1": "itemA", + "sei.discovery.something.2": "itemB", + "sei.discovery.something.5": "itemC", + "sei.discovery.something.12": "itemD", + "sette.discovery.something.01": "itemA", + "sette.discovery.something.2": "itemB", + "sette.discovery.something.05": "itemC", + "sette.discovery.something.5": "itemD", } m := NewFromHashmap(data) - s1 := m.ExtractSubIndexLists("uno.discovery.required") - require.Len(t, s1, 1) - require.Equal(t, s1[0], "item") + require.Equal(t, []string{"item"}, m.ExtractSubIndexLists("uno.discovery.required")) + require.Equal(t, []string{"item1", "item2", "item3"}, m.ExtractSubIndexLists("due.discovery.required")) + require.Equal(t, []string{"itemA", "itemB", "itemC"}, m.ExtractSubIndexLists("tre.discovery.required")) + require.Equal(t, []string{"itemB", "itemC"}, m.ExtractSubIndexLists("quattro.discovery.required")) + require.Equal(t, []string{}, m.ExtractSubIndexLists("cinque.discovery.required")) + require.Equal(t, []string{"itemA", "itemB", "itemC", "itemD"}, m.ExtractSubIndexLists("sei.discovery.something")) + require.Equal(t, []string{"itemB", "itemD"}, m.ExtractSubIndexLists("sette.discovery.something")) +} + +func TestLoadingNonUTF8Properties(t *testing.T) { + m, err := LoadFromPath(paths.New("testdata/non-utf8.properties")) + require.NoError(t, err) + require.Equal(t, "Aáa", m.Get("maintainer")) +} - s2 := m.ExtractSubIndexLists("due.discovery.required") - require.Len(t, s2, 3) - require.Equal(t, s2[0], "item1") - require.Equal(t, s2[1], "item2") - require.Equal(t, s2[2], "item3") +func TestAsSlice(t *testing.T) { + emptyProperties := NewMap() + require.Len(t, emptyProperties.AsSlice(), 0) - s3 := m.ExtractSubIndexLists("tre.discovery.required") - require.Len(t, s3, 3) - require.Equal(t, s3[0], "itemA") - require.Equal(t, s3[1], "itemB") - require.Equal(t, s3[2], "itemC") + properties := NewMap() + properties.Set("key1", "value1") + properties.Set("key2", "value2") + properties.Set("key3", "value3=somethingElse") - s4 := m.ExtractSubIndexLists("quattro.discovery.required") - require.Len(t, s4, 2) - require.Equal(t, s4[0], "itemB") - require.Equal(t, s4[1], "itemC") + require.Len(t, properties.AsSlice(), properties.Size()) - s5 := m.ExtractSubIndexLists("cinque.discovery.required") - require.Len(t, s5, 0) + require.Equal(t, []string{ + "key1=value1", + "key2=value2", + "key3=value3=somethingElse"}, + properties.AsSlice()) } diff --git a/strings.go b/strings.go index f64b15e..4eae333 100644 --- a/strings.go +++ b/strings.go @@ -39,9 +39,29 @@ import ( // to use spaces in a single element of the split using quote characters. // // For example the call: -// SplitQuotedString(`This 'is an' "Hello World!" example`, `'"`, false) +// +// SplitQuotedString(`This 'is an' "Hello World!" example`, `'"`, false) +// // returns the following array: -// []string{"This", "is an", "Hello World!", "example"} +// +// []string{"This", "is an", "Hello World!", "example"} +// +// The quoteChars parameter is a string containing all the characters that +// are considered as quote characters. If a quote character is found, the +// function will consider the text between the quote character and the next +// quote character as a single element of the split. +// +// The acceptEmptyArguments parameter is a boolean that indicates if the +// function should consider empty arguments as valid elements of the split. +// If set to false, the function will ignore empty arguments. +// +// If the function finds an opening quote character and does not find the +// closing quote character, it will return an error. In any case, the function +// will return the split array up to the point where the error occurred. +// +// The function does not support escaping of quote characters. +// +// The function is UTF-8 safe. func SplitQuotedString(src string, quoteChars string, acceptEmptyArguments bool) ([]string, error) { // Make a map of valid quote runes isQuote := map[rune]bool{} @@ -83,7 +103,7 @@ func SplitQuotedString(src string, quoteChars string, acceptEmptyArguments bool) } if escapingChar != 0 { - return nil, fmt.Errorf("invalid quoting, no closing `%c` char found", escapingChar) + return result, fmt.Errorf("invalid quoting, no closing `%c` char found", escapingChar) } return result, nil diff --git a/strings_test.go b/strings_test.go index 2f5319d..9245f04 100644 --- a/strings_test.go +++ b/strings_test.go @@ -64,8 +64,11 @@ func TestSplitQuotedStringWithUTF8(t *testing.T) { } func TestSplitQuotedStringInvalid(t *testing.T) { - _, err := SplitQuotedString(`'this is' a 'test of quoting`, `"'`, true) - require.Error(t, err) - _, err = SplitQuotedString(`'this is' a "'test" of "quoting`, `"'`, true) - require.Error(t, err) + res, err := SplitQuotedString(`'this is' a 'test of quoting`, `"'`, true) + require.EqualError(t, err, "invalid quoting, no closing `'` char found") + require.Equal(t, res, []string{"this is", "a"}) + + res, err = SplitQuotedString(`'this is' a "'test" of "quoting`, `"'`, true) + require.EqualError(t, err, "invalid quoting, no closing `\"` char found") + require.Equal(t, res, []string{"this is", "a", "'test", "of"}) } diff --git a/testdata/non-utf8.properties b/testdata/non-utf8.properties new file mode 100644 index 0000000..34390ea --- /dev/null +++ b/testdata/non-utf8.properties @@ -0,0 +1 @@ +maintainer=Aáa