diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ead98c7..bea8b19 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,8 @@ version: 2 updates: - package-ecosystem: cargo - directory: "/data/render" + directory: "/ci" schedule: interval: daily time: "11:00" open-pull-requests-limit: 10 - ignore: - - dependency-name: tokio - versions: - - 1.1.1 - - 1.2.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5cfabb..b2c5f89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,11 +3,84 @@ name: CI on: pull_request: branches: [master] + push: + branches: [master] + paths: + - "ci/**" + - ".github/workflows/ci.yml" + workflow_dispatch: + +permissions: + contents: read jobs: - build: + rust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check formatting + run: cargo fmt --manifest-path ci/Cargo.toml --all -- --check + - name: Check workspace + run: cargo check --locked --manifest-path ci/Cargo.toml --workspace --all-targets + - name: Test workspace + run: cargo test --locked --manifest-path ci/Cargo.toml --workspace + + readme-check: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + - name: Check README.md was not edited directly + env: + AUTHOR: ${{ github.event.pull_request.user.login }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + case "$AUTHOR" in mre|jakubsacha) exit 0 ;; esac + if ! git diff --quiet "$BASE_SHA...$HEAD_SHA" -- README.md; then + mkdir -p ci-readme-output + printf '%s\n' 'README.md was edited directly. It is generated from the YAML files in data/tools/. Please edit those files instead and revert the README change.' > ci-readme-output/comment.md + echo 'README.md must not be edited directly.' >&2 + exit 1 + fi + - name: Upload README feedback + if: failure() + uses: actions/upload-artifact@v4 + with: + name: ci-readme-output + path: ci-readme-output/comment.md + if-no-files-found: ignore + retention-days: 7 + + render: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Render list + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@stable + - name: Render list without credentials + id: render + # Fork code must not receive a token. The renderer supports anonymous mode. run: make render-skip-deprecated + - name: Prepare render feedback + if: failure() && steps.render.outcome == 'failure' && github.event_name == 'pull_request' + run: | + mkdir -p ci-render-output + printf '%s\n' 'The catalog renderer failed. Please check the workflow logs and compare your YAML with the other files in data/tools/.' > ci-render-output/comment.md + - name: Upload render feedback + if: failure() && github.event_name == 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: ci-render-output + path: ci-render-output/comment.md + if-no-files-found: ignore + retention-days: 7 diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 0000000..e08b775 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,122 @@ +name: PR Check + +on: + pull_request: + branches: [master] + paths: + - "data/tools/**.yml" + - "data/tools/**.yaml" + - "ci/**" + - ".github/workflows/pr-check.yml" + - ".github/workflows/pr-comment.yml" + workflow_dispatch: + inputs: + pr_number: + description: "PR number to check (results are uploaded as an artifact)" + required: true + type: string + tool_files: + description: "Space-separated tool YAML paths at the PR head; quote paths containing spaces" + required: true + type: string + +permissions: + contents: read + +jobs: + pr-check: + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Select tool files + env: + TOOL_FILES: ${{ inputs.tool_files }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + shell: python + run: | + import json + import os + from pathlib import Path, PurePosixPath + import re + import shlex + import subprocess + + if not re.fullmatch(r"[1-9][0-9]*", os.environ["PR_NUMBER"]): + raise SystemExit("PR number must be a positive integer") + manual = os.environ["GITHUB_EVENT_NAME"] == "workflow_dispatch" + if manual: + files = shlex.split(os.environ["TOOL_FILES"]) + if not files: + raise SystemExit("Provide at least one tool file") + else: + diff = subprocess.check_output([ + "git", "diff", "--name-only", "--diff-filter=A", "-z", + os.environ["BASE_SHA"] + "..." + os.environ["HEAD_SHA"], + "--", "data/tools/*.yml", "data/tools/*.yaml", + ]) + files = [os.fsdecode(path) for path in diff.split(b"\0") if path] + for name in files: + path = PurePosixPath(name) + if (path.parts[:2] != ("data", "tools") or ".." in path.parts + or path.suffix not in (".yml", ".yaml") or ":" in name): + raise SystemExit("Only paths under data/tools ending in .yml or .yaml are allowed") + if manual: + subprocess.run([ + "git", "fetch", "origin", + "refs/pull/" + os.environ["PR_NUMBER"] + "/head", + ], check=True) + # Do not execute the PR's Rust code or workflow on manual dispatch. + subprocess.run(["git", "--literal-pathspecs", "checkout", "FETCH_HEAD", "--", *files], check=True) + root = Path("data/tools").resolve() + for name in files: + path = Path(name) + if path.is_symlink() or not path.is_file() or not path.resolve().is_relative_to(root): + raise SystemExit("Tool paths must be regular files inside data/tools") + (Path(os.environ["RUNNER_TEMP"]) / "tool-files.json").write_text(json.dumps(files)) + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Build pr-check + run: cargo build --locked --release --manifest-path ci/Cargo.toml -p pr-check + + - name: Run pr-check without comment permissions + env: + GITHUB_TOKEN: ${{ github.token }} + COMMENT_OUTPUT_FILE: pr-check-output/comment.md + shell: python + run: | + import json + import os + from pathlib import Path + import subprocess + + files = json.loads((Path(os.environ["RUNNER_TEMP"]) / "tool-files.json").read_text()) + Path("pr-check-output").mkdir(exist_ok=True) + Path(os.environ["COMMENT_OUTPUT_FILE"]).write_text( + "The PR checker could not finish. Please inspect the PR Check workflow logs.\n" + ) + result = subprocess.run(["ci/target/release/pr-check", *files]) + raise SystemExit(result.returncode) + + - name: Upload check feedback + if: always() + uses: actions/upload-artifact@v4 + with: + name: pr-check-output + path: pr-check-output/comment.md + if-no-files-found: ignore + retention-days: 7 + + - name: Explain manual results + if: always() && github.event_name == 'workflow_dispatch' + run: | + echo 'Manual checks are read-only. Download pr-check-output for the report; no PR comment is posted because workflow_dispatch does not provide an associated PR.' >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml new file mode 100644 index 0000000..68621b4 --- /dev/null +++ b/.github/workflows/pr-comment.yml @@ -0,0 +1,119 @@ +name: PR Check Comment + +on: + workflow_run: + workflows: ["CI", "PR Check"] + types: [completed] + +permissions: + actions: read + pull-requests: write + +jobs: + comment: + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.repository.id == github.event.repository.id + runs-on: ubuntu-latest + steps: + # No checkout: artifacts are untrusted text, never code or comment targets. + - name: Resolve associated PR + id: target + uses: actions/github-script@v7 + with: + script: | + const run = context.payload.workflow_run; + if (!run.head_repository?.id || !run.head_sha) { + core.notice('Missing run head identity. Skipping comment.'); + return; + } + const matchesRun = pr => + pr.state === 'open' && + pr.base?.repo?.id === context.payload.repository.id && + pr.head?.sha === run.head_sha && + pr.head?.repo?.id === run.head_repository.id; + let candidates; + if (run.pull_requests?.length) { + candidates = await Promise.all(run.pull_requests.map(async pr => { + const {data} = await github.rest.pulls.get({ + ...context.repo, pull_number: pr.number + }); + return data; + })); + } else { + // Fork runs can omit pull_requests. Only GitHub's API may supply + // fallback targets; a shared commit alone does not identify a fork. + candidates = await github.paginate( + github.rest.repos.listPullRequestsAssociatedWithCommit, { + ...context.repo, commit_sha: run.head_sha, per_page: 100 + }); + } + const prs = candidates.filter(matchesRun); + if (prs.length !== 1) { + core.notice('No unique open PR matches the run head and repositories. Skipping comment.'); + return; + } + // Recheck current state after discovery, including API fallback results. + const {data: pr} = await github.rest.pulls.get({ + ...context.repo, pull_number: prs[0].number + }); + if (!matchesRun(pr)) { + core.notice('PR no longer matches the run. Skipping comment.'); + return; + } + core.setOutput('number', pr.number); + + - name: Download feedback + if: steps.target.outputs.number != '' + uses: actions/download-artifact@v4 + with: + # Unlike name, pattern succeeds with zero artifacts (normal for passing CI). + pattern: ${{ github.event.workflow_run.name == 'PR Check' && 'pr-check-output' || 'ci-*-output' }} + path: ${{ runner.temp }}/feedback + github-token: ${{ github.token }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Post or update feedback + if: steps.target.outputs.number != '' + uses: actions/github-script@v7 + env: + PR_NUMBER: ${{ steps.target.outputs.number }} + with: + script: | + const fs = require('fs'); + const path = require('path'); + const run = context.payload.workflow_run; + const names = run.name === 'PR Check' + ? ['pr-check-output'] : ['ci-readme-output', 'ci-render-output']; + const feedback = names.map(name => ({ + name, file: path.join(process.env.RUNNER_TEMP, 'feedback', name, 'comment.md') + })).filter(({file}) => fs.existsSync(file)); + if (!feedback.length) { + core.notice('No feedback artifacts were produced. Nothing to comment.'); + return; + } + const issue_number = Number(process.env.PR_NUMBER); + const comments = await github.paginate(github.rest.issues.listComments, { + ...context.repo, issue_number, per_page: 100 + }); + for (const {name, file} of feedback) { + const stat = fs.lstatSync(file); + if (!stat.isFile() || stat.size > 60000) { + core.warning(`Ignoring invalid or oversized feedback from ${name}`); + continue; + } + const text = fs.readFileSync(file, 'utf8').trim(); + if (!text) continue; + const marker = ``; + const body = `${marker}\n${text}\n\n[Workflow logs](${run.html_url})`; + const existing = comments.find(c => + c.user.login === 'github-actions[bot]' && c.user.type === 'Bot' && + c.body.startsWith(marker)); + if (existing) { + await github.rest.issues.updateComment({ + ...context.repo, comment_id: existing.id, body + }); + } else { + await github.rest.issues.createComment({ + ...context.repo, issue_number, body + }); + } + } diff --git a/.github/workflows/render.yml b/.github/workflows/render.yml index 8877e57..a502971 100644 --- a/.github/workflows/render.yml +++ b/.github/workflows/render.yml @@ -4,18 +4,33 @@ on: push: branches: [master] +permissions: + contents: write + +concurrency: + group: render-master + cancel-in-progress: false + jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - + - uses: actions/checkout@v4 + with: + ref: master + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Render list run: make render - - - uses: stefanzweifel/git-auto-commit-action@v4.1.2 + env: + GITHUB_TOKEN: ${{ github.token }} + + - uses: stefanzweifel/git-auto-commit-action@v5 with: - branch: ${{ github.head_ref }} + branch: master + file_pattern: README.md data/api commit_message: Commit list commit_user_name: Analysis Tools Bot commit_user_email: bot@analysis-tools.dev diff --git a/Makefile b/Makefile index 50962d6..f88df26 100644 --- a/Makefile +++ b/Makefile @@ -16,23 +16,23 @@ help: # Main rendering targets render: - cargo run --manifest-path data/render/Cargo.toml -- --tags data/tags.yml --tools data/tools --md-out README.md --json-out data/api + cargo run --locked --manifest-path ci/Cargo.toml -p render -- --tags data/tags.yml --tools data/tools --md-out README.md --json-out data/api render-skip-deprecated: - cargo run --manifest-path data/render/Cargo.toml -- --tags data/tags.yml --tools data/tools --md-out README.md --json-out data/api --skip-deprecated + cargo run --locked --manifest-path ci/Cargo.toml -p render -- --tags data/tags.yml --tools data/tools --md-out README.md --json-out data/api --skip-deprecated # Development targets check: - cargo check --manifest-path data/render/Cargo.toml + cargo check --locked --workspace --manifest-path ci/Cargo.toml clippy: - cargo clippy --manifest-path data/render/Cargo.toml -- -D warnings + cargo clippy --locked --workspace --manifest-path ci/Cargo.toml -- -D warnings fmt: - cargo fmt --manifest-path data/render/Cargo.toml + cargo fmt --all --manifest-path ci/Cargo.toml test: - cargo test --manifest-path data/render/Cargo.toml + cargo test --locked --workspace --manifest-path ci/Cargo.toml clean: - cargo clean --manifest-path data/render/Cargo.toml \ No newline at end of file + cargo clean --manifest-path ci/Cargo.toml diff --git a/ci/.gitignore b/ci/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/ci/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/ci/Cargo.lock b/ci/Cargo.lock new file mode 100644 index 0000000..ee185e7 --- /dev/null +++ b/ci/Cargo.lock @@ -0,0 +1,2251 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "annotate-snippets" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" +dependencies = [ + "anstyle", + "memchr", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", + "humansize", + "num-traits", + "percent-encoding", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "granit-parser" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aa83cc6ac4dc610adf5501a5392b4bcc543b2c1f24eaf77469a96e31ec9abe" +dependencies = [ + "arraydeque", + "smallvec", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hubcaps" +version = "0.6.2" +source = "git+https://github.com/softprops/hubcaps#d60d157b6638760fc725b2e4e4f329a4ec6b901e" +dependencies = [ + "base64 0.13.1", + "data-encoding", + "futures", + "http 0.2.12", + "hyperx", + "jsonwebtoken", + "log", + "mime", + "percent-encoding", + "reqwest 0.11.27", + "serde", + "serde_derive", + "serde_json", + "url", +] + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.4.0", + "hyper 1.2.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.32", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.2.0", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "hyperx" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5617e92fc2f2501c3e2bc6ce547cad841adba2bae5b921c7e52510beca6d084c" +dependencies = [ + "base64 0.13.1", + "bytes", + "http 0.2.12", + "httpdate", + "language-tags", + "mime", + "percent-encoding", + "unicase", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afabcc15e437a6484fc4f12d0fd63068fe457bf93f1c148d3d9649c60b103f32" +dependencies = [ + "base64 0.12.3", + "pem", + "ring 0.16.20", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64 0.13.1", + "once_cell", + "regex", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "pr-check" +version = "0.1.0" +dependencies = [ + "anyhow", + "askama", + "chrono", + "pico-args", + "reqwest 0.12.4", + "serde", + "serde-saphyr", + "serde_json", + "tokio", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "render" +version = "0.2.0" +dependencies = [ + "anyhow", + "askama", + "chrono", + "hubcaps", + "pico-args", + "serde", + "serde-saphyr", + "serde_derive", + "serde_json", + "slug", + "tokio", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.2.0", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.11", + "winreg 0.52.0", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.14", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.14", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-saphyr" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3afb591f9cdb6223c88ba39269aff895620c7f0716dc42b705b5733d5c7c0823" +dependencies = [ + "annotate-snippets", + "encoding_rs_io", + "granit-parser", + "num-traits", + "serde_core", + "smallvec", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simple_asn1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" +dependencies = [ + "chrono", + "num-bigint", + "num-traits", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.43.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333f1ce734dbc263af1106964dba1f8c993a91d1857910fb542d45179c3d3da5" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/ci/Cargo.toml b/ci/Cargo.toml new file mode 100644 index 0000000..cbb6b1b --- /dev/null +++ b/ci/Cargo.toml @@ -0,0 +1,17 @@ +[workspace] +members = [ + "render", + "pr-check", +] +resolver = "2" + +[workspace.dependencies] +anyhow = "1.0" +askama = "0.12" +chrono = { version = "0.4", features = ["serde"] } +pico-args = "0.5" +serde = { version = "1.0", features = ["derive"] } +serde_derive = "1.0" +serde_json = "1.0" +serde-saphyr = { version = "1.2", default-features = false, features = ["deserialize"] } +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } \ No newline at end of file diff --git a/ci/pr-check/Cargo.toml b/ci/pr-check/Cargo.toml new file mode 100644 index 0000000..43e4713 --- /dev/null +++ b/ci/pr-check/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "pr-check" +version = "0.1.0" +edition = "2024" +description = "Checks pull requests against contributing criteria" +license = "MIT" +repository = "https://github.com/analysis-tools-dev/dynamic-analysis" +publish = false + +[lints.clippy] +# Keep standard checks and targeted reliability restrictions. Broad opt-in +# groups and documentation/printing restrictions are deferred from the sister policy. +correctness = { level = "deny", priority = -1 } +style = { level = "warn", priority = -1 } +complexity = { level = "warn", priority = -1 } +perf = { level = "warn", priority = -1 } +suspicious = { level = "warn", priority = -1 } + +unwrap_used = "deny" +expect_used = "warn" +panic = "deny" +unimplemented = "deny" +unreachable = "deny" +todo = "warn" + +dbg_macro = "warn" + + +[dependencies] +anyhow = { workspace = true } +askama = { workspace = true } +chrono = { workspace = true } +pico-args = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde-saphyr = { workspace = true } +tokio = { workspace = true } +reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } \ No newline at end of file diff --git a/ci/pr-check/src/main.rs b/ci/pr-check/src/main.rs new file mode 100644 index 0000000..ba5efe7 --- /dev/null +++ b/ci/pr-check/src/main.rs @@ -0,0 +1,576 @@ +//! PR contribution checker for analysis-tools-dev/dynamic-analysis. +//! +//! Reads new or modified YAML files under `data/tools/` that were introduced +//! by a pull request, fetches metadata from the GitHub API for each tool's +//! source repository, and evaluates each tool against the contributing +//! criteria: +//! +//! - More than 20 stars +//! - More than one contributor +//! - Repository is at least 3 months old +//! +//! The results are either posted as a single comment on the PR (updating an +//! existing bot comment if one already exists) or written to a file when the +//! `COMMENT_OUTPUT_FILE` environment variable is set. The latter mode is used +//! in CI to work around the GitHub Actions restriction that prevents fork PRs +//! from writing to the base repository. A separate `pr-comment` workflow then +//! picks up the file and posts the comment with the necessary permissions. +//! +//! The process exits with a non-zero status code when any hard criterion is +//! not met, causing CI to fail. +//! +//! Expected environment variables: +//! GITHUB_TOKEN - a token for GitHub API reads; `pull-requests: write` +//! is required only when posting comments directly +//! (COMMENT_OUTPUT_FILE is unset) +//! GITHUB_REPOSITORY - owner/repo, e.g. "analysis-tools-dev/dynamic-analysis" +//! PR_NUMBER - the pull request number +//! COMMENT_OUTPUT_FILE - (optional) path to write the rendered comment body +//! to instead of posting it directly via the API. + +use anyhow::{Context, Result, bail}; +use askama::Template; +use chrono::{DateTime, Duration, Utc}; +use serde::Deserialize; +use std::collections::HashMap; +use std::env; +use std::path::{Path, PathBuf}; + +/// A minimal tool entry parsed from `data/tools/.yml`. +/// Only the fields needed for the contributing criteria check are required. +#[derive(Debug, Deserialize)] +struct ToolEntry { + name: String, + source: Option, +} + +/// Response from `GET /repos/{owner}/{repo}`. +#[derive(Debug, Deserialize)] +struct RepoInfo { + stargazers_count: u64, + created_at: DateTime, +} + +/// One item from `GET /repos/{owner}/{repo}/contributors`. +#[derive(Debug, Deserialize)] +struct Contributor { + #[serde(rename = "type")] + account_type: String, +} + +/// One PR comment from `GET /repos/{owner}/{repo}/issues/{pr}/comments`. +#[derive(Debug, Deserialize)] +struct IssueComment { + id: u64, + body: String, +} + +const MIN_STARS: u64 = 20; +const MIN_CONTRIBUTORS: usize = 2; +const MIN_AGE_DAYS: i64 = 90; + +// Marker text embedded in every comment we post so we can find and update it. +const COMMENT_MARKER: &str = ""; + +/// The outcome of one criterion check. +#[derive(Debug)] +enum CheckResult { + Pass(String), + Fail(String), + Skip(String), +} + +impl CheckResult { + fn is_fail(&self) -> bool { + matches!(self, Self::Fail(_)) + } + + fn symbol(&self) -> &'static str { + match self { + Self::Pass(_) => "pass", + Self::Fail(_) => "fail", + Self::Skip(_) => "skip", + } + } + + fn message(&self) -> &str { + match self { + Self::Pass(m) | Self::Fail(m) | Self::Skip(m) => m, + } + } +} + +/// All checks for a single tool. +#[derive(Debug)] +struct ToolReport { + name: String, + source: Option, + stars: CheckResult, + contributors: CheckResult, + age: CheckResult, + /// Non-GitHub source repositories cannot be checked automatically. + note: Option, +} + +impl ToolReport { + fn any_fail(&self) -> bool { + self.stars.is_fail() || self.contributors.is_fail() || self.age.is_fail() + } + + fn status(&self) -> &'static str { + if self.any_fail() { "FAIL" } else { "PASS" } + } +} + +#[derive(Template)] +#[template(path = "comment.md")] +struct CommentTemplate<'a> { + marker: &'a str, + reports: &'a [ToolReport], + any_failures: bool, +} + +struct GithubClient { + client: reqwest::Client, + token: String, +} + +impl GithubClient { + /// Creates a new client. + /// + /// # Errors + /// + /// Returns an error if the `reqwest` client cannot be constructed. + fn new(token: String) -> Result { + let client = reqwest::Client::builder() + .user_agent("pr-check-bot/1.0 (analysis-tools-dev)") + .build() + .context("Failed to build HTTP client")?; + Ok(Self { client, token }) + } + + /// Sends an authenticated GET request and deserialises the JSON body. + /// + /// # Errors + /// + /// Returns an error on network failure or if the response cannot be + /// deserialised as `T`. + async fn get Deserialize<'de>>(&self, url: &str) -> Result> { + let resp = self + .client + .get(url) + .header("Authorization", format!("Bearer {}", self.token)) + .header("Accept", "application/vnd.github+json") + .header("X-GitHub-Api-Version", "2022-11-28") + .send() + .await + .with_context(|| format!("GET {url} failed"))?; + + let status = resp.status(); + if status == reqwest::StatusCode::NOT_FOUND { + return Ok(None); + } + if !status.is_success() { + let body = resp.text().await.unwrap_or_default(); + bail!("GET {url} returned {status}: {body}"); + } + + resp.json::() + .await + .with_context(|| format!("Failed to deserialise response from {url}")) + .map(Some) + } + + /// Fetches repository metadata. + /// + /// # Errors + /// + /// Returns an error if the API call fails. + async fn repo_info(&self, owner: &str, repo: &str) -> Result> { + let url = format!("https://api.github.com/repos/{owner}/{repo}"); + self.get::(&url).await + } + + /// Fetches the contributor list (up to 100, which is enough to confirm + /// whether there is more than one human contributor). + /// + /// # Errors + /// + /// Returns an error if the API call fails. + async fn contributor_count(&self, owner: &str, repo: &str) -> Result> { + let url = + format!("https://api.github.com/repos/{owner}/{repo}/contributors?per_page=100&anon=0"); + let Some(contributors) = self.get::>(&url).await? else { + return Ok(None); + }; + // Exclude bot accounts from the contributor count. + let human_count = contributors + .iter() + .filter(|c| c.account_type != "Bot") + .count(); + Ok(Some(human_count)) + } + + /// Lists all comments on a PR/issue. + /// + /// # Errors + /// + /// Returns an error if the API call fails. + async fn list_pr_comments(&self, repo: &str, pr: u64) -> Result> { + let url = format!("https://api.github.com/repos/{repo}/issues/{pr}/comments?per_page=100"); + self.get::>(&url) + .await? + .with_context(|| format!("PR {pr} not found in {repo}")) + } + + /// Creates a new PR comment. + /// + /// # Errors + /// + /// Returns an error if the API call fails. + async fn create_pr_comment(&self, repo: &str, pr: u64, body: &str) -> Result<()> { + let url = format!("https://api.github.com/repos/{repo}/issues/{pr}/comments"); + let mut payload = HashMap::new(); + payload.insert("body", body); + + let resp = self + .client + .post(&url) + .header("Authorization", format!("Bearer {}", self.token)) + .header("Accept", "application/vnd.github+json") + .header("X-GitHub-Api-Version", "2022-11-28") + .json(&payload) + .send() + .await + .with_context(|| format!("POST {url} failed"))?; + + let status = resp.status(); + if !status.is_success() { + let body = resp.text().await.unwrap_or_default(); + bail!("POST {url} returned {status}: {body}"); + } + Ok(()) + } + + /// Updates an existing PR comment. + /// + /// # Errors + /// + /// Returns an error if the API call fails. + async fn update_pr_comment(&self, repo: &str, comment_id: u64, body: &str) -> Result<()> { + let url = format!("https://api.github.com/repos/{repo}/issues/comments/{comment_id}"); + let mut payload = HashMap::new(); + payload.insert("body", body); + + let resp = self + .client + .patch(&url) + .header("Authorization", format!("Bearer {}", self.token)) + .header("Accept", "application/vnd.github+json") + .header("X-GitHub-Api-Version", "2022-11-28") + .json(&payload) + .send() + .await + .with_context(|| format!("PATCH {url} failed"))?; + + let status = resp.status(); + if !status.is_success() { + let body = resp.text().await.unwrap_or_default(); + bail!("PATCH {url} returned {status}: {body}"); + } + Ok(()) + } +} + +/// Parses `owner` and `repo` out of a GitHub URL like +/// `https://github.com/owner/repo` or `https://github.com/owner/repo/`. +/// Returns `None` for non-GitHub URLs or malformed paths. +fn parse_github_repo(url: &str) -> Option<(String, String)> { + let url = url.trim_end_matches('/'); + let without_scheme = url + .strip_prefix("https://github.com/") + .or_else(|| url.strip_prefix("http://github.com/"))?; + + let parts: Vec<&str> = without_scheme.splitn(3, '/').collect(); + if parts.len() < 2 || parts[0].is_empty() || parts[1].is_empty() { + return None; + } + // Reject sub-paths inside a repo (e.g. /tree/main/…). + if parts.len() == 3 && !parts[2].is_empty() { + return None; + } + Some((parts[0].to_owned(), parts[1].to_owned())) +} + +/// Reads and deserialises a single tool YAML file. +/// +/// # Errors +/// +/// Returns an error if the file cannot be read or parsed. +fn read_tool(path: &Path) -> Result { + let f = std::fs::File::open(path).with_context(|| format!("Cannot open {}", path.display()))?; + serde_saphyr::from_reader(f).with_context(|| format!("Cannot parse {}", path.display())) +} + +/// Runs all contributing-criteria checks for one tool. +/// +/// # Errors +/// +/// Returns an error only for unexpected failures (network, auth). Missing +/// criteria produce `CheckResult::Fail` values, not errors. +async fn check_tool(client: &GithubClient, tool: &ToolEntry) -> Result { + let source = tool.source.clone(); + + let gh_coords = source.as_deref().and_then(parse_github_repo); + + if let Some((owner, repo)) = gh_coords { + let repo_result = client.repo_info(&owner, &repo).await; + let contributors_result = client.contributor_count(&owner, &repo).await; + + let stars_check = match &repo_result { + Ok(Some(info)) => { + let s = info.stargazers_count; + if s >= MIN_STARS { + CheckResult::Pass(format!("{s} stars")) + } else { + CheckResult::Fail(format!("{s} stars (minimum is {MIN_STARS})")) + } + } + Ok(None) => CheckResult::Skip("repository not found".into()), + Err(e) => CheckResult::Fail(format!("Could not fetch repo info: {e}")), + }; + + let age_check = match &repo_result { + Ok(Some(info)) => { + let age = Utc::now().signed_duration_since(info.created_at); + let days = age.num_days(); + let months = days / 30; + if age >= Duration::days(MIN_AGE_DAYS) { + CheckResult::Pass(format!("created {days} days ago (~{months} months)")) + } else { + let remaining = MIN_AGE_DAYS - days; + CheckResult::Fail(format!( + "created {days} days ago, needs {remaining} more days to meet the 3-month minimum" + )) + } + } + Ok(None) => CheckResult::Skip("repository not found".into()), + Err(_) => CheckResult::Skip("Could not determine age (repo info unavailable)".into()), + }; + + let contributors_check = match contributors_result { + Ok(Some(count)) => { + if count >= MIN_CONTRIBUTORS { + CheckResult::Pass(format!("{count} contributors")) + } else { + CheckResult::Fail(format!( + "{count} contributor(s) (minimum is {MIN_CONTRIBUTORS})" + )) + } + } + Ok(None) => CheckResult::Skip("repository not found".into()), + Err(e) => CheckResult::Fail(format!("Could not fetch contributors: {e}")), + }; + + let repo_not_found = matches!(repo_result, Ok(None)); + let note = repo_not_found.then_some( + "The source URL returned a 404. Please check that the repository exists and is public.", + ); + + Ok(ToolReport { + name: tool.name.to_string(), + source, + stars: stars_check, + contributors: contributors_check, + age: age_check, + note: note.map(str::to_owned), + }) + } else { + // No source or non-GitHub source. This is fine for proprietary or + // hosted tools. Skip automated checks and leave a note for manual review. + let note = "No GitHub source URL found. Automated checks for stars, contributor count, \ + and age are not possible. Please verify the contributing criteria manually."; + + Ok(ToolReport { + name: tool.name.to_string(), + source, + stars: CheckResult::Skip("N/A".into()), + contributors: CheckResult::Skip("N/A".into()), + age: CheckResult::Skip("N/A".into()), + note: Some(note.into()), + }) + } +} + +/// Renders all tool reports into a Markdown comment body. +/// +/// # Errors +/// +/// Returns an error if the template fails to render. +fn render_comment(reports: &[ToolReport]) -> Result { + let any_failures = reports.iter().any(|r| r.any_fail()); + CommentTemplate { + marker: COMMENT_MARKER, + reports, + any_failures, + } + .render() + .context("Failed to render comment template") +} + +/// Posts or updates the bot comment on the PR. +/// +/// # Errors +/// +/// Returns an error if the GitHub API calls fail. +async fn upsert_comment(client: &GithubClient, repo: &str, pr: u64, body: &str) -> Result<()> { + let comments = client.list_pr_comments(repo, pr).await?; + + let existing = comments.iter().find(|c| c.body.contains(COMMENT_MARKER)); + + match existing { + Some(c) => client.update_pr_comment(repo, c.id, body).await, + None => client.create_pr_comment(repo, pr, body).await, + } +} + +/// Parses a PR number from a string. +/// +/// # Errors +/// +/// Returns an error if the string is not a valid integer. +fn parse_pr_number(s: &str) -> Result { + s.trim() + .parse::() + .with_context(|| format!("Invalid PR number: {s}")) +} + +#[tokio::main] +async fn main() -> Result<()> { + let token = env::var("GITHUB_TOKEN").context("GITHUB_TOKEN not set")?; + let gh_repo = env::var("GITHUB_REPOSITORY").context("GITHUB_REPOSITORY not set")?; + let pr_number_str = env::var("PR_NUMBER").context("PR_NUMBER not set")?; + let pr_number = parse_pr_number(&pr_number_str)?; + + // Remaining CLI arguments are the paths to check. + // Usage: pr-check data/tools/foo.yml data/tools/bar.yml + let pico = pico_args::Arguments::from_env(); + let tool_paths: Vec = pico.finish().into_iter().map(PathBuf::from).collect(); + + let tool_paths: Vec = tool_paths + .into_iter() + .filter(|p| { + p.starts_with("data/tools") + && matches!( + p.extension().and_then(|e| e.to_str()), + Some("yml") | Some("yaml") + ) + }) + .collect(); + + let client = GithubClient::new(token)?; + + let mut reports = Vec::new(); + for path in &tool_paths { + let tool = read_tool(path).with_context(|| format!("Failed to read {}", path.display()))?; + eprintln!("Checking '{}'...", tool.name); + let report = check_tool(&client, &tool).await?; + reports.push(report); + } + + let comment_body = render_comment(&reports)?; + + // If COMMENT_OUTPUT_FILE is set, write the comment to that file instead of + // posting it via the API. This is used by the `pull_request` CI workflow to + // avoid the 403 that GitHub returns when a fork PR tries to write comments. + // A separate `pr-comment` workflow picks up the file and posts the comment + // with the write permissions it has as a `workflow_run` job. + if let Ok(output_file) = env::var("COMMENT_OUTPUT_FILE") { + if let Some(parent) = std::path::Path::new(&output_file).parent() { + std::fs::create_dir_all(parent) + .with_context(|| format!("Failed to create directory for {output_file}"))?; + } + std::fs::write(&output_file, &comment_body) + .with_context(|| format!("Failed to write comment to {output_file}"))?; + eprintln!("Comment written to {output_file}"); + } else { + upsert_comment(&client, &gh_repo, pr_number, &comment_body).await?; + } + + let any_failures = reports.iter().any(|r| r.any_fail()); + if any_failures { + eprintln!("One or more tools failed the contributing criteria check."); + std::process::exit(1); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_catalog() -> Result<()> { + let tools = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/tools"); + let mut count = 0; + let mut found_bap = false; + for entry in std::fs::read_dir(tools)? { + let path = entry?.path(); + if path + .extension() + .is_some_and(|ext| ext == "yml" || ext == "yaml") + { + let tool = read_tool(&path)?; + assert!(!tool.name.is_empty(), "{}", path.display()); + found_bap |= tool.name == "BAP"; + count += 1; + } + } + assert!(count > 0); + assert!(found_bap); + Ok(()) + } + + #[test] + fn parses_plain_github_url() { + let result = parse_github_repo("https://github.com/owner/repo"); + assert_eq!(result, Some(("owner".into(), "repo".into()))); + } + + #[test] + fn parses_trailing_slash() { + let result = parse_github_repo("https://github.com/owner/repo/"); + assert_eq!(result, Some(("owner".into(), "repo".into()))); + } + + #[test] + fn rejects_subpath() { + let result = parse_github_repo("https://github.com/owner/repo/tree/main/subdir"); + assert!(result.is_none()); + } + + #[test] + fn rejects_gitlab() { + let result = parse_github_repo("https://gitlab.com/owner/repo"); + assert!(result.is_none()); + } + + #[test] + fn rejects_missing_repo() { + let result = parse_github_repo("https://github.com/owner"); + assert!(result.is_none()); + } + + #[test] + fn render_comment_no_files() { + let comment = render_comment(&[]).unwrap(); + assert!(comment.contains("No new tool files detected")); + } + + #[test] + fn render_comment_contains_marker() { + let comment = render_comment(&[]).unwrap(); + assert!(comment.contains(COMMENT_MARKER)); + } +} diff --git a/ci/pr-check/templates/comment.md b/ci/pr-check/templates/comment.md new file mode 100644 index 0000000..19c1980 --- /dev/null +++ b/ci/pr-check/templates/comment.md @@ -0,0 +1,32 @@ +{{ marker }} +## Contributing criteria check + +{% if reports.is_empty() %} +No new tool files detected in `data/tools/`. Nothing to check. +{% else %} +{% for report in reports %} +### [{{ report.status() }}] `{{ report.name }}` + +{% if let Some(src) = report.source.as_ref() %} +Source: {{ src }} + +{% endif %} +{% if let Some(note) = report.note.as_ref() %} +> **Note:** {{ note }} + +{% endif %} +| Criterion | Result | +|---|---| +| Stars (min 20) | {{ report.stars.symbol() }} {{ report.stars.message() }} | +| Contributors (min 2) | {{ report.contributors.symbol() }} {{ report.contributors.message() }} | +| Age (min 3 months) | {{ report.age.symbol() }} {{ report.age.message() }} | + +{% endfor %} +--- + +{% if any_failures %} +One or more tools do not meet the [contributing criteria](CONTRIBUTING.md) yet. We will keep this PR open. Feel free to update it once the thresholds are met. +{% else %} +All criteria passed. Thank you for your contribution. +{% endif %} +{% endif %} \ No newline at end of file diff --git a/data/render/.gitignore b/ci/render/.gitignore similarity index 100% rename from data/render/.gitignore rename to ci/render/.gitignore diff --git a/ci/render/Cargo.toml b/ci/render/Cargo.toml new file mode 100644 index 0000000..4ba540d --- /dev/null +++ b/ci/render/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "render" +version = "0.2.0" +authors = ["Matthias Endler "] +edition = "2024" +description = "Dynamic analysis tools catalog renderer" +license = "MIT" +repository = "https://github.com/analysis-tools-dev/dynamic-analysis" +keywords = ["dynamic-analysis", "fuzzing", "profiling", "tools", "catalog"] +categories = ["development-tools"] +publish = false + +[lints.clippy] +# Keep standard checks and targeted reliability restrictions. Broad opt-in +# groups and documentation/printing restrictions are deferred from the sister policy. +correctness = { level = "deny", priority = -1 } + +# Style lints +style = { level = "warn", priority = -1 } +complexity = { level = "warn", priority = -1 } +perf = { level = "warn", priority = -1 } +suspicious = { level = "warn", priority = -1 } + + +unwrap_used = "deny" +expect_used = "warn" +panic = "deny" +unimplemented = "deny" +unreachable = "deny" +todo = "warn" + +dbg_macro = "warn" + + +[dependencies] +anyhow = { workspace = true } +chrono = { workspace = true } +pico-args = { workspace = true } +serde = { workspace = true } +serde_derive = { workspace = true } +serde_json = { workspace = true } +serde-saphyr = { workspace = true } +tokio = { workspace = true } +askama = { workspace = true } +# Switch back to crates as soon as a new release with tokio 1.x support is +# released. See https://github.com/softprops/hubcaps/pull/285 +hubcaps = { git="https://github.com/softprops/hubcaps" } +slug = "0.1.6" diff --git a/data/render/clippy.toml b/ci/render/clippy.toml similarity index 100% rename from data/render/clippy.toml rename to ci/render/clippy.toml diff --git a/ci/render/src/bin/main.rs b/ci/render/src/bin/main.rs new file mode 100644 index 0000000..372bcad --- /dev/null +++ b/ci/render/src/bin/main.rs @@ -0,0 +1,286 @@ +use anyhow::{Context, Result}; +use askama::Template; +use pico_args::Arguments; +use render::types::{Entry, ParsedEntry, Tag, Tags, Type}; +use render::{check_deprecated, create_api, create_catalog}; +use slug::slugify; +use std::collections::BTreeMap; +use std::env; +use std::ffi::OsStr; +use std::fs; +use std::path::{Path, PathBuf}; + +struct Args { + tags: PathBuf, + tools: PathBuf, + md_out: PathBuf, + json_out: PathBuf, + skip_deprecated: bool, +} + +fn parse_path(s: &OsStr) -> Result { + Ok(s.into()) +} + +fn read_tags(path: PathBuf) -> Result { + let f = fs::File::open(&path).with_context(|| format!("Cannot open {}", path.display()))?; + serde_saphyr::from_reader(f).with_context(|| format!("Cannot parse {}", path.display())) +} + +fn read_tool(path: &Path, tags: &[Tag]) -> Result { + let file = fs::File::open(path).with_context(|| format!("Cannot open {}", path.display()))?; + let parsed: ParsedEntry = serde_saphyr::from_reader(file) + .with_context(|| format!("Cannot parse {}", path.display()))?; + Entry::from_parsed(parsed, tags).with_context(|| format!("Invalid tool in {}", path.display())) +} + +fn read_tools(path: PathBuf, tags: &[Tag]) -> Result> { + let dir = + fs::read_dir(&path).with_context(|| format!("Cannot read directory {}", path.display()))?; + let mut tools = Vec::new(); + for entry in dir { + let entry = + entry.with_context(|| format!("Cannot read directory entry in {}", path.display()))?; + let path = entry.path(); + if matches!( + path.extension().and_then(OsStr::to_str), + Some("yml" | "yaml") + ) { + println!("Checking {}", path.display()); + tools.push(read_tool(&path, tags)?); + } + } + Ok(tools) +} + +/// Backfills the deprecated field in the tools data from the old tools data. +fn backfill_deprecated(tools: &mut Vec, path: &Path) -> Result<()> { + let tools_raw = match fs::read_to_string(path) { + Ok(content) => content, + Err(_) => return Ok(()), // No old data to backfill from. Skip silently. + }; + + let old_tools_data: BTreeMap = serde_json::from_str(&tools_raw) + .with_context(|| format!("Cannot parse {}", path.display()))?; + + for tool in tools { + let id = slugify(&tool.name); + if let Some(old_tool) = old_tools_data.get(&id) { + // Only backfill deprecated if it's not already set + if tool.deprecated.is_none() { + tool.deprecated = old_tool.get("deprecated").and_then(|d| d.as_bool()); + } + } + } + Ok(()) +} + +fn main() -> Result<()> { + let mut args = Arguments::from_env(); + let args = Args { + tags: args.value_from_os_str("--tags", parse_path)?, + tools: args.value_from_os_str("--tools", parse_path)?, + md_out: args.value_from_os_str("--md-out", parse_path)?, + json_out: args.value_from_os_str("--json-out", parse_path)?, + skip_deprecated: args.contains("--skip-deprecated"), + }; + + let tags = read_tags(args.tags)?; + + let mut tools = read_tools(args.tools, &tags)?; + tools.sort(); + + let should_check_deprecation = !args.skip_deprecated; + let github_token = env::var("GITHUB_TOKEN"); + let old_tools_path = args.json_out.join("tools.json"); + + match (should_check_deprecation, github_token) { + (true, Ok(token)) => check_deprecated(token, &mut tools)?, + (true, Err(_)) => { + eprintln!("No GITHUB_TOKEN environment variable found. Reusing old deprecation data."); + backfill_deprecated(&mut tools, &old_tools_path)?; + } + (false, _) => backfill_deprecated(&mut tools, &old_tools_path)?, + } + + let languages: Vec = tags + .clone() + .into_iter() + .filter(|t| t.tag_type == Type::Language) + .collect(); + + let other_tags: Vec = tags + .clone() + .into_iter() + .filter(|t| t.tag_type == Type::Other) + .collect(); + + let catalog = create_catalog(&tools, &languages, &other_tags)?; + fs::write(&args.md_out, catalog.render()?).context(format!( + "Cannot write Markdown output to {}", + args.md_out.display() + ))?; + + let api = create_api(catalog, &languages, &other_tags)?; + + let json = serde_json::to_string_pretty(&api)?; + let tools_out = args.json_out.join("tools.json"); + fs::write(&tools_out, json).context(format!( + "Cannot write tools JSON output to {}", + args.json_out.display() + ))?; + + let mut tags_json = BTreeMap::new(); + tags_json.insert("languages", languages); + tags_json.insert("other", other_tags); + let json = serde_json::to_string_pretty(&tags_json)?; + + let tags_out = args.json_out.join("tags.json"); + fs::write(&tags_out, json).context(format!( + "Cannot write tags JSON output to {}", + args.json_out.display() + ))?; + + // let stats_raw = fs::read_to_string("data/api/stats_raw.json")?; + // let stats: StatsRaw = serde_json::from_str(&stats_raw)?; + + // let stats = format_stats(stats); + // let json = serde_json::to_string(&stats)?; + + // let stats_out = args.json_out.join("stats.json"); + // fs::write(&stats_out, json).context(format!( + // "Cannot write stats JSON output to {}", + // args.json_out.display() + // ))?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use render::types::{Category, ToolType}; + + fn fixture(name: &str) -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures") + .join(name) + } + + fn tags() -> Tags { + vec![Tag { + name: "Rust".into(), + value: "rust".into(), + tag_type: Type::Language, + }] + } + + #[test] + fn reads_both_yaml_extensions_and_preserves_dynamic_categories() -> Result<()> { + let tags = tags(); + let tools = read_tools(fixture("tools"), &tags)?; + assert_eq!(tools.len(), 2); + let catalog = create_catalog(&tools, &tags, &[])?; + let markdown = catalog.render()?; + assert!(markdown.contains("Example Fuzzer")); + assert!(markdown.contains("Example Profiler")); + let api = create_api(catalog, &tags, &[])?; + assert!(api["example-fuzzer"].categories.contains(&Category::Fuzzer)); + assert!( + api["example-profiler"] + .categories + .contains(&Category::Profiler) + ); + assert!(api["example-profiler"].types.contains(&ToolType::Gui)); + let json = serde_json::to_value(api)?; + assert_eq!( + json["example-fuzzer"]["categories"], + serde_json::json!(["fuzzer"]) + ); + assert_eq!( + json["example-profiler"]["categories"], + serde_json::json!(["profiler"]) + ); + Ok(()) + } + + #[test] + fn malformed_yaml_reports_actual_path() { + let path = fixture("malformed.yaml"); + let error = read_tool(&path, &tags()).unwrap_err(); + assert!( + error + .to_string() + .contains(&format!("Cannot parse {}", path.display())) + ); + let error = read_tags(path.clone()).unwrap_err(); + assert!( + error + .to_string() + .contains(&format!("Cannot parse {}", path.display())) + ); + } + + #[test] + fn invalid_tags_report_actual_path_and_all_errors() { + let path = fixture("unexpected-filename.yaml"); + let error = format!("{:#}", read_tool(&path, &tags()).unwrap_err()); + assert!(error.contains(&path.display().to_string())); + assert!(error.contains("Different Tool Name")); + assert!(error.contains("Invalid tag: unknown-tag")); + assert!(error.contains("Invalid tag: another-unknown-tag")); + assert!(!error.contains("different-tool-name.yml")); + } + + #[test] + fn missing_inputs_report_actual_path() { + let path = fixture("missing.yaml"); + assert!( + read_tool(&path, &tags()) + .unwrap_err() + .to_string() + .contains(&path.display().to_string()) + ); + assert!( + read_tags(path.clone()) + .unwrap_err() + .to_string() + .contains(&path.display().to_string()) + ); + assert!( + read_tools(path.clone(), &tags()) + .unwrap_err() + .to_string() + .contains(&path.display().to_string()) + ); + } + + #[test] + fn backfills_from_selected_output_without_overriding_explicit_flags() -> Result<()> { + let mut tools = read_tools(fixture("tools"), &tags())?; + for tool in &mut tools { + if tool.name == "Example Profiler" { + tool.deprecated = Some(false); + } + } + backfill_deprecated(&mut tools, &fixture("tools.json"))?; + for tool in &tools { + assert_eq!(tool.deprecated, Some(tool.name == "Example Fuzzer")); + } + let previous = tools.clone(); + backfill_deprecated(&mut tools, &fixture("missing.json"))?; + assert_eq!(tools, previous); + Ok(()) + } + + #[test] + fn parses_catalog() -> Result<()> { + let data = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../data"); + let tags = read_tags(data.join("tags.yml"))?; + let tools = read_tools(data.join("tools"), &tags)?; + assert!(!tags.is_empty()); + assert!(!tools.is_empty()); + assert!(tools.iter().any(|tool| tool.name == "BAP")); + Ok(()) + } +} diff --git a/data/render/src/lib.rs b/ci/render/src/lib.rs similarity index 88% rename from data/render/src/lib.rs rename to ci/render/src/lib.rs index 95930f0..1c2bb09 100644 --- a/data/render/src/lib.rs +++ b/ci/render/src/lib.rs @@ -11,7 +11,7 @@ mod lints; pub mod stats; pub mod types; -use std::{collections::BTreeMap, iter::FromIterator}; +use std::collections::BTreeMap; use types::{Api, ApiEntry, Catalog, Entry, ParsedEntry, Tag, Type}; fn valid(entry: &ParsedEntry, tags: &[Tag]) -> Result<()> { @@ -32,13 +32,10 @@ pub async fn check_deprecated(token: String, entries: &mut Vec) -> Result continue; } - let components: Vec<&str> = entry - .source - .as_ref() - .unwrap() - .trim_end_matches('/') - .split('/') - .collect(); + let Some(source) = entry.source.as_ref() else { + continue; + }; + let components: Vec<&str> = source.trim_end_matches('/').split('/').collect(); if !(components.contains(&"github.com") && components.len() == 5) { // valid github source must have 5 elements - anything longer and they are probably a // reference to a path inside a repo, rather than a repo itself. @@ -49,10 +46,15 @@ pub async fn check_deprecated(token: String, entries: &mut Vec) -> Result let repo = components[4]; if let Ok(commit_list) = github.repo(owner, repo).commits().list("").await { - let date = &commit_list[0].commit.author.date; + let Some(commit) = commit_list.first() else { + continue; + }; + let date = &commit.commit.author.date; let last_commit = NaiveDateTime::parse_from_str(date, "%Y-%m-%dT%H:%M:%SZ")?; - let last_commit_utc: DateTime = DateTime::from_naive_utc_and_offset(last_commit, Utc); - let duration = Local::now().date_naive().signed_duration_since(last_commit_utc.date_naive()); + let last_commit_utc: DateTime = + DateTime::from_naive_utc_and_offset(last_commit, Utc); + let now = Local::now().date_naive(); + let duration = now.signed_duration_since(last_commit_utc.date_naive()); if duration.num_days() > 365 { entry.deprecated = Some(true); @@ -111,8 +113,8 @@ pub fn create_api(catalog: Catalog, languages: &[Tag], other_tags: &[Tag]) -> Re let mut api_entries = BTreeMap::new(); // Concatenate all entries into one vector - let mut entries: Vec = Vec::from_iter(catalog.linters.into_values().flatten()); - entries.extend(Vec::from_iter(catalog.others.into_values().flatten())); + let mut entries: Vec = catalog.linters.into_values().flatten().collect(); + entries.extend(catalog.others.into_values().flatten()); entries.extend(catalog.multi); for entry in entries { diff --git a/data/render/src/lints.rs b/ci/render/src/lints.rs similarity index 64% rename from data/render/src/lints.rs rename to ci/render/src/lints.rs index 5583d38..d5e25b4 100644 --- a/data/render/src/lints.rs +++ b/ci/render/src/lints.rs @@ -1,25 +1,27 @@ -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use crate::types::ParsedEntry; use crate::types::Tag; pub fn name(entry: &ParsedEntry, _: &[Tag]) -> Result<()> { - match entry.name.len() <= 50 { - true => Ok(()), - false => Err(anyhow!( + if entry.name.len() <= 50 { + Ok(()) + } else { + Err(anyhow!( "Name of entry may be at most 50 characters long, but {} is {} long", entry.name, entry.name.len() - )), + )) } } pub fn min_one_tag(entry: &ParsedEntry, _: &[Tag]) -> Result<()> { - match entry.tags.is_empty() { - true => Err(anyhow!( + if entry.tags.is_empty() { + Err(anyhow!( "{} must have at least one tag from `tags.yml`.", entry.name - )), - false => Ok(()), + )) + } else { + Ok(()) } } diff --git a/data/render/src/stats.rs b/ci/render/src/stats.rs similarity index 100% rename from data/render/src/stats.rs rename to ci/render/src/stats.rs diff --git a/data/render/src/types.rs b/ci/render/src/types.rs similarity index 89% rename from data/render/src/types.rs rename to ci/render/src/types.rs index 3ec8dbf..61e014c 100644 --- a/data/render/src/types.rs +++ b/ci/render/src/types.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use askama::Template; use serde::Deserialize; use std::cmp::Ordering; @@ -23,8 +23,8 @@ pub struct Tag { } impl Tag { - fn new(name: &str, value: &str, tag_type: Type) -> Tag { - Tag { + fn new(name: &str, value: &str, tag_type: Type) -> Self { + Self { name: name.into(), value: value.into(), tag_type, @@ -93,6 +93,8 @@ pub struct ParsedEntry { pub enum ToolType { #[serde(rename = "cli")] Commandline, + #[serde(rename = "gui")] + Gui, #[serde(rename = "service")] Service, #[serde(rename = "ide-plugin")] @@ -133,13 +135,23 @@ impl Entry { pub fn from_parsed(p: ParsedEntry, tags: &[Tag]) -> Result { valid(&p, tags)?; - let entry_tags: Result> = p.tags.iter().map(|t| get_tag(t, tags)).collect(); + + let tag_results: Vec> = p.tags.iter().map(|t| get_tag(t, tags)).collect(); + let tag_errors: Vec = tag_results + .iter() + .filter_map(|r| r.as_ref().err().map(|e| e.to_string())) + .collect(); + if !tag_errors.is_empty() { + bail!("Tool '{}': {}", p.name, tag_errors.join("\n")); + } + let entry_tags: Result> = tag_results.into_iter().collect(); + let types: Result> = p .types .iter() .map(|t| { - serde_json::from_value::(serde_json::to_value(t).unwrap()) - .map_err(|e| e.into()) + let value = serde_json::to_value(t)?; + serde_json::from_value::(value).map_err(Into::into) }) .collect(); diff --git a/data/render/templates/README.md b/ci/render/templates/README.md similarity index 100% rename from data/render/templates/README.md rename to ci/render/templates/README.md diff --git a/ci/render/tests/fixtures/malformed.yaml b/ci/render/tests/fixtures/malformed.yaml new file mode 100644 index 0000000..99cab7c --- /dev/null +++ b/ci/render/tests/fixtures/malformed.yaml @@ -0,0 +1 @@ +name: [ diff --git a/ci/render/tests/fixtures/tools.json b/ci/render/tests/fixtures/tools.json new file mode 100644 index 0000000..b35f61a --- /dev/null +++ b/ci/render/tests/fixtures/tools.json @@ -0,0 +1,4 @@ +{ + "example-fuzzer": { "deprecated": true }, + "example-profiler": { "deprecated": true } +} diff --git a/ci/render/tests/fixtures/tools/fuzzer.yaml b/ci/render/tests/fixtures/tools/fuzzer.yaml new file mode 100644 index 0000000..9f9dc3d --- /dev/null +++ b/ci/render/tests/fixtures/tools/fuzzer.yaml @@ -0,0 +1,7 @@ +name: Example Fuzzer +categories: [fuzzer] +tags: [rust] +license: MIT +types: [cli] +homepage: https://example.com/fuzzer +description: A fuzzer. diff --git a/ci/render/tests/fixtures/tools/ignored.txt b/ci/render/tests/fixtures/tools/ignored.txt new file mode 100644 index 0000000..bc70a29 --- /dev/null +++ b/ci/render/tests/fixtures/tools/ignored.txt @@ -0,0 +1 @@ +This is not a YAML tool entry and must be ignored. diff --git a/ci/render/tests/fixtures/tools/profiler.yml b/ci/render/tests/fixtures/tools/profiler.yml new file mode 100644 index 0000000..db9e295 --- /dev/null +++ b/ci/render/tests/fixtures/tools/profiler.yml @@ -0,0 +1,7 @@ +name: Example Profiler +categories: [profiler] +tags: [rust] +license: MIT +types: [gui] +homepage: https://example.com/profiler +description: A profiler. diff --git a/ci/render/tests/fixtures/unexpected-filename.yaml b/ci/render/tests/fixtures/unexpected-filename.yaml new file mode 100644 index 0000000..b74ea9e --- /dev/null +++ b/ci/render/tests/fixtures/unexpected-filename.yaml @@ -0,0 +1,7 @@ +name: Different Tool Name +categories: [fuzzer] +tags: [unknown-tag, another-unknown-tag] +license: MIT +types: [cli] +homepage: https://example.com/tool +description: A tool with invalid tags. diff --git a/data/README.md b/data/README.md index c69509a..fe36e1b 100644 --- a/data/README.md +++ b/data/README.md @@ -1,4 +1,9 @@ -This is the project's CI environment. -Read more about the tooling [here](https://matthias-endler.de/2017/obsolete/). -Want to add an entry to the list? -Here's how to [contribute](https://github.com/analysis-tools-dev/static-analysis/blob/master/CONTRIBUTING.md). +This folder contains the source data for the list. + +- `tools/` - one YAML file per tool +- `tags.yml` - all valid tags that can be used in tool entries +- `api/` - generated JSON output (do not edit manually) + +To add a tool, create a file in `tools/` following the format described in [CONTRIBUTING.md](../CONTRIBUTING.md). + +The Rust workspace in [`ci/`](../ci/) contains the renderer and pull request checker. Run `make render` from the repository root to regenerate the README and JSON API, or `make render-skip-deprecated` to reuse existing deprecation data without GitHub API requests. diff --git a/data/render/Cargo.lock b/data/render/Cargo.lock deleted file mode 100644 index e3b876f..0000000 --- a/data/render/Cargo.lock +++ /dev/null @@ -1,1456 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" - -[[package]] -name = "askama" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" -dependencies = [ - "askama_derive", - "askama_escape", - "humansize", - "num-traits", - "percent-encoding", -] - -[[package]] -name = "askama_derive" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e80b5ad1afe82872b7aa3e9de9b206ecb85584aa324f0f60fa4c903ce935936b" -dependencies = [ - "basic-toml", - "mime", - "mime_guess", - "nom", - "proc-macro2", - "quote", - "serde", - "syn 1.0.104", -] - -[[package]] -name = "askama_escape" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "basic-toml" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bumpalo" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "data-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - -[[package]] -name = "deunicode" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a1abaf4d861455be59f64fd2b55606cb151fce304ede7165f410243ce96bde6" - -[[package]] -name = "encoding_rs" -version = "0.8.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" - -[[package]] -name = "fastrand" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" - -[[package]] -name = "futures-io" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" - -[[package]] -name = "futures-sink" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" - -[[package]] -name = "futures-task" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" - -[[package]] -name = "futures-util" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" -dependencies = [ - "futures-core", - "futures-sink", - "futures-task", - "pin-project-lite", - "pin-utils", -] - -[[package]] -name = "h2" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 1.9.1", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "http" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hubcaps" -version = "0.6.2" -source = "git+https://github.com/softprops/hubcaps#d60d157b6638760fc725b2e4e4f329a4ec6b901e" -dependencies = [ - "base64 0.13.0", - "data-encoding", - "futures", - "http", - "hyperx", - "jsonwebtoken", - "log", - "mime", - "percent-encoding", - "reqwest", - "serde", - "serde_derive", - "serde_json", - "url", -] - -[[package]] -name = "humansize" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] - -[[package]] -name = "hyper" -version = "0.14.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "hyperx" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5617e92fc2f2501c3e2bc6ce547cad841adba2bae5b921c7e52510beca6d084c" -dependencies = [ - "base64 0.13.0", - "bytes", - "http", - "httpdate", - "language-tags", - "mime", - "percent-encoding", - "unicase", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "js-sys", - "once_cell", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" -dependencies = [ - "equivalent", - "hashbrown 0.14.3", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" - -[[package]] -name = "itoa" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" - -[[package]] -name = "js-sys" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "jsonwebtoken" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afabcc15e437a6484fc4f12d0fd63068fe457bf93f1c148d3d9649c60b103f32" -dependencies = [ - "base64 0.12.3", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64de3cc433455c14174d42e554d4027ee631c4d046d43e3ecc6efc4636cdc7a7" - -[[package]] -name = "libm" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "mio" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.36.1", -] - -[[package]] -name = "native-tls" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "nom" -version = "7.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" -dependencies = [ - "memchr", - "minimal-lexical", - "version_check", -] - -[[package]] -name = "num-bigint" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" - -[[package]] -name = "openssl" -version = "0.10.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "pem" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" -dependencies = [ - "base64 0.13.0", - "once_cell", - "regex", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "pin-project-lite" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" - -[[package]] -name = "proc-macro2" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "render" -version = "0.2.0" -dependencies = [ - "anyhow", - "askama", - "chrono", - "hubcaps", - "pico-args", - "serde", - "serde_derive", - "serde_json", - "serde_yaml", - "slug", - "tokio", -] - -[[package]] -name = "reqwest" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" -dependencies = [ - "base64 0.13.0", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "ryu" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi", -] - -[[package]] -name = "security-framework" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.82", -] - -[[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.2.1", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "simple_asn1" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" -dependencies = [ - "chrono", - "num-bigint", - "num-traits", -] - -[[package]] -name = "slab" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" - -[[package]] -name = "slug" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" -dependencies = [ - "deunicode", - "wasm-bindgen", -] - -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "syn" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "tinyvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.45.0", -] - -[[package]] -name = "tokio-macros" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.104", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - -[[package]] -name = "tracing" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" - -[[package]] -name = "unicode-ident" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 1.0.104", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.104", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" - -[[package]] -name = "web-sys" -version = "0.3.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-link" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" - -[[package]] -name = "winreg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" -dependencies = [ - "winapi", -] - -[[package]] -name = "zmij" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4a4e8e9dc5c62d159f04fcdbe07f4c3fb710415aab4754bf11505501e3251d" diff --git a/data/render/Cargo.toml b/data/render/Cargo.toml deleted file mode 100644 index 47d2e00..0000000 --- a/data/render/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "render" -version = "0.2.0" -authors = ["Matthias Endler "] -edition = "2018" - -[dependencies] -serde = "1.0.228" -serde_derive = "1.0.136" -serde_yaml = "0.9.34" -askama = "0.12.1" -# Switch back to crates as soon as a new release with tokio 1.x support is -# released. See https://github.com/softprops/hubcaps/pull/285 -hubcaps = { git="https://github.com/softprops/hubcaps" } -tokio = { version = "1.26.0", features = ["rt-multi-thread", "macros"] } -chrono = "0.4.45" -anyhow = "1.0.102" -pico-args = "0.5.0" -serde_json = "1.0.150" -slug = "0.1.6" diff --git a/data/render/src/bin/main.rs b/data/render/src/bin/main.rs deleted file mode 100644 index 31838f3..0000000 --- a/data/render/src/bin/main.rs +++ /dev/null @@ -1,129 +0,0 @@ -use anyhow::{Context, Result}; -use askama::Template; -use pico_args::Arguments; -use render::types::{Entry, ParsedEntry, Tag, Tags, Type}; -use render::{check_deprecated, create_api, create_catalog}; -use std::collections::BTreeMap; -use std::env; -use std::ffi::OsStr; -use std::fs; -use std::io; -use std::path::PathBuf; - -struct Args { - tags: PathBuf, - tools: PathBuf, - md_out: PathBuf, - json_out: PathBuf, - skip_deprecated: bool, -} - -fn parse_path(s: &OsStr) -> Result { - Ok(s.into()) -} - -fn read_tags(path: PathBuf) -> Result { - let f = std::fs::File::open(path)?; - Ok(serde_yaml::from_reader(f)?) -} - -fn read_tools(path: PathBuf) -> Result> { - let dir: std::fs::ReadDir = std::fs::read_dir(path)?; - - let files = dir - .map(|res| res.map(|e| e.path())) - .filter(|x| match x { - Ok(pb) => pb.extension().and_then(OsStr::to_str) == Some("yml"), - Err(_) => false, - }) - .collect::, io::Error>>()?; - - files - .iter() - .inspect(|p| println!("Checking {}", p.display())) - .map(|p| { - let file = std::fs::File::open(p)?; - let entry: ParsedEntry = serde_yaml::from_reader(file)?; - Ok(entry) - }) - .collect::, _>>() -} - -fn main() -> Result<()> { - let mut args = Arguments::from_env(); - let args = Args { - tags: args.value_from_os_str("--tags", parse_path)?, - tools: args.value_from_os_str("--tools", parse_path)?, - md_out: args.value_from_os_str("--md-out", parse_path)?, - json_out: args.value_from_os_str("--json-out", parse_path)?, - skip_deprecated: args.contains("--skip-deprecated"), - }; - - let tags = read_tags(args.tags)?; - - let parsed_tools = read_tools(args.tools)?; - let tools: Result> = parsed_tools - .into_iter() - .map(|t| Entry::from_parsed(t, &tags)) - .collect(); - let mut tools = tools?; - tools.sort(); - - if !args.skip_deprecated { - if let Ok(token) = env::var("GITHUB_TOKEN") { - check_deprecated(token, &mut tools)?; - } - } - - let languages: Vec = tags - .clone() - .into_iter() - .filter(|t| t.tag_type == Type::Language) - .collect(); - - let other_tags: Vec = tags - .clone() - .into_iter() - .filter(|t| t.tag_type == Type::Other) - .collect(); - - let catalog = create_catalog(&tools, &languages, &other_tags)?; - fs::write(&args.md_out, catalog.render()?).context(format!( - "Cannot write Markdown output to {}", - args.md_out.display() - ))?; - - let api = create_api(catalog, &languages, &other_tags)?; - - let json = serde_json::to_string_pretty(&api)?; - let tools_out = args.json_out.join("tools.json"); - fs::write(&tools_out, json).context(format!( - "Cannot write tools JSON output to {}", - args.json_out.display() - ))?; - - let mut tags_json = BTreeMap::new(); - tags_json.insert("languages", languages); - tags_json.insert("other", other_tags); - let json = serde_json::to_string_pretty(&tags_json)?; - - let tags_out = args.json_out.join("tags.json"); - fs::write(&tags_out, json).context(format!( - "Cannot write tags JSON output to {}", - args.json_out.display() - ))?; - - // let stats_raw = fs::read_to_string("data/api/stats_raw.json")?; - // let stats: StatsRaw = serde_json::from_str(&stats_raw)?; - - // let stats = format_stats(stats); - // let json = serde_json::to_string(&stats)?; - - // let stats_out = args.json_out.join("stats.json"); - // fs::write(&stats_out, json).context(format!( - // "Cannot write stats JSON output to {}", - // args.json_out.display() - // ))?; - - Ok(()) -}