diff --git a/.all-contributorsrc b/.all-contributorsrc index fec61ca1..02a1d93f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -258,6 +258,52 @@ "contributions": [ "test" ] + }, + { + "login": "auzkok", + "name": "auzkok", + "avatar_url": "https://avatars.githubusercontent.com/u/35302680?v=4", + "profile": "https://github.com/auzkok", + "contributions": [ + "syntax-highlighting" + ] + }, + { + "login": "Jan9103", + "name": "Jan9103", + "avatar_url": "https://avatars.githubusercontent.com/u/55753387?v=4", + "profile": "https://github.com/Jan9103", + "contributions": [ + "code" + ] + }, + { + "login": "joshka", + "name": "Josh McKinney", + "avatar_url": "https://avatars.githubusercontent.com/u/381361?v=4", + "profile": "http://joshka.net", + "contributions": [ + "infra", + "code" + ] + }, + { + "login": "zamazan4ik", + "name": "Alexander Zaitsev", + "avatar_url": "https://avatars.githubusercontent.com/u/7355383?v=4", + "profile": "https://zamazan4ik.github.io", + "contributions": [ + "infra" + ] + }, + { + "login": "BanuAdrian", + "name": "Adrian Banu", + "avatar_url": "https://avatars.githubusercontent.com/u/66678108?v=4", + "profile": "https://github.com/BanuAdrian", + "contributions": [ + "test" + ] } ], "commitType": "docs" diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..35049cbc --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --package xtask --" diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml deleted file mode 100644 index d9c1ebb1..00000000 --- a/.github/workflows/audit.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Security audit -on: - pull_request: - paths: - - '**/Cargo.toml' - - '**/Cargo.lock' - - '.github/workflows/audit.yml' - schedule: - - cron: '0 0 * * *' -jobs: - security_audit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions-rs/audit-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf964814..75d613a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,15 @@ on: push: branches: - master + paths: + - "**.rs" + - ".github/workflows/ci.yml" pull_request: branches: - master + paths: + - "**.rs" + - ".github/workflows/ci.yml" env: CARGO_TERM_COLOR: always @@ -38,87 +44,95 @@ jobs: # WASI - os: ubuntu-latest - target: wasm32-wasi + target: wasm32-wasip1 # MSRV (Linux) - os: ubuntu-latest target: x86_64-unknown-linux-gnu - toolchain: 1.64.0 + toolchain: "1.80" # MSRV (MacOS) - os: macos-latest target: x86_64-apple-darwin - toolchain: 1.64.0 + toolchain: "1.80" # MSRV (Windows) - os: windows-latest target: x86_64-pc-windows-gnu - toolchain: 1.64.0 + toolchain: "1.80" # MSRV (WASI) - os: ubuntu-latest target: wasm32-wasi - toolchain: 1.64.0 + toolchain: "1.80" + + runs-on: ${{ matrix.os }} + + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - name: Install gcc-multilib + if: matrix.target == 'i686-unknown-linux-gnu' + run: | + sudo apt-get update + sudo apt-get install gcc-multilib + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain || 'stable' }} + targets: ${{ matrix.target }} + - name: Build + run: cargo build + - name: Run tests + run: cargo test --verbose --all-features --no-fail-fast + + build_and_test_nightly_with_coverage: + strategy: + matrix: + include: # Nightly (Linux) - os: ubuntu-latest target: x86_64-unknown-linux-gnu - toolchain: nightly - coverage: true # Nightly (MacOS) - os: macos-latest target: x86_64-apple-darwin - toolchain: nightly - coverage: true # Nightly (Windows) - os: windows-latest target: x86_64-pc-windows-gnu - toolchain: nightly - coverage: true # Nightly (WASI) - os: ubuntu-latest - target: wasm32-wasi - toolchain: nightly - coverage: true + target: wasm32-wasip1 runs-on: ${{ matrix.os }} env: - RUSTFLAGS: -D warnings + RUSTFLAGS: "-D warnings -C instrument-coverage" steps: - uses: actions/checkout@v4 - - name: Install gcc-multilib - if: matrix.target == 'i686-unknown-linux-gnu' - run: | - sudo apt-get update - sudo apt-get install gcc-multilib - name: Install Rust toolchain run: | - rustup toolchain install ${{ matrix.toolchain || 'stable' }} - rustup override set ${{ matrix.toolchain || 'stable' }} - rustup target add --toolchain ${{ matrix.toolchain || 'stable' }} ${{ matrix.target }} + rustup toolchain install nightly --component llvm-tools-preview --allow-downgrade + rustup override set nightly + rustup target add --toolchain nightly ${{ matrix.target }} - name: Build run: cargo build - - name: Run tests without coverage instrumentation - if: ${{ ! matrix.coverage }} - run: cargo test --verbose --all-features --no-fail-fast - name: Run tests with coverage instrumentation - if: matrix.coverage run: cargo test --verbose --all-features --no-fail-fast env: - CARGO_INCREMENTAL: '0' - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' - RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' + LLVM_PROFILE_FILE: "kibi-%p-%m.profraw" - name: Collect code coverage results - if: matrix.coverage - uses: actions-rs/grcov@v0.1 - id: coverage + run: | + cargo install grcov + grcov . --binary-path ./target/debug/ --source-dir . --output-types lcov --branch --output-path lcov.info --keep-only 'src/*' - name: Upload code coverage results - if: matrix.coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: - files: ${{ steps.coverage.outputs.report }} + files: lcov.info flags: ${{ matrix.target }} fail_ci_if_error: false verbose: true + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} static_checks: strategy: @@ -138,22 +152,36 @@ jobs: # WASI - os: ubuntu-latest - target: wasm32-wasi + target: wasm32-wasip1 runs-on: ${{ matrix.os }} + permissions: + pull-requests: write + checks: write + steps: - uses: actions/checkout@v4 - name: Install nightly with clippy and rustfmt - run: | - rustup toolchain install nightly --component rustfmt --component clippy --allow-downgrade - rustup override set nightly - rustup target add --toolchain nightly ${{ matrix.target }} - - name: Run rustfmt check - run: cargo fmt --check - - name: Run Clippy - uses: giraffate/clippy-action@v1 + uses: dtolnay/rust-toolchain@nightly with: - clippy_flags: --all-features --target ${{ matrix.target }} -- -D warnings -D clippy::pedantic - tool_name: clippy-${{ matrix.target }} - reporter: github-pr-review + targets: ${{ matrix.target }} + components: rustfmt, clippy + - name: Format + if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} + run: cargo fmt --check --all + - name: Set up cargo-action-fmt + if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }} + uses: olix0r/cargo-action-fmt/setup@v2 + - name: Check + if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }} + run: cargo check --all-features --all-targets --workspace --target ${{ matrix.target }} -q --message-format=json | cargo-action-fmt + - name: Check + if: ${{ github.event_name != 'pull_request' || matrix.target != 'x86_64-unknown-linux-gnu' }} + run: cargo check --all-features --all-targets --workspace --target ${{ matrix.target }} + - name: Clippy + if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }} + run: cargo clippy --all-features --all-targets --workspace --target ${{ matrix.target }} -q --message-format=json | cargo-action-fmt + - name: Clippy + if: ${{ github.event_name != 'pull_request' || matrix.target != 'x86_64-unknown-linux-gnu' }} + run: cargo clippy --all-features --all-targets --workspace --target ${{ matrix.target }} diff --git a/.github/workflows/loc.yml b/.github/workflows/loc.yml index 5f57c9e1..a01cd10d 100644 --- a/.github/workflows/loc.yml +++ b/.github/workflows/loc.yml @@ -1,21 +1,17 @@ on: pull_request: paths: - - '**.rs' - - 'count_loc.sh' - - '.github/workflows/loc.yml' + - "**.rs" + - ".github/workflows/loc.yml" -name: 'LOC' +name: LOC jobs: - check_lines_of_code: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 - - name: Install tokei - run: cargo install tokei + - name: Install rust + uses: dtolnay/rust-toolchain@stable - name: Check LOC <= 1024 - run: ./count_loc.sh + run: cargo xtask count-loc diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml new file mode 100644 index 00000000..6f137a6b --- /dev/null +++ b/.github/workflows/sanitizers.yml @@ -0,0 +1,57 @@ +name: Sanitizers + +on: + pull_request: + branches: + - master + paths: + - '.github/workflows/sanitizers.yml' + schedule: + - cron: '12 07 * * 2' # weekly on Tuesday + +jobs: + sanitizers: + strategy: + matrix: + sanitizer: + - address + - leak + - memory + - safestack + - thread + target: + - x86_64-unknown-linux-gnu + - x86_64-apple-darwin + include: + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + - target: x86_64-apple-darwin + os: macos-latest + - sanitizer: memory + extra_flags: -Zsanitizer-memory-track-origins + exclude: + # LeakSanitizer is not supported on MacOS + - sanitizer: leak + target: x86_64-apple-darwin + # MemorySanitizer is not supported on MacOS + - sanitizer: memory + target: x86_64-apple-darwin + # Safestack is not supported on MacOS + - sanitizer: safestack + target: x86_64-apple-darwin + + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + with: + targets: ${{ matrix.target }} + components: rust-src + - name: Run tests with sanitizer + run: cargo test -Zbuild-std --verbose --all-features --no-fail-fast --target ${{ matrix.target }} + env: + RUSTFLAGS: -D warnings -Zsanitizer=${{ matrix.sanitizer }} ${{ matrix.extra_flags }} + RUSTDOCFLAGS: -Zsanitizer=${{ matrix.sanitizer }} ${{ matrix.extra_flags }} diff --git a/.github/workflows/devskim.yml b/.github/workflows/security.yml similarity index 58% rename from .github/workflows/devskim.yml rename to .github/workflows/security.yml index 7263e884..a0899a75 100644 --- a/.github/workflows/devskim.yml +++ b/.github/workflows/security.yml @@ -1,4 +1,4 @@ -name: DevSkim +name: Security on: push: @@ -8,12 +8,12 @@ on: branches: - master schedule: - - cron: '25 16 * * 1' + - cron: '22 17 * * 5' jobs: - lint: + dev-skim: name: DevSkim - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest permissions: actions: read contents: read @@ -26,6 +26,15 @@ jobs: uses: microsoft/DevSkim-Action@v1 - name: Upload DevSkim scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: devskim-results.sarif + cargo-deny: + name: Cargo Deny + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v1 + with: + command-arguments: -D warnings + \ No newline at end of file diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml deleted file mode 100644 index d88bba85..00000000 --- a/.github/workflows/shellcheck.yml +++ /dev/null @@ -1,18 +0,0 @@ -on: - pull_request: - paths: - - '**.sh' - - '.github/workflows/shellcheck.yml' - -name: 'ShellCheck' - -jobs: - shellcheck: - name: ShellCheck - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Run ShellCheck - uses: ludeeus/action-shellcheck@master - env: - SHELLCHECK_OPTS: --enable all diff --git a/.github/workflows/slscan.yml b/.github/workflows/slscan.yml deleted file mode 100644 index 05f3ce25..00000000 --- a/.github/workflows/slscan.yml +++ /dev/null @@ -1,35 +0,0 @@ -# This workflow integrates Scan with GitHub's code scanning feature -# Scan is a free open-source security tool for modern DevOps teams from ShiftLeft -# Visit https://slscan.io/en/latest/integrations/code-scan for help -name: SL Scan - -on: - push: - branches: - - master - pull_request: - branches: - - master - schedule: - - cron: '22 17 * * 5' - -jobs: - Scan-Build: - # Scan runs on ubuntu, mac and windows - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Perform Scan - uses: ShiftLeftSecurity/scan-action@master - env: - WORKSPACE: "" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SCAN_AUTO_BUILD: true - with: - output: reports - - - name: Upload report - uses: github/codeql-action/upload-sarif@v1 - with: - sarif_file: reports diff --git a/.markdownlint.json b/.markdownlint.json index 3f38df6e..4711e71c 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -1,19 +1,10 @@ { - "MD003": { - "style": "atx" - }, "MD013": { "tables": false }, "MD024": { "siblings_only": true }, - "MD026": { - "punctuation": ".,;:!。,;:!" - }, - "MD029": { - "style": "ordered" - }, "MD033": { "allowed_elements": [ "details", diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..a47cdf96 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.rustfmt.extraArgs": ["+nightly"] +} diff --git a/Cargo.lock b/Cargo.lock index 2769272b..14a0d63f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,365 +2,1388 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[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 = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +dependencies = [ + "shlex", +] + +[[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.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets", +] + +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap", + "unicode-width 0.1.14", + "vec_map", +] + +[[package]] +name = "clap" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if", + "num_cpus", + "serde", +] + +[[package]] +name = "deunicode" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick 1.1.3", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags 2.6.0", + "ignore", + "walkdir", +] + +[[package]] +name = "grep-matcher" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02" +dependencies = [ + "memchr", +] + +[[package]] +name = "grep-searcher" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b6c14b3fc2e0a107d6604d3231dec0509e691e62447104bc385a46a7892cda" +dependencies = [ + "bstr", + "encoding_rs", + "encoding_rs_io", + "grep-matcher", + "log", + "memchr", + "memmap2", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "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 = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kibi" +version = "0.2.2" +dependencies = [ + "libc", + "tempfile", + "unicode-width 0.2.0", + "winapi", + "winapi-util", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] [[package]] -name = "bitflags" -version = "1.3.2" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] [[package]] -name = "bitflags" -version = "2.4.1" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] -name = "dashmap" -version = "5.5.0" +name = "rayon" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ - "cfg-if", - "hashbrown", - "lock_api", - "once_cell", - "parking_lot_core", + "either", + "rayon-core", ] [[package]] -name = "errno" -version = "0.3.8" +name = "rayon-core" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "libc", - "windows-sys", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] -name = "fastrand" -version = "2.0.0" +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] [[package]] -name = "futures" -version = "0.3.28" +name = "regex" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "aho-corasick 1.1.3", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "futures-channel" -version = "0.3.28" +name = "regex-automata" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ - "futures-core", - "futures-sink", + "aho-corasick 1.1.3", + "memchr", + "regex-syntax", ] [[package]] -name = "futures-core" -version = "0.3.28" +name = "regex-syntax" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] -name = "futures-executor" -version = "0.3.28" +name = "rustix" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ - "futures-core", - "futures-task", - "futures-util", + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] -name = "futures-io" -version = "0.3.28" +name = "ryu" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] -name = "futures-sink" -version = "0.3.28" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] -name = "futures-task" -version = "0.3.28" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "futures-util" -version = "0.3.28" +name = "serde" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", + "serde_derive", ] [[package]] -name = "hashbrown" -version = "0.14.0" +name = "serde_derive" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "kibi" -version = "0.2.2" +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ - "libc", - "serial_test", - "tempfile", - "unicode-width", - "winapi", - "winapi-util", + "itoa", + "memchr", + "ryu", + "serde", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "sha2" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] [[package]] -name = "libc" -version = "0.2.152" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "linux-raw-sys" -version = "0.4.12" +name = "siphasher" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] -name = "lock_api" -version = "0.4.10" +name = "slug" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" dependencies = [ - "autocfg", - "scopeguard", + "deunicode", + "wasm-bindgen", ] [[package]] -name = "log" -version = "0.4.19" +name = "smallvec" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "memchr" -version = "2.5.0" +name = "strsim" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] -name = "once_cell" -version = "1.18.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "parking_lot" -version = "0.12.1" +name = "syn" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ - "lock_api", - "parking_lot_core", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "parking_lot_core" -version = "0.9.8" +name = "tempfile" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tera" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ "libc", - "redox_syscall 0.3.5", - "smallvec", - "windows-targets 0.48.1", + "winapi", ] [[package]] -name = "pin-project-lite" -version = "0.2.10" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "textwrap" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width 0.1.14", +] [[package]] -name = "proc-macro2" -version = "1.0.66" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "unicode-ident", + "thiserror-impl", ] [[package]] -name = "quote" -version = "1.0.31" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", + "quote", + "syn", ] [[package]] -name = "redox_syscall" -version = "0.3.5" +name = "tokei" +version = "12.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "a41f915e075a8a98ad64a5f7be6b7cc1710fc835c5f07e4a3efcaeb013291c00" dependencies = [ - "bitflags 1.3.2", + "aho-corasick 0.7.20", + "clap 2.34.0", + "crossbeam-channel", + "dashmap", + "dirs", + "encoding_rs_io", + "env_logger", + "grep-searcher", + "ignore", + "log", + "num-format", + "once_cell", + "parking_lot", + "rayon", + "regex", + "serde", + "serde_json", + "tera", + "term_size", + "toml", ] [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "toml" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ - "bitflags 1.3.2", + "serde", ] [[package]] -name = "rustix" -version = "0.38.28" +name = "typenum" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" dependencies = [ - "bitflags 2.4.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", + "unic-char-range", ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "unic-char-range" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" [[package]] -name = "serial_test" -version = "3.0.0" +name = "unic-common" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ad9342b3aaca7cb43c45c097dd008d4907070394bd0751a0aa8817e5a018d" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" dependencies = [ - "dashmap", - "futures", - "lazy_static", - "log", - "parking_lot", - "serial_test_derive", + "unic-ucd-segment", ] [[package]] -name = "serial_test_derive" -version = "3.0.0" +name = "unic-ucd-segment" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" dependencies = [ - "proc-macro2", - "quote", - "syn", + "unic-char-property", + "unic-char-range", + "unic-ucd-version", ] [[package]] -name = "slab" -version = "0.4.8" +name = "unic-ucd-version" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" dependencies = [ - "autocfg", + "unic-common", ] [[package]] -name = "smallvec" -version = "1.11.0" +name = "unicode-ident" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] -name = "syn" -version = "2.0.26" +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[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.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ + "bumpalo", + "log", + "once_cell", "proc-macro2", "quote", - "unicode-ident", + "syn", + "wasm-bindgen-shared", ] [[package]] -name = "tempfile" -version = "3.9.0" +name = "wasm-bindgen-macro" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.4.1", - "rustix", - "windows-sys", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "unicode-ident" -version = "1.0.11" +name = "wasm-bindgen-macro-support" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] [[package]] -name = "unicode-width" -version = "0.1.11" +name = "wasm-bindgen-shared" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "winapi" @@ -380,11 +1403,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -394,124 +1417,125 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" +name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.1" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.52.0" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows-targets", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_msvc" -version = "0.48.0" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnu" -version = "0.52.0" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +name = "xtask" +version = "0.1.0" +dependencies = [ + "anstream", + "anstyle", + "clap 4.5.21", + "glob", + "regex", + "tokei", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" +name = "zerocopy-derive" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 1cdcc3c0..0b85c2a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,11 @@ +[workspace] +resolver = "2" +members = ["xtask"] + [package] name = "kibi" version = "0.2.2" -rust-version = "1.64" +rust-version = "1.80" authors = ["Ilaï Deutel"] license = "MIT OR Apache-2.0" edition = "2018" @@ -13,18 +17,99 @@ categories = ["text-editors", "development-tools"] include = ["src/**/*", "Cargo.toml", "LICENSE*", "COPYRIGHT"] [dependencies] -unicode-width = "0.1.11" +unicode-width = "0.2.0" [target.'cfg(unix)'.dependencies] -libc = "0.2.152" +libc = "0.2.162" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.9", default-features = false, features = ["wincon"] } -winapi-util = "0.1.6" +winapi-util = "0.1.9" [dev-dependencies] -tempfile = "3.9.0" -serial_test = "3.0.0" +tempfile = "3.13.0" [badges] maintenance = { status = "actively-developed" } + +[lints] +workspace = true + +[workspace.lints.rust] +explicit_outlives_requirements = "deny" +let-underscore-drop = "deny" +meta-variable-misuse = "deny" +non_ascii_idents = "deny" +non-local-definitions = "deny" +redundant-imports = "deny" +redundant-lifetimes = "deny" +single-use-lifetimes = "deny" +trivial-casts = "deny" +trivial_numeric_casts = "deny" +unit-bindings = "deny" +unnameable-types = "deny" +unsafe-code = "deny" # Will only be allowed in platform-specific modules +unused-import-braces = "deny" +unused-lifetimes = "deny" +unused-macro-rules = "deny" +unused_qualifications = "deny" +variant_size_differences = "deny" + +[workspace.lints.clippy] +correctness = { level = "deny", priority = -1 } +style = { level = "deny", priority = -1 } +complexity = { level = "deny", priority = -1 } +perf = { level = "deny", priority = -1 } +pedantic = { level = "deny", priority = -1 } +cargo = { level = "deny", priority = -1 } +suspicious = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -1 } + +restriction = { level = "allow", priority = -1 } +allow_attributes = "deny" +as_underscore = "deny" +assertions_on_result_states = "deny" +cfg_not_test = "deny" +clone_on_ref_ptr = "deny" +create_dir = "deny" +dbg_macro = "deny" +empty_drop = "deny" +empty_enum_variants_with_brackets = "deny" +empty_structs_with_brackets = "deny" +exit = "deny" +expect_used = "deny" +float_cmp_const = "deny" +fn_to_numeric_cast_any = "deny" +format_push_string = "deny" +get_unwrap = "deny" +if_then_some_else_none = "deny" +impl_trait_in_params = "deny" +infinite_loop = "deny" +map_with_unused_argument_over_ranges = "deny" +mem_forget = "deny" +missing_assert_message = "deny" +mixed_read_write_in_expression = "deny" +mod_module_files = "deny" +module_name_repetitions = "deny" +multiple_inherent_impl = "deny" +mutex_atomic = "deny" +needless_raw_strings = "deny" +panic = "deny" +rc_buffer = "deny" +rc_mutex = "deny" +redundant_type_annotations = "deny" +ref_patterns = "deny" +renamed_function_params = "deny" +rest_pat_in_fully_bound_structs = "deny" +same_name_method = "deny" +semicolon_outside_block = "deny" +separated_literal_suffix = "deny" +str_to_string = "deny" +unused_result_ok = "deny" +unwrap_used = "deny" +use_debug = "deny" +verbose_file_reads = "deny" +wildcard_enum_match_arm = "deny" + +[profile.release] +lto = true diff --git a/README.md b/README.md index 6c3f8c23..4e2b2cd5 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,15 @@ [![Build Status](https://img.shields.io/github/actions/workflow/status/ilai-deutel/kibi/ci.yml?branch=master&logo=github-actions)](https://github.com/ilai-deutel/kibi/actions/workflows/ci.yml?query=branch%3Amaster) [![Lines of code](https://img.shields.io/github/actions/workflow/status/ilai-deutel/kibi/loc.yml?label=LOC%E2%89%A41024)](#) [![Crate](https://img.shields.io/crates/v/kibi.svg)](https://crates.io/crates/kibi) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.64+-blue.svg?logo=rust)](https://www.rust-lang.org/) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.80+-blue.svg?logo=rust)](https://www.rust-lang.org/) [![Platform](https://img.shields.io/badge/platform-Linux%20|%20macOS%20|%20Windows%2010%20|%20WASI-blue)](#) [![Packaging status](https://repology.org/badge/tiny-repos/kibi.svg)](https://repology.org/project/kibi/versions) [![Dependency Status](https://deps.rs/repo/github/ilai-deutel/kibi/status.svg)](https://deps.rs/repo/github/ilai-deutel/kibi) [![License](https://img.shields.io/crates/l/kibi?color=blue)](#license) -[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/ilai-deutel/kibi ) -[![All Contributors](https://img.shields.io/badge/all_contributors-27-orange.svg)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-32-orange.svg)](#contributors) [![asciicast](assets/asciicast.gif)](https://asciinema.org/a/KY7tKPlxHXqRdJiv5KaTJbPj5) @@ -31,7 +31,7 @@ Contributions are welcome! Be careful to stay below the 1024-line limit... 1.: Counted per platform, excluding tests and -Clippy directives, see [`count_loc.sh`](count_loc.sh) \ +Clippy directives, see [`count_loc`](./xtask/src/count_loc.rs) \ 2.: Kibi requires the terminal to support ANSI escape sequences. Windows 10 version 1703 ([Creators Update](https://devblogs.microsoft.com/commandline/windows-10-creators-update-whats-new-in-bashwsl-windows-console), April 2017) and above are supported. @@ -52,6 +52,7 @@ April 2017) and above are supported. * [Global configuration](#global-configuration) * [Syntax highlighting](#syntax-highlighting) * [Comparison with kilo](#comparison-with-kilo) +* [Contributing](#contributing) * [Dependencies](#dependencies) * [Why Kibi?](#why-kibi) * [Contributors](#contributors) @@ -144,7 +145,6 @@ sudo dnf install kibi Kibi is available from [the official repos](https://cdn.netbsd.org/pub/pkgsrc/current/pkgsrc/editors/kibi/README.html). -
Installation instructions Install using: @@ -168,16 +168,17 @@ Kibi is available [on Flathub](https://flathub.org/ca/apps/com.github.ilai_deute
Command line instructions The flatpak can be installed using: - + ```bash flatpak install flathub com.github.ilai_deutel.kibi ``` You can then run Kibi with: - - ```bash - flatpak run com.github.ilai_deutel.kibi - ``` + +```bash +flatpak run com.github.ilai_deutel.kibi +``` +
## Usage @@ -306,6 +307,15 @@ editor written by Salvatore Sanfilippo (antirez) in C, and * Memory safety, thanks to Rust! * Many bug fixes +## Contributing + +The most important limitation is that the line check the line count remains +under 1024. To check this run: + +```shell +cargo xtask count-loc +``` + ## Dependencies This project must remain tiny, so using advanced dependencies such as [`ncurses`](https://crates.io/crates/ncurses), @@ -378,6 +388,13 @@ any kind welcome!
CosminGGeorgescu

💻
Tanvir

💠
Prisacaru Bogdan-Paul

⚠️ +
auzkok

💠 + + +
Jan9103

💻 +
Josh McKinney

🚇 💻 +
Alexander Zaitsev

🚇 +
Adrian Banu

⚠️ diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..c64d65f8 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,3 @@ +allow-expect-in-tests = true +allow-panic-in-tests = true +allow-unwrap-in-tests = true diff --git a/count_loc.sh b/count_loc.sh deleted file mode 100755 index eafa2e0c..00000000 --- a/count_loc.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash - -# Count the number of lines of code in kibi source code, display a result -# table. Exit code 0 will be returned if the total LOC count is below 1024. - -# The lines of codes are counted using `tokei`, after removing the following -# from the code: -# * Clippy directives -# * Anything after #[cfg(test)] - -set -euo pipefail - -declare -i file_loc total_loc left_col_width -declare -A file_locs per_platform_total_locs - -paths=("$(dirname "${BASH_SOURCE[0]:-$0}")/"{.,src}/*.rs) - -left_col_width=6 -per_platform_total_locs['unix']=0 -per_platform_total_locs['wasi']=0 -per_platform_total_locs['windows']=0 - -for path in "${paths[@]}"; do - if (( ${#path} > left_col_width )); then left_col_width=${#path}; fi; - - tempfile=$(mktemp --suffix .rs) - # Ignore Clippy directives - code=$(grep -v -P '^\s*#!?\[(?:allow|warn|deny)\(clippy::' "${path}") - # Ignore everything after #[cfg(test)] - echo "${code%'#[cfg(test)]'*}" > "${tempfile}" - file_loc=$(tokei "${tempfile}" -t=Rust -o json | jq .Rust.code) - rm "${tempfile}" - - file_locs[${path}]=${file_loc} - - if [[ "${path}" == "./src/unix.rs" ]]; then - per_platform_total_locs['unix']=$((per_platform_total_locs['unix'] + file_loc)) - elif [[ "${path}" == "./src/wasi.rs" ]]; then - per_platform_total_locs['wasi']=$((per_platform_total_locs['wasi'] + file_loc)) - elif [[ "${path}" == "./src/windows.rs" ]]; then - per_platform_total_locs['windows']=$((per_platform_total_locs['windows'] + file_loc)) - else - for platform in "${!per_platform_total_locs[@]}"; do - per_platform_total_locs[${platform}]=$((per_platform_total_locs[${platform}] + file_loc)) - done - fi -done - -for path in "${paths[@]}"; do - printf "%-${left_col_width}s %4i\n" "${path}" "${file_locs[${path}]}" -done - -loc_too_high=false -for platform in "${!per_platform_total_locs[@]}"; do - total_loc=${per_platform_total_locs[${platform}]} - printf "%b%-${left_col_width}s %4i %b" '\x1b[1m' "Total (${platform})" "${total_loc}" '\x1b[0m' - if [[ ${total_loc} -gt 1024 ]]; then - echo -e ' \x1b[31m(> 1024)\x1b[0m' - loc_too_high=true - else - echo -e ' \x1b[32m(≤ 1024)\x1b[0m' - fi -done - -if [[ ${loc_too_high} = true ]]; then - exit 1 -fi diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..60668593 --- /dev/null +++ b/deny.toml @@ -0,0 +1,14 @@ +[graph] +all-features = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", +] diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 3510362a..eb10077f 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -8,12 +8,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c38b6b6b79f671c25e1a3e785b7b82d7562ffc9cd3efdc98627e5668a2472490" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.4.1" @@ -48,14 +42,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "fastrand" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "jobserver" @@ -88,73 +82,63 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "libfuzzer-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" dependencies = [ "arbitrary", "cc", - "once_cell", ] [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "once_cell" -version = "1.9.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ - "bitflags 2.4.1", + "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "tempfile" -version = "3.9.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "once_cell", "rustix", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "winapi" @@ -174,11 +158,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -196,15 +180,25 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -213,42 +207,48 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +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.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 9b118a33..7b16e1d3 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" -tempfile = "3.9.0" +tempfile = "3.14.0" env-test-util = "1.0.1" [dependencies.kibi] diff --git a/rustfmt.toml b/rustfmt.toml index f44e2af7..343e3e11 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,10 +2,20 @@ brace_style = "PreferSameLine" condense_wildcard_suffixes = true fn_params_layout = "Compressed" fn_single_line = true +format_code_in_doc_comments = true +format_macro_matchers = true +format_strings = true +group_imports = "StdExternalCrate" inline_attribute_width = 50 +match_arm_blocks = false +normalize_comments = true +normalize_doc_attributes = true +overflow_delimited_expr = true +reorder_impl_items = true +style_edition = "2024" use_field_init_shorthand = true use_small_heuristics = "Max" use_try_shorthand = true +type_punctuation_density = "Compressed" where_single_line = true -match_arm_blocks = false -group_imports = "StdExternalCrate" +wrap_comments = true diff --git a/src/ansi_escape.rs b/src/ansi_escape.rs index 147b8ec0..533ffbde 100644 --- a/src/ansi_escape.rs +++ b/src/ansi_escape.rs @@ -1,7 +1,10 @@ //! # ANSI Escape sequences -/// Clear from cursor to beginning of the screen -pub const CLEAR_SCREEN: &str = "\x1b[2J"; +/// Switches to the main buffer. +pub(crate) const USE_MAIN_SCREEN: &str = "\x1b[?1049l"; + +/// Switches to a new alternate screen buffer. +pub(crate) const USE_ALTERNATE_SCREEN: &str = "\x1b[?1049h"; /// Reset the formatting pub(crate) const RESET_FMT: &str = "\x1b[m"; @@ -10,7 +13,7 @@ pub(crate) const RESET_FMT: &str = "\x1b[m"; pub(crate) const REVERSE_VIDEO: &str = "\x1b[7m"; /// Move the cursor to 1:1 -pub const MOVE_CURSOR_TO_START: &str = "\x1b[H"; +pub(crate) const MOVE_CURSOR_TO_START: &str = "\x1b[H"; /// DECTCTEM: Make the cursor invisible pub(crate) const HIDE_CURSOR: &str = "\x1b[?25l"; diff --git a/src/config.rs b/src/config.rs index e5cf8189..948d1fd6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,15 +6,15 @@ use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::{fmt::Display, fs::File, str::FromStr, time::Duration}; -use crate::{sys::conf_dirs as cdirs, Error, Error::Config as ConfErr}; +use crate::{Error, Error::Config as ConfErr, sys::conf_dirs as cdirs}; /// The global Kibi configuration. #[derive(Debug, PartialEq, Eq)] pub struct Config { /// The size of a tab. Must be > 0. pub tab_stop: usize, - /// The number of confirmations needed before quitting, when changes have been made since the - /// file was last changed. + /// The number of confirmations needed before quitting, when changes have + /// been made since the file was last changed. pub quit_times: usize, /// The duration for which messages are shown in the status bar. pub message_dur: Duration, @@ -30,18 +30,20 @@ impl Default for Config { } impl Config { - /// Load the configuration, potentially overridden using `config.ini` files that can be located - /// in the following directories: + /// Load the configuration, potentially overridden using `config.ini` files + /// that can be located in the following directories: /// - On Linux, macOS, and other *nix systems: /// - `/etc/kibi` (system-wide configuration). - /// - `$XDG_CONFIG_HOME/kibi` if environment variable `$XDG_CONFIG_HOME` is defined, - /// `$HOME/.config/kibi` otherwise (user-level configuration). + /// - `$XDG_CONFIG_HOME/kibi` if environment variable `$XDG_CONFIG_HOME` + /// is defined, `$HOME/.config/kibi` otherwise (user-level + /// configuration). /// - On Windows: /// - `%APPDATA%\Kibi` /// /// # Errors /// - /// Will return `Err` if one of the configuration file cannot be parsed properly. + /// Will return `Err` if one of the configuration file cannot be parsed + /// properly. pub fn load() -> Result { let mut conf = Self::default(); @@ -50,13 +52,12 @@ impl Config { for path in paths.iter().filter(|p| p.is_file()).rev() { process_ini_file(path, &mut |key, value| { match key { - "tab_stop" => match parse_value(value)? { - 0 => return Err("tab_stop must be > 0".into()), - tab_stop => conf.tab_stop = tab_stop, - }, + "tab_stop" => + conf.tab_stop = parse_value(value).map_err(|_| "tab_stop must be > 0")?, "quit_times" => conf.quit_times = parse_value(value)?, "message_duration" => - conf.message_dur = Duration::from_secs_f32(parse_value(value)?), + conf.message_dur = Duration::try_from_secs_f32(parse_value(value)?) + .map_err(|x| x.to_string())?, "show_line_numbers" => conf.show_line_num = parse_value(value)?, _ => return Err(format!("Invalid key: {key}")), }; @@ -70,8 +71,8 @@ impl Config { /// Process an INI file. /// -/// The `kv_fn` function will be called for each key-value pair in the file. Typically, this -/// function will update a configuration instance. +/// The `kv_fn` function will be called for each key-value pair in the file. +/// Typically, this function will update a configuration instance. pub fn process_ini_file(path: &Path, kv_fn: &mut F) -> Result<(), Error> where F: FnMut(&str, &str) -> Result<(), String> { let file = File::open(path).map_err(|e| ConfErr(path.into(), 0, e.to_string()))?; @@ -89,13 +90,13 @@ where F: FnMut(&str, &str) -> Result<(), String> { } /// Trim a value (right-hand side of a key=value INI line) and parses it. -pub fn parse_value, E: Display>(value: &str) -> Result { +pub fn parse_value, E: Display>(value: &str) -> Result { value.trim().parse().map_err(|e| format!("Parser error: {e}")) } -/// Split a comma-separated list of values (right-hand side of a key=value1,value2,... INI line) and -/// parse it as a Vec. -pub fn parse_values, E: Display>(value: &str) -> Result, String> { +/// Split a comma-separated list of values (right-hand side of a +/// key=value1,value2,... INI line) and parse it as a Vec. +pub fn parse_values, E: Display>(value: &str) -> Result, String> { value.split(',').map(parse_value).collect() } @@ -103,9 +104,9 @@ pub fn parse_values, E: Display>(value: &str) -> Result) -> TempEnvVar { + fn new(key: &OsStr, value: Option<&OsStr>) -> Self { let orig_value = env::var_os(key); match value { Some(value) => env::set_var(key, value), None => env::remove_var(key), } - TempEnvVar { key: key.into(), orig_value } + Self { key: key.into(), orig_value } } } impl Drop for TempEnvVar { fn drop(&mut self) { - match self.orig_value { - Some(ref orig_value) => env::set_var(&self.key, orig_value), + match &self.orig_value { + Some(orig_value) => env::set_var(&self.key, orig_value), None => env::remove_var(&self.key), } } @@ -245,10 +246,13 @@ mod tests { assert_eq!(config, custom_config); } + #[cfg(unix)] + static XDG_CONFIG_FLAG_LOCK: LazyLock> = LazyLock::new(Mutex::default); + #[cfg(unix)] #[test] - #[serial] fn xdg_config_home() { + let _lock = XDG_CONFIG_FLAG_LOCK.lock(); let tmp_config_home = TempDir::new().expect("Could not create temporary directory"); test_config_dir( "XDG_CONFIG_HOME".as_ref(), @@ -259,8 +263,8 @@ mod tests { #[cfg(unix)] #[test] - #[serial] fn config_home() { + let _lock = XDG_CONFIG_FLAG_LOCK.lock(); let _temp_env_var = TempEnvVar::new(OsStr::new("XDG_CONFIG_HOME"), None); let tmp_home = TempDir::new().expect("Could not create temporary directory"); test_config_dir( @@ -272,7 +276,6 @@ mod tests { #[cfg(windows)] #[test] - #[serial] fn app_data() { let tmp_home = TempDir::new().expect("Could not create temporary directory"); test_config_dir( diff --git a/src/editor.rs b/src/editor.rs index fe3c1e13..58c2f181 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -3,10 +3,12 @@ use std::fmt::{Display, Write as _}; use std::io::{self, BufRead, BufReader, ErrorKind, Read, Seek, Write}; use std::iter::{self, repeat, successors}; -use std::{fs::File, path::Path, process::Command, thread, time::Instant}; +use std::{cell::RefCell, fs::File, path::Path, process::Command, thread, time::Instant}; + +use unicode_width::UnicodeWidthStr; use crate::row::{HlState, Row}; -use crate::{ansi_escape::*, syntax::Conf as SyntaxConf, sys, terminal, Config, Error}; +use crate::{Config, Error, ansi_escape::*, syntax::Conf as SyntaxConf, sys, terminal}; const fn ctrl_key(key: u8) -> u8 { key & 0x1f } const EXIT: u8 = ctrl_key(b'Q'); @@ -23,20 +25,23 @@ const EXECUTE: u8 = ctrl_key(b'E'); const REMOVE_LINE: u8 = ctrl_key(b'R'); const BACKSPACE: u8 = 127; -const HELP_MESSAGE: &str = - "^S save | ^Q quit | ^F find | ^G go to | ^D duplicate | ^E execute | ^C copy | ^X cut | ^V paste"; +const HELP_MESSAGE: &str = "^S save | ^Q quit | ^F find | ^G go to | ^D duplicate | ^E execute | \ + ^C copy | ^X cut | ^V paste"; /// `set_status!` sets a formatted status message for the editor. -/// Example usage: `set_status!(editor, "{} written to {}", file_size, file_name)` -macro_rules! set_status { - ($editor:expr, $($arg:expr),*) => ($editor.status_msg = Some(StatusMessage::new(format!($($arg),*)))) -} +/// Example usage: `set_status!(editor, "{} written to {}", file_size, +/// file_name)` +macro_rules! set_status { ($editor:expr, $($arg:expr),*) => ($editor.status_msg = Some(StatusMessage::new(format!($($arg),*)))) } + +// `width!` returns the display width of a string, plus one for the cursor +fn dsp_width(msg: &String) -> usize { UnicodeWidthStr::width(msg.as_str()) + 1 } /// Enum of input keys enum Key { Arrow(AKey), CtrlArrow(AKey), - Page(PageKey), + PageUp, + PageDown, Home, End, Delete, @@ -52,12 +57,6 @@ enum AKey { Down, } -/// Enum of page keys -enum PageKey { - Up, - Down, -} - /// Describes the cursor position and the screen offset #[derive(Default, Clone)] struct CursorState { @@ -74,42 +73,47 @@ struct CursorState { impl CursorState { fn move_to_next_line(&mut self) { (self.x, self.y) = (0, self.y + 1); } - /// Scroll the terminal window vertically and horizontally (i.e. adjusting the row offset and - /// the column offset) so that the cursor can be shown. + /// Scroll the terminal window vertically and horizontally (i.e. adjusting + /// the row offset and the column offset) so that the cursor can be + /// shown. fn scroll(&mut self, rx: usize, screen_rows: usize, screen_cols: usize) { self.roff = self.roff.clamp(self.y.saturating_sub(screen_rows.saturating_sub(1)), self.y); self.coff = self.coff.clamp(rx.saturating_sub(screen_cols.saturating_sub(1)), rx); } } -/// The `Editor` struct, contains the state and configuration of the text editor. +/// The `Editor` struct, contains the state and configuration of the text +/// editor. #[derive(Default)] pub struct Editor { - /// If not `None`, the current prompt mode (Save, Find, GoTo). If `None`, we are in regular - /// edition mode. + /// If not `None`, the current prompt mode (`Save`, `Find`, `GoTo`, or + /// `Execute`). If `None`, we are in regular edition mode. prompt_mode: Option, /// The current state of the cursor. cursor: CursorState, /// The padding size used on the left for line numbering. ln_pad: usize, - /// The width of the current window. Will be updated when the window is resized. + /// The width of the current window. Will be updated when the window is + /// resized. window_width: usize, - /// The number of rows that can be used for the editor, excluding the status bar and the message - /// bar + /// The number of rows that can be used for the editor, excluding the status + /// bar and the message bar screen_rows: usize, - /// The number of columns that can be used for the editor, excluding the part used for line numbers + /// The number of columns that can be used for the editor, excluding the + /// part used for line numbers screen_cols: usize, - /// The collection of rows, including the content and the syntax highlighting information. + /// The collection of rows, including the content and the syntax + /// highlighting information. rows: Vec, /// Whether the document has been modified since it was open. dirty: bool, /// The configuration for the editor. config: Config, - /// The number of warnings remaining before we can quit without saving. Defaults to - /// `config.quit_times`, then decreases to 0. + /// The number of warnings remaining before we can quit without saving. + /// Defaults to `config.quit_times`, then decreases to 0. quit_times: usize, - /// The file name. If None, the user will be prompted for a file name the first time they try to - /// save. + /// The file name. If None, the user will be prompted for a file name the + /// first time they try to save. // TODO: It may be better to store a PathBuf instead file_name: Option, /// The current status message being shown. @@ -118,7 +122,8 @@ pub struct Editor { syntax: SyntaxConf, /// The number of bytes contained in `rows`. This excludes new lines. n_bytes: u64, - /// The original terminal mode. It will be restored when the `Editor` instance is dropped. + /// The original terminal mode. It will be restored when the `Editor` + /// instance is dropped. orig_term_mode: Option, /// The copied buffer of a row copied_row: Vec, @@ -143,16 +148,17 @@ fn format_size(n: u64) -> String { return format!("{n}B"); } // i is the largest value such that 1024 ^ i < n - // To find i we compute the smallest b such that n <= 1024 ^ b and subtract 1 from it + // To find i we compute the smallest b such that n <= 1024 ^ b and subtract 1 + // from it let i = (64 - n.leading_zeros() + 9) / 10 - 1; - // Compute the size with two decimal places (rounded down) as the last two digits of q - // This avoid float formatting reducing the binary size + // Compute the size with two decimal places (rounded down) as the last two + // digits of q This avoid float formatting reducing the binary size let q = 100 * n / (1024 << ((i - 1) * 10)); format!("{}.{:02}{}B", q / 100, q % 100, b" kMGTPEZ"[i as usize] as char) } -/// `slice_find` returns the index of `needle` in slice `s` if `needle` is a subslice of `s`, -/// otherwise returns `None`. +/// `slice_find` returns the index of `needle` in slice `s` if `needle` is a +/// subslice of `s`, otherwise returns `None`. fn slice_find(s: &[T], needle: &[T]) -> Option { (0..(s.len() + 1).saturating_sub(needle.len())).find(|&i| s[i..].starts_with(needle)) } @@ -162,8 +168,8 @@ impl Editor { /// /// # Errors /// - /// Will return `Err` if an error occurs when enabling termios raw mode, creating the signal hook - /// or when obtaining the terminal window size. + /// Will return `Err` if an error occurs when enabling termios raw mode, + /// creating the signal hook or when obtaining the terminal window size. #[allow(clippy::field_reassign_with_default)] // False positive : https://github.com/rust-lang/rust-clippy/issues/6312 pub fn new(config: Config) -> Result { sys::register_winsize_change_signal_handler()?; @@ -172,18 +178,21 @@ impl Editor { // Enable raw mode and store the original (non-raw) terminal mode. editor.orig_term_mode = Some(sys::enable_raw_mode()?); - editor.update_window_size()?; + print!("{USE_ALTERNATE_SCREEN}"); + editor.update_window_size()?; set_status!(editor, "{}", HELP_MESSAGE); Ok(editor) } - /// Return the current row if the cursor points to an existing row, `None` otherwise. + /// Return the current row if the cursor points to an existing row, `None` + /// otherwise. fn current_row(&self) -> Option<&Row> { self.rows.get(self.cursor.y) } - /// Return the position of the cursor, in terms of rendered characters (as opposed to - /// `self.cursor.x`, which is the position of the cursor in terms of bytes). + /// Return the position of the cursor, in terms of rendered characters (as + /// opposed to `self.cursor.x`, which is the position of the cursor in + /// terms of bytes). fn rx(&self) -> usize { self.current_row().map_or(0, |r| r.cx2rx[self.cursor.x]) } /// Move the cursor following an arrow key (← → ↑ ↓). @@ -220,16 +229,17 @@ impl Editor { self.update_cursor_x_position(); } - /// Update the cursor x position. If the cursor y position has changed, the current position - /// might be illegal (x is further right than the last character of the row). If that is the - /// case, clamp `self.cursor.x`. + /// Update the cursor x position. If the cursor y position has changed, the + /// current position might be illegal (x is further right than the last + /// character of the row). If that is the case, clamp `self.cursor.x`. fn update_cursor_x_position(&mut self) { self.cursor.x = self.cursor.x.min(self.current_row().map_or(0, |row| row.chars.len())); } - /// Run a loop to obtain the key that was pressed. At each iteration of the loop (until a key is - /// pressed), we listen to the `ws_changed` channel to check if a window size change signal has - /// been received. When bytes are received, we match to a corresponding `Key`. In particular, + /// Run a loop to obtain the key that was pressed. At each iteration of the + /// loop (until a key is pressed), we listen to the `ws_changed` channel + /// to check if a window size change signal has been received. When + /// bytes are received, we match to a corresponding `Key`. In particular, /// we handle ANSI escape codes to return `Key::Delete`, `Key::Home` etc. fn loop_until_keypress(&mut self) -> Result { loop { @@ -239,8 +249,8 @@ impl Editor { self.refresh_screen()?; } let mut bytes = sys::stdin()?.bytes(); - // Match on the next byte received or, if the first byte is ('\x1b'), on the next - // few bytes. + // Match on the next byte received or, if the first byte is ('\x1b'), on + // the next few bytes. match bytes.next().transpose()? { Some(b'\x1b') => { return Ok(match bytes.next().transpose()? { @@ -253,7 +263,7 @@ impl Editor { (b'[' | b'O', Some(b'F')) => Key::End, (b'[', mut c @ Some(b'0'..=b'8')) => { let mut d = bytes.next().transpose()?; - if let (Some(b'1'), Some(b';')) = (c, d) { + if (c, d) == (Some(b'1'), Some(b';')) { // 1 is the default modifier value. Therefore, [1;5C is // equivalent to [5C, etc. c = bytes.next().transpose()?; @@ -263,8 +273,8 @@ impl Editor { (Some(c), Some(b'~')) if c == b'1' || c == b'7' => Key::Home, (Some(c), Some(b'~')) if c == b'4' || c == b'8' => Key::End, (Some(b'3'), Some(b'~')) => Key::Delete, - (Some(b'5'), Some(b'~')) => Key::Page(PageKey::Up), - (Some(b'6'), Some(b'~')) => Key::Page(PageKey::Down), + (Some(b'5'), Some(b'~')) => Key::PageUp, + (Some(b'6'), Some(b'~')) => Key::PageDown, (Some(b'5'), Some(b'A')) => Key::CtrlArrow(AKey::Up), (Some(b'5'), Some(b'B')) => Key::CtrlArrow(AKey::Down), (Some(b'5'), Some(b'C')) => Key::CtrlArrow(AKey::Right), @@ -287,7 +297,8 @@ impl Editor { } } - /// Update the `screen_rows`, `window_width`, `screen_cols` and `ln_padding` attributes. + /// Update the `screen_rows`, `window_width`, `screen_cols` and `ln_padding` + /// attributes. fn update_window_size(&mut self) -> Result<(), Error> { let wsize = sys::get_window_size().or_else(|_| terminal::get_window_size_using_cursor())?; // Make room for the status bar and status message @@ -296,12 +307,13 @@ impl Editor { Ok(()) } - /// Update the `screen_cols` and `ln_padding` attributes based on the maximum number of digits - /// for line numbers (since the left padding depends on this number of digits). + /// Update the `screen_cols` and `ln_padding` attributes based on the + /// maximum number of digits for line numbers (since the left padding + /// depends on this number of digits). fn update_screen_cols(&mut self) { - // The maximum number of digits to use for the line number is the number of digits of the - // last line number. This is equal to the number of times we can divide this number by ten, - // computed below using `successors`. + // The maximum number of digits to use for the line number is the number of + // digits of the last line number. This is equal to the number of times + // we can divide this number by ten, computed below using `successors`. let n_digits = successors(Some(self.rows.len()), |u| Some(u / 10).filter(|u| *u > 0)).count(); let show_line_num = self.config.show_line_num && n_digits + 2 < self.window_width / 4; @@ -309,9 +321,10 @@ impl Editor { self.screen_cols = self.window_width.saturating_sub(self.ln_pad); } - /// Given a file path, try to find a syntax highlighting configuration that matches the path - /// extension in one of the config directories (`/etc/kibi/syntax.d`, etc.). If such a - /// configuration is found, set the `syntax` attribute of the editor. + /// Given a file path, try to find a syntax highlighting configuration that + /// matches the path extension in one of the config directories + /// (`/etc/kibi/syntax.d`, etc.). If such a configuration is found, set + /// the `syntax` attribute of the editor. fn select_syntax_highlight(&mut self, path: &Path) -> Result<(), Error> { let extension = path.extension().and_then(std::ffi::OsStr::to_str); if let Some(s) = extension.and_then(|e| SyntaxConf::get(e).transpose()) { @@ -320,9 +333,9 @@ impl Editor { Ok(()) } - /// Update a row, given its index. If `ignore_following_rows` is `false` and the highlight state - /// has changed during the update (for instance, it is now in "multi-line comment" state, keep - /// updating the next rows + /// Update a row, given its index. If `ignore_following_rows` is `false` and + /// the highlight state has changed during the update (for instance, it + /// is now in "multi-line comment" state, keep updating the next rows fn update_row(&mut self, y: usize, ignore_following_rows: bool) { let mut hl_state = if y > 0 { self.rows[y - 1].hl_state } else { HlState::Normal }; for row in self.rows.iter_mut().skip(y) { @@ -331,8 +344,9 @@ impl Editor { if ignore_following_rows || hl_state == previous_hl_state { return; } - // If the state has changed (for instance, a multi-line comment started in this row), - // continue updating the following rows + // If the state has changed (for instance, a multi-line comment + // started in this row), continue updating the following + // rows } } @@ -344,8 +358,8 @@ impl Editor { } } - /// Insert a byte at the current cursor position. If there is no row at the current cursor - /// position, add a new row and insert the byte. + /// Insert a byte at the current cursor position. If there is no row at the + /// current cursor position, add a new row and insert the byte. fn insert_byte(&mut self, c: u8) { if let Some(row) = self.rows.get_mut(self.cursor.y) { row.chars.insert(self.cursor.x, c); @@ -358,13 +372,15 @@ impl Editor { (self.cursor.x, self.n_bytes, self.dirty) = (self.cursor.x + 1, self.n_bytes + 1, true); } - /// Insert a new line at the current cursor position and move the cursor to the start of the new - /// line. If the cursor is in the middle of a row, split off that row. + /// Insert a new line at the current cursor position and move the cursor to + /// the start of the new line. If the cursor is in the middle of a row, + /// split off that row. fn insert_new_line(&mut self) { let (position, new_row_chars) = if self.cursor.x == 0 { (self.cursor.y, Vec::new()) } else { - // self.rows[self.cursor.y] must exist, since cursor.x = 0 for any cursor.y ≥ row.len() + // self.rows[self.cursor.y] must exist, since cursor.x = 0 for any cursor.y ≥ + // row.len() let new_chars = self.rows[self.cursor.y].chars.split_off(self.cursor.x); self.update_row(self.cursor.y, false); (self.cursor.y + 1, new_chars) @@ -376,13 +392,15 @@ impl Editor { self.dirty = true; } - /// Delete a character at the current cursor position. If the cursor is located at the beginning - /// of a row that is not the first or last row, merge the current row and the previous row. If - /// the cursor is located after the last row, move up to the last character of the previous row. + /// Delete a character at the current cursor position. If the cursor is + /// located at the beginning of a row that is not the first or last row, + /// merge the current row and the previous row. If the cursor is located + /// after the last row, move up to the last character of the previous row. fn delete_char(&mut self) { if self.cursor.x > 0 { let row = &mut self.rows[self.cursor.y]; - // Obtain the number of bytes to be removed: could be 1-4 (UTF-8 character size). + // Obtain the number of bytes to be removed: could be 1-4 (UTF-8 character + // size). let n_bytes_to_remove = row.get_char_size(row.cx2rx[self.cursor.x] - 1); row.chars.splice(self.cursor.x - n_bytes_to_remove..self.cursor.x, iter::empty()); self.update_row(self.cursor.y, false); @@ -400,8 +418,8 @@ impl Editor { self.update_screen_cols(); (self.dirty, self.cursor.y) = (self.dirty, self.cursor.y - 1); } else if self.cursor.y == self.rows.len() { - // If the cursor is located after the last row, pressing backspace is equivalent to - // pressing the left arrow key. + // If the cursor is located after the last row, pressing backspace is equivalent + // to pressing the left arrow key. self.move_cursor(&AKey::Left, false); } } @@ -442,35 +460,34 @@ impl Editor { self.update_screen_cols(); } - /// Try to load a file. If found, load the rows and update the render and syntax highlighting. - /// If not found, do not return an error. + /// Try to load a file. If found, load the rows and update the render and + /// syntax highlighting. If not found, do not return an error. fn load(&mut self, path: &Path) -> Result<(), Error> { - let ft = std::fs::metadata(path)?.file_type(); + let mut file = match File::open(path) { + Err(e) if e.kind() == ErrorKind::NotFound => { + self.rows.push(Row::new(Vec::new())); + return Ok(()); + } + r => r, + }?; + let ft = file.metadata()?.file_type(); if !(ft.is_file() || ft.is_symlink()) { return Err(io::Error::new(ErrorKind::InvalidInput, "Invalid input file type").into()); } - - match File::open(path) { - Ok(file) => { - for line in BufReader::new(file).split(b'\n') { - self.rows.push(Row::new(line?)); - } - // If the file ends with an empty line or is empty, we need to append an empty row - // to `self.rows`. Unfortunately, BufReader::split doesn't yield an empty Vec in - // this case, so we need to check the last byte directly. - let mut file = File::open(path)?; - file.seek(io::SeekFrom::End(0))?; - if file.bytes().next().transpose()?.map_or(true, |b| b == b'\n') { - self.rows.push(Row::new(Vec::new())); - } - self.update_all_rows(); - // The number of rows has changed. The left padding may need to be updated. - self.update_screen_cols(); - self.n_bytes = self.rows.iter().map(|row| row.chars.len() as u64).sum(); - } - Err(e) if e.kind() == ErrorKind::NotFound => self.rows.push(Row::new(Vec::new())), - Err(e) => return Err(e.into()), + for line in BufReader::new(&file).split(b'\n') { + self.rows.push(Row::new(line?)); } + // If the file ends with an empty line or is empty, we need to append an empty + // row to `self.rows`. Unfortunately, BufReader::split doesn't yield an + // empty Vec in this case, so we need to check the last byte directly. + file.seek(io::SeekFrom::End(0))?; + if file.bytes().next().transpose()?.map_or(true, |b| b == b'\n') { + self.rows.push(Row::new(Vec::new())); + } + self.update_all_rows(); + // The number of rows has changed. The left padding may need to be updated. + self.update_screen_cols(); + self.n_bytes = self.rows.iter().map(|row| row.chars.len() as u64).sum(); Ok(()) } @@ -482,7 +499,7 @@ impl Editor { file.write_all(&row.chars)?; written += row.chars.len(); if i != (self.rows.len() - 1) { - file.write_all(&[b'\n'])?; + file.write_all(b"\n")?; written += 1; } } @@ -490,8 +507,9 @@ impl Editor { Ok(written) } - /// Save the text to a file and handle all errors. Errors and success messages will be printed - /// to the status bar. Return whether the file was successfully saved. + /// Save the text to a file and handle all errors. Errors and success + /// messages will be printed to the status bar. Return whether the file + /// was successfully saved. fn save_and_handle_io_errors(&mut self, file_name: &str) -> bool { let saved = self.save(file_name); // Print error or success message to the status bar @@ -504,8 +522,9 @@ impl Editor { saved.is_ok() } - /// Save to a file after obtaining the file path from the prompt. If successful, the `file_name` - /// attribute of the editor will be set and syntax highlighting will be updated. + /// Save to a file after obtaining the file path from the prompt. If + /// successful, the `file_name` attribute of the editor will be set and + /// syntax highlighting will be updated. fn save_as(&mut self, file_name: String) -> Result<(), Error> { // TODO: What if file_name already exists? if self.save_and_handle_io_errors(&file_name) { @@ -526,11 +545,13 @@ impl Editor { Ok(()) } - /// Return whether the file being edited is empty or not. If there is more than one row, even if - /// all the rows are empty, `is_empty` returns `false`, since the text contains new lines. + /// Return whether the file being edited is empty or not. If there is more + /// than one row, even if all the rows are empty, `is_empty` returns + /// `false`, since the text contains new lines. fn is_empty(&self) -> bool { self.rows.len() <= 1 && self.n_bytes == 0 } - /// Draw rows of text and empty rows on the terminal, by adding characters to the buffer. + /// Draw rows of text and empty rows on the terminal, by adding characters + /// to the buffer. fn draw_rows(&self, buffer: &mut String) -> Result<(), Error> { let row_it = self.rows.iter().map(Some).chain(repeat(None)).enumerate(); for (i, row) in row_it.skip(self.cursor.roff).take(self.screen_rows) { @@ -571,7 +592,8 @@ impl Editor { Ok(()) } - /// Draw the message bar on the terminal, by adding characters to the buffer. + /// Draw the message bar on the terminal, by adding characters to the + /// buffer. fn draw_message_bar(&self, buffer: &mut String) { buffer.push_str(CLEAR_LINE_RIGHT_OF_CURSOR); let msg_duration = self.config.message_dur; @@ -580,8 +602,8 @@ impl Editor { } } - /// Refresh the screen: update the offsets, draw the rows, the status bar, the message bar, and - /// move the cursor to the correct position. + /// Refresh the screen: update the offsets, draw the rows, the status bar, + /// the message bar, and move the cursor to the correct position. fn refresh_screen(&mut self) -> Result<(), Error> { self.cursor.scroll(self.rx(), self.screen_rows, self.screen_cols); let mut buffer = format!("{HIDE_CURSOR}{MOVE_CURSOR_TO_START}"); @@ -589,33 +611,35 @@ impl Editor { self.draw_status_bar(&mut buffer)?; self.draw_message_bar(&mut buffer); let (cursor_x, cursor_y) = if self.prompt_mode.is_none() { - // If not in prompt mode, position the cursor according to the `cursor` attributes. + // If not in prompt mode, position the cursor according to the `cursor` + // attributes. (self.rx() - self.cursor.coff + 1 + self.ln_pad, self.cursor.y - self.cursor.roff + 1) } else { - // If in prompt mode, position the cursor on the prompt line at the end of the line. - (self.status_msg.as_ref().map_or(0, |sm| sm.msg.len() + 1), self.screen_rows + 2) + // If in prompt mode, position the cursor on the prompt line at the end of the + // line. + (self.status_msg.as_ref().map_or(0, |s| dsp_width(&s.msg)), self.screen_rows + 2) }; // Finally, print `buffer` and move the cursor print!("{buffer}\x1b[{cursor_y};{cursor_x}H{SHOW_CURSOR}"); io::stdout().flush().map_err(Error::from) } - /// Process a key that has been pressed, when not in prompt mode. Returns whether the program - /// should exit, and optionally the prompt mode to switch to. + /// Process a key that has been pressed, when not in prompt mode. Returns + /// whether the program should exit, and optionally the prompt mode to + /// switch to. fn process_keypress(&mut self, key: &Key) -> (bool, Option) { // This won't be mutated, unless key is Key::Character(EXIT) let mut quit_times = self.config.quit_times; let mut prompt_mode = None; match key { - // TODO: CtrlArrow should move to next word Key::Arrow(arrow) => self.move_cursor(arrow, false), Key::CtrlArrow(arrow) => self.move_cursor(arrow, true), - Key::Page(PageKey::Up) => { + Key::PageUp => { self.cursor.y = self.cursor.roff.saturating_sub(self.screen_rows); self.update_cursor_x_position(); } - Key::Page(PageKey::Down) => { + Key::PageDown => { self.cursor.y = (self.cursor.roff + 2 * self.screen_rows - 1).min(self.rows.len()); self.update_cursor_x_position(); } @@ -662,11 +686,12 @@ impl Editor { (false, prompt_mode) } - /// Try to find a query, this is called after pressing Ctrl-F and for each key that is pressed. - /// `last_match` is the last row that was matched, `forward` indicates whether to search forward - /// or backward. Returns the row of a new match, or `None` if the search was unsuccessful. + /// Try to find a query, this is called after pressing Ctrl-F and for each + /// key that is pressed. `last_match` is the last row that was matched, + /// `forward` indicates whether to search forward or backward. Returns + /// the row of a new match, or `None` if the search was unsuccessful. #[allow(clippy::trivially_copy_pass_by_ref)] // This Clippy recommendation is only relevant on 32 bit platforms. - fn find(&mut self, query: &str, last_match: &Option, forward: bool) -> Option { + fn find(&mut self, query: &str, last_match: Option, forward: bool) -> Option { let num_rows = self.rows.len(); let mut current = last_match.unwrap_or_else(|| num_rows.saturating_sub(1)); // TODO: Handle multiple matches per line @@ -674,11 +699,12 @@ impl Editor { current = (current + if forward { 1 } else { num_rows - 1 }) % num_rows; let row = &mut self.rows[current]; if let Some(cx) = slice_find(&row.chars, query.as_bytes()) { - // self.cursor.coff: Try to reset the column offset; if the match is after the offset, this - // will be updated in self.cursor.scroll() so that the result is visible + // self.cursor.coff: Try to reset the column offset; if the match is after the + // offset, this will be updated in self.cursor.scroll() so that + // the result is visible (self.cursor.x, self.cursor.y, self.cursor.coff) = (cx, current, 0); let rx = row.cx2rx[cx]; - row.match_segment = Some(rx..rx + query.len()); + row.match_segment = Some(rx..rx + UnicodeWidthStr::width(query)); return Some(current); } } @@ -700,7 +726,7 @@ impl Editor { self.file_name = None; } loop { - if let Some(mode) = self.prompt_mode.as_ref() { + if let Some(mode) = &self.prompt_mode { set_status!(self, "{}", mode.status_msg()); } self.refresh_screen()?; @@ -719,13 +745,14 @@ impl Editor { } impl Drop for Editor { + #[allow(clippy::expect_used)] /// When the editor is dropped, restore the original terminal mode. fn drop(&mut self) { if let Some(orig_term_mode) = self.orig_term_mode.take() { sys::set_term_mode(&orig_term_mode).expect("Could not restore original terminal mode."); } if !thread::panicking() { - print!("{CLEAR_SCREEN}{MOVE_CURSOR_TO_START}"); + print!("{USE_MAIN_SCREEN}"); io::stdout().flush().expect("Could not flush stdout"); } } @@ -743,8 +770,8 @@ enum PromptMode { Execute(String), } -// TODO: Use trait with mode_status_msg and process_keypress, implement the trait for separate -// structs for Save and Find? +// TODO: Use trait with mode_status_msg and process_keypress, implement the +// trait for separate structs for Save and Find? impl PromptMode { /// Return the status message to print for the selected `PromptMode`. fn status_msg(&self) -> String { @@ -771,13 +798,14 @@ impl PromptMode { } match process_prompt_keypress(b, key) { PromptState::Active(query) => { + #[allow(clippy::wildcard_enum_match_arm)] let (last_match, forward) = match key { Key::Arrow(AKey::Right | AKey::Down) | Key::Char(FIND) => (last_match, true), Key::Arrow(AKey::Left | AKey::Up) => (last_match, false), _ => (None, true), }; - let curr_match = ed.find(&query, &last_match, forward); + let curr_match = ed.find(&query, last_match, forward); return Ok(Some(Self::Find(query, saved_cursor, curr_match))); } // The prompt was cancelled. Restore the previous position. @@ -837,18 +865,23 @@ enum PromptState { Cancelled, } +thread_local! (static CHARACTER: RefCell> = {let cache = Vec::new(); RefCell::new(cache)}); /// Process a prompt keypress event and return the new state for the prompt. fn process_prompt_keypress(mut buffer: String, key: &Key) -> PromptState { + #[allow(clippy::wildcard_enum_match_arm)] match key { Key::Char(b'\r') => return PromptState::Completed(buffer), Key::Escape | Key::Char(EXIT) => return PromptState::Cancelled, - Key::Char(BACKSPACE | DELETE_BIS) => { - buffer.pop(); - } + Key::Char(BACKSPACE | DELETE_BIS) => _ = buffer.pop(), Key::Char(c @ 0..=126) if !c.is_ascii_control() => buffer.push(*c as char), + Key::Char(c @ 128..=255) => CHARACTER.with(|cache| cache.borrow_mut().push(*c)), // No-op _ => (), } + let character = CHARACTER.with(|cache| String::from_utf8(cache.borrow_mut().clone())); + let _ = character.clone().map_or((), |c| buffer.push_str(c.as_str())); + let _ = character.map_or((), |_| CHARACTER.with(|cache| cache.borrow_mut().clear())); + PromptState::Active(buffer) } @@ -917,12 +950,12 @@ mod tests { editor.insert_byte(*b); } editor.delete_char(); - assert_eq!(editor.rows[0].chars, "Hello world".as_bytes()); + assert_eq!(editor.rows[0].chars, b"Hello world"); editor.move_cursor(&AKey::Left, true); editor.move_cursor(&AKey::Left, false); editor.move_cursor(&AKey::Left, false); editor.delete_char(); - assert_eq!(editor.rows[0].chars, "Helo world".as_bytes()); + assert_eq!(editor.rows[0].chars, b"Helo world"); } #[test] @@ -1084,4 +1117,70 @@ mod tests { assert_eq!(editor.cursor.x, 0); assert_eq!(editor.cursor.y, 3); } + + #[test] + fn editor_press_home_key() { + let mut editor = Editor::default(); + for b in b"Hello\nWorld\nand\nFerris!" { + if *b == b'\n' { + editor.insert_new_line(); + } else { + editor.insert_byte(*b); + } + } + + // check current position + assert_eq!(editor.cursor.x, 7); + assert_eq!(editor.cursor.y, 3); + + editor.process_keypress(&Key::Home); + assert_eq!(editor.cursor.x, 0); + assert_eq!(editor.cursor.y, 3); + + editor.move_cursor(&AKey::Up, false); + editor.move_cursor(&AKey::Up, false); + editor.move_cursor(&AKey::Up, false); + + assert_eq!(editor.cursor.x, 0); + assert_eq!(editor.cursor.y, 0); + + editor.move_cursor(&AKey::Right, true); + assert_eq!(editor.cursor.x, 5); + assert_eq!(editor.cursor.y, 0); + + editor.process_keypress(&Key::Home); + assert_eq!(editor.cursor.x, 0); + assert_eq!(editor.cursor.y, 0); + } + + #[test] + fn editor_press_end_key() { + let mut editor = Editor::default(); + for b in b"Hello\nWorld\nand\nFerris!" { + if *b == b'\n' { + editor.insert_new_line(); + } else { + editor.insert_byte(*b); + } + } + + // check current position + assert_eq!(editor.cursor.x, 7); + assert_eq!(editor.cursor.y, 3); + + editor.process_keypress(&Key::End); + assert_eq!(editor.cursor.x, 7); + assert_eq!(editor.cursor.y, 3); + + editor.move_cursor(&AKey::Up, false); + editor.move_cursor(&AKey::Up, false); + editor.move_cursor(&AKey::Up, false); + + assert_eq!(editor.cursor.x, 3); + assert_eq!(editor.cursor.y, 0); + + editor.process_keypress(&Key::End); + assert_eq!(editor.cursor.x, 5); + assert_eq!(editor.cursor.y, 0); + } } diff --git a/src/error.rs b/src/error.rs index 3745239a..333c3c2f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,15 +7,16 @@ pub enum Error { Io(std::io::Error), /// Wrapper around `std::fmt::Error` Fmt(std::fmt::Error), - /// Error returned when the window size obtained through a system call is invalid. + /// Error returned when the window size obtained through a system call is + /// invalid. InvalidWindowSize, /// Error setting or retrieving the cursor position. CursorPosition, - /// Configuration error. The three attributes correspond the file path, the line number and the - /// error message. + /// Configuration error. The three attributes correspond the file path, the + /// line number and the error message. Config(std::path::PathBuf, usize, String), - /// Too many arguments given to kibi. The attribute corresponds to the total number of command - /// line arguments. + /// Too many arguments given to kibi. The attribute corresponds to the total + /// number of command line arguments. TooManyArguments(usize), /// Unrecognized option given as a command line argument. UnrecognizedOption(String), diff --git a/src/lib.rs b/src/lib.rs index 50740b0c..96a7c817 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,13 +12,9 @@ mod row; mod syntax; mod terminal; -#[cfg(windows)] mod windows; -#[cfg(windows)] use windows as sys; +#[cfg_attr(windows, path = "windows.rs")] +#[cfg_attr(unix, path = "unix.rs")] +#[cfg_attr(target_os = "wasi", path = "wasi.rs")] +mod sys; -#[cfg(unix)] mod unix; -#[cfg(unix)] mod xdg; -#[cfg(unix)] use unix as sys; - -#[cfg(target_os = "wasi")] mod wasi; -#[cfg(target_os = "wasi")] mod xdg; -#[cfg(target_os = "wasi")] use wasi as sys; +#[cfg(any(unix, target_os = "wasi"))] mod xdg; diff --git a/src/main.rs b/src/main.rs index 9dd0a0c5..99b15e2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,15 +2,16 @@ use kibi::{Config, Editor, Error}; -/// Load the configuration, initialize the editor and run the program, optionally opening a file if -/// an argument is given. +/// Load the configuration, initialize the editor and run the program, +/// optionally opening a file if an argument is given. /// /// # Errors /// -/// Any error that occur during the execution of the program will be returned by this function. +/// Any error that occur during the execution of the program will be returned by +/// this function. fn main() -> Result<(), Error> { let mut args = std::env::args(); - match (args.nth(1), /*remaining_args=*/ args.len()) { + match (args.nth(1), /* remaining_args= */ args.len()) { (Some(arg), 0) if arg == "--version" => println!("kibi {}", env!("KIBI_VERSION")), (Some(arg), 0) if arg.starts_with('-') => return Err(Error::UnrecognizedOption(arg)), (file_name, 0) => Editor::new(Config::load()?)?.run(&file_name)?, diff --git a/src/row.rs b/src/row.rs index fb853a6e..f7cbe13d 100644 --- a/src/row.rs +++ b/src/row.rs @@ -1,7 +1,7 @@ //! # Row //! -//! Utilities for rows. A `Row` owns the underlying characters, the rendered string and the syntax -//! highlighting information. +//! Utilities for rows. A `Row` owns the underlying characters, the rendered +//! string and the syntax highlighting information. use std::{fmt::Write, iter::repeat}; @@ -19,7 +19,8 @@ pub enum HlState { Normal, /// A multi-line comment has been open, but not yet closed. MultiLineComment, - /// A string has been open with the given quote character (for instance b'\'' or b'"'), but not yet closed. + /// A string has been open with the given quote character (for instance + /// b'\'' or b'"'), but not yet closed. String(u8), /// A multi-line string has been open, but not yet closed. MultiLineString, @@ -30,18 +31,22 @@ pub enum HlState { pub struct Row { /// The characters of the row. pub chars: Vec, - /// How the characters are rendered. In particular, tabs are converted into several spaces, and - /// bytes may be combined into single UTF-8 characters. + /// How the characters are rendered. In particular, tabs are converted into + /// several spaces, and bytes may be combined into single UTF-8 + /// characters. render: String, - /// Mapping from indices in `self.chars` to the corresponding indices in `self.render`. + /// Mapping from indices in `self.chars` to the corresponding indices in + /// `self.render`. pub cx2rx: Vec, - /// Mapping from indices in `self.render` to the corresponding indices in `self.chars`. + /// Mapping from indices in `self.render` to the corresponding indices in + /// `self.chars`. pub rx2cx: Vec, /// The vector of `HLType` for each rendered character. hl: Vec, /// The final state of the row. pub hl_state: HlState, - /// If not `None`, the range that is currently matched during a FIND operation. + /// If not `None`, the range that is currently matched during a FIND + /// operation. pub match_segment: Option>, } @@ -59,17 +64,17 @@ impl Row { // The number of rendered characters let n_rend_chars = if c == '\t' { tab - (rx % tab) } else { c.width().unwrap_or(1) }; self.render.push_str(&(if c == '\t' { " ".repeat(n_rend_chars) } else { c.into() })); - self.cx2rx.extend(std::iter::repeat(rx).take(c.len_utf8())); - self.rx2cx.extend(std::iter::repeat(cx).take(n_rend_chars)); + self.cx2rx.extend(repeat(rx).take(c.len_utf8())); + self.rx2cx.extend(repeat(cx).take(n_rend_chars)); (rx, cx) = (rx + n_rend_chars, cx + c.len_utf8()); } let (..) = (self.cx2rx.push(rx), self.rx2cx.push(cx)); self.update_syntax(syntax, hl_state) } - /// Obtain the character size, in bytes, given its position in `self.render`. This is done in - /// constant time by using the difference between `self.rx2cx[rx]` and the cx for the next - /// character. + /// Obtain the character size, in bytes, given its position in + /// `self.render`. This is done in constant time by using the difference + /// between `self.rx2cx[rx]` and the cx for the next character. pub fn get_char_size(&self, rx: usize) -> usize { let cx0 = self.rx2cx[rx]; self.rx2cx.iter().skip(rx + 1).map(|cx| cx - cx0).find(|d| *d > 0).unwrap_or(1) @@ -80,14 +85,14 @@ impl Row { self.hl.clear(); let line = self.render.as_bytes(); - // Delimiters for multi-line comments and multi-line strings, as Option<&String, &String> + // Delimiters for multi-line comments and multi-line strings, as Option<&String, + // &String> let ml_comment_delims = syntax.ml_comment_delims.as_ref().map(|(start, end)| (start, end)); let ml_string_delims = syntax.ml_string_delim.as_ref().map(|x| (x, x)); 'syntax_loop: while self.hl.len() < line.len() { let i = self.hl.len(); - let find_str = - |s: &str| line.get(i..(i + s.len())).map_or(false, |r| r.eq(s.as_bytes())); + let find_str = |s: &str| line.get(i..(i + s.len())).is_some_and(|r| r.eq(s.as_bytes())); if hl_state == HlState::Normal && syntax.sl_comment_start.iter().any(|s| find_str(s)) { self.hl.extend(repeat(HlType::Comment).take(line.len() - i)); @@ -167,9 +172,10 @@ impl Row { self.hl_state } - /// Draw the row and write the result to a buffer. An `offset` can be given, as well as a limit - /// on the length of the row (`max_len`). After writing the characters, clear the rest of the - /// line and move the cursor to the start of the next line. + /// Draw the row and write the result to a buffer. An `offset` can be given, + /// as well as a limit on the length of the row (`max_len`). After + /// writing the characters, clear the rest of the line and move the + /// cursor to the start of the next line. pub fn draw(&self, offset: usize, max_len: usize, buffer: &mut String) -> Result<(), Error> { let mut current_hl_type = HlType::Normal; let chars = self.render.chars().skip(offset).take(max_len); @@ -206,6 +212,6 @@ impl Row { } /// Return whether `c` is an ASCII separator. -fn is_sep(c: u8) -> bool { +const fn is_sep(c: u8) -> bool { c.is_ascii_whitespace() || c == b'\0' || (c.is_ascii_punctuation() && c != b'_') } diff --git a/src/syntax.rs b/src/syntax.rs index 65dc106c..f33949b1 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -2,13 +2,13 @@ use std::fmt::{self, Display, Formatter}; use std::path::{Path, PathBuf}; use crate::config::{self, parse_value as pv, parse_values as pvs}; -use crate::{sys, Error}; +use crate::{Error, sys}; /// Type of syntax highlighting for a single rendered character. /// -/// Each `HLType` is associated with a color, via its discriminant. The ANSI color is equal -/// to the discriminant, modulo 100. The colors are described here: -/// +/// Each `HLType` is associated with a color, via its discriminant. The ANSI +/// color is equal to the discriminant, modulo 100. The colors are described +/// here: #[derive(PartialEq, Eq, Copy, Clone)] pub enum HlType { Normal = 39, // Default foreground color @@ -23,7 +23,8 @@ pub enum HlType { } impl Display for HlType { - /// Write the ANSI color escape sequence for the `HLType` using the given formatter. + /// Write the ANSI color escape sequence for the `HLType` using the given + /// formatter. fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "\x1b[{}m", (*self as u32) % 100) } } @@ -40,16 +41,17 @@ pub struct Conf { pub sl_comment_start: Vec, /// The tokens that start and end a multi-line comment, e.g. ("/*", "*/"). pub ml_comment_delims: Option<(String, String)>, - /// The token that start and end a multi-line strings, e.g. "\"\"\"" for Python. + /// The token that start and end a multi-line strings, e.g. "\"\"\"" for + /// Python. pub ml_string_delim: Option, - /// Keywords to highlight and there corresponding HLType (typically - /// HLType::Keyword1 or HLType::Keyword2) + /// Keywords to highlight and there corresponding `HLType` (typically + /// `HLType::Keyword1` or `HLType::Keyword2`) pub keywords: Vec<(HlType, Vec)>, } impl Conf { - /// Return the syntax configuration corresponding to the given file extension, if a matching - /// INI file is found in a config directory. + /// Return the syntax configuration corresponding to the given file + /// extension, if a matching INI file is found in a config directory. pub fn get(ext: &str) -> Result, Error> { for conf_dir in sys::data_dirs() { match PathBuf::from(conf_dir).join("syntax.d").read_dir() { @@ -66,6 +68,7 @@ impl Conf { } Ok(None) } + /// Load a `SyntaxConf` from file. pub fn from_file(path: &Path) -> Result<(Self, Vec), Error> { let (mut sc, mut extensions) = (Self::default(), Vec::new()); diff --git a/src/terminal.rs b/src/terminal.rs index 803634c3..b09f2a91 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,19 +1,21 @@ use std::io::{self, BufRead, Read, Write}; -use crate::{ansi_escape::DEVICE_STATUS_REPORT, ansi_escape::REPOSITION_CURSOR_END, sys, Error}; +use crate::{Error, ansi_escape::DEVICE_STATUS_REPORT, ansi_escape::REPOSITION_CURSOR_END, sys}; /// Obtain the window size using the cursor position. /// -/// This function moves the cursor to the bottom-right using ANSI escape sequence -/// `\x1b[999C\x1b[999B`, then requests the cursor position using ANSI escape sequence `\x1b[6n`. -/// After this sequence is sent, the next characters on stdin should be `\x1b[{row};{column}R`. +/// This function moves the cursor to the bottom-right using ANSI escape +/// sequence `\x1b[999C\x1b[999B`, then requests the cursor position using ANSI +/// escape sequence `\x1b[6n`. After this sequence is sent, the next characters +/// on stdin should be `\x1b[{row};{column}R`. /// -/// It is used as an alternative method if `sys::get_window_size()` returns an error. +/// It is used as an alternative method if `sys::get_window_size()` returns an +/// error. pub fn get_window_size_using_cursor() -> Result<(usize, usize), Error> { let mut stdin = sys::stdin()?; print!("{REPOSITION_CURSOR_END}{DEVICE_STATUS_REPORT}"); io::stdout().flush()?; - let mut prefix_buffer = [0_u8; 2]; + let mut prefix_buffer = [0u8; 2]; stdin.read_exact(&mut prefix_buffer)?; if prefix_buffer != [b'\x1b', b'['] { return Err(Error::CursorPosition); @@ -21,7 +23,8 @@ pub fn get_window_size_using_cursor() -> Result<(usize, usize), Error> { Ok((read_value_until(b';')?, read_value_until(b'R')?)) } -/// Read value until a certain stop byte is reached, and parse the result (pre-stop byte). +/// Read value until a certain stop byte is reached, and parse the result +/// (pre-stop byte). fn read_value_until(stop_byte: u8) -> Result { let mut buf = Vec::new(); io::stdin().lock().read_until(stop_byte, &mut buf)?; diff --git a/src/unix.rs b/src/unix.rs index a73cc835..3019be44 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -1,16 +1,18 @@ //! # sys (UNIX) //! -//! UNIX-specific structs and functions. Will be imported as `sys` on UNIX systems. +//! UNIX-specific structs and functions. Will be imported as `sys` on UNIX +//! systems. +#![allow(unsafe_code)] use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; // On UNIX systems, termios represents the terminal mode. pub use libc::termios as TermMode; -use libc::{c_int, c_void, sigaction, sighandler_t, siginfo_t, winsize}; use libc::{SA_SIGINFO, STDIN_FILENO, STDOUT_FILENO, TCSADRAIN, TIOCGWINSZ, VMIN, VTIME}; +use libc::{c_int, c_void, sigaction, sighandler_t, siginfo_t, winsize}; -pub use crate::xdg::*; use crate::Error; +pub use crate::xdg::*; fn cerr(err: c_int) -> Result<(), Error> { match err { @@ -21,8 +23,8 @@ fn cerr(err: c_int) -> Result<(), Error> { /// Return the current window size as (rows, columns). /// -/// We use the `TIOCGWINSZ` ioctl to get window size. If it succeeds, a `Winsize` struct will be -/// populated. +/// We use the `TIOCGWINSZ` ioctl to get window size. If it succeeds, a +/// `Winsize` struct will be populated. /// This ioctl is described here: pub fn get_window_size() -> Result<(usize, usize), Error> { let mut maybe_ws = std::mem::MaybeUninit::::uninit(); @@ -32,28 +34,32 @@ pub fn get_window_size() -> Result<(usize, usize), Error> { .map_or(Err(Error::InvalidWindowSize), |ws| Ok((ws.ws_row as usize, ws.ws_col as usize))) } -/// Stores whether the window size has changed since last call to `has_window_size_changed`. +/// Stores whether the window size has changed since last call to +/// `has_window_size_changed`. static WSC: AtomicBool = AtomicBool::new(false); /// Handle a change in window size. extern "C" fn handle_wsize(_: c_int, _: *mut siginfo_t, _: *mut c_void) { WSC.store(true, Relaxed) } -/// Register a signal handler that sets a global variable when the window size changes. -/// After calling this function, use `has_window_size_changed` to query the global variable. +#[allow(clippy::fn_to_numeric_cast_any)] +/// Register a signal handler that sets a global variable when the window size +/// changes. After calling this function, use `has_window_size_changed` to query +/// the global variable. pub fn register_winsize_change_signal_handler() -> Result<(), Error> { unsafe { let mut maybe_sa = std::mem::MaybeUninit::::uninit(); cerr(libc::sigemptyset(&mut (*maybe_sa.as_mut_ptr()).sa_mask))?; - // We could use sa_handler here, however, sigaction defined in libc does not have - // sa_handler field, so we use sa_sigaction instead. + // We could use sa_handler here, however, sigaction defined in libc does not + // have sa_handler field, so we use sa_sigaction instead. (*maybe_sa.as_mut_ptr()).sa_flags = SA_SIGINFO; (*maybe_sa.as_mut_ptr()).sa_sigaction = handle_wsize as sighandler_t; - cerr(libc::sigaction(libc::SIGWINCH, maybe_sa.as_ptr(), std::ptr::null_mut())) + cerr(sigaction(libc::SIGWINCH, maybe_sa.as_ptr(), std::ptr::null_mut())) } } /// Check if the windows size has changed since the last call to this function. -/// The `register_winsize_change_signal_handler` needs to be called before this function. +/// The `register_winsize_change_signal_handler` needs to be called before this +/// function. pub fn has_window_size_changed() -> bool { WSC.swap(false, Relaxed) } /// Set the terminal mode. diff --git a/src/wasi.rs b/src/wasi.rs index 02fd1b04..545ea6e3 100644 --- a/src/wasi.rs +++ b/src/wasi.rs @@ -1,32 +1,34 @@ //! # sys (WASI) //! -//! WASI-specific structs and functions. Will be imported as `sys` on WASI systems. +//! WASI-specific structs and functions. Will be imported as `sys` on WASI +//! systems. -pub use crate::xdg::*; use crate::Error; +pub use crate::xdg::*; -pub struct TermMode {} +pub struct TermMode; /// Return the current window size as (rows, columns). -/// By returning an error we cause kibi to fall back to another method of getting the window size -pub fn get_window_size() -> Result<(usize, usize), Error> { Err(Error::InvalidWindowSize) } +/// By returning an error we cause kibi to fall back to another method of +/// getting the window size +pub const fn get_window_size() -> Result<(usize, usize), Error> { Err(Error::InvalidWindowSize) } -/// Register a signal handler that sets a global variable when the window size changes. On WASI -/// platforms, this does nothing. +/// Register a signal handler that sets a global variable when the window size +/// changes. On WASI platforms, this does nothing. #[allow(clippy::unnecessary_wraps)] // Result required on other platforms -pub fn register_winsize_change_signal_handler() -> Result<(), Error> { Ok(()) } +pub const fn register_winsize_change_signal_handler() -> Result<(), Error> { Ok(()) } -/// Check if the windows size has changed since the last call to this function. On WASI platforms, -/// this always return false. -pub fn has_window_size_changed() -> bool { false } +/// Check if the windows size has changed since the last call to this function. +/// On WASI platforms, this always return false. +pub const fn has_window_size_changed() -> bool { false } /// Set the terminal mode. On WASI platforms, this does nothing. #[allow(clippy::unnecessary_wraps)] // Result required on other platforms -pub fn set_term_mode(_term: &TermMode) -> Result<(), Error> { Ok(()) } +pub const fn set_term_mode(_term: &TermMode) -> Result<(), Error> { Ok(()) } // Opening the file /dev/tty is effectively the same as `raw_mode` #[allow(clippy::unnecessary_wraps)] // Result required on other platforms -pub fn enable_raw_mode() -> Result { Ok(TermMode {}) } +pub const fn enable_raw_mode() -> Result { Ok(TermMode {}) } pub fn stdin() -> std::io::Result { std::fs::File::open("/dev/tty") } diff --git a/src/windows.rs b/src/windows.rs index c8e7aa2c..749baafc 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,18 +1,19 @@ //! # sys (Windows) //! -//! Windows-specific structs and functions. Will be imported as `sys` on Windows systems. +//! Windows-specific structs and functions. Will be imported as `sys` on Windows +//! systems. #![allow(clippy::wildcard_imports)] use std::{convert::TryInto, env::var, io}; use winapi::um::wincon::*; -use winapi_util::{console as cons, HandleRef}; +use winapi_util::{HandleRef, console as cons}; use crate::Error; -// On Windows systems, the terminal mode is represented as 2 unsigned integers (one for stdin, one -// for stdout). +// On Windows systems, the terminal mode is represented as 2 unsigned integers +// (one for stdin, one for stdout). pub type TermMode = (u32, u32); /// Return configuration directories for Windows systems @@ -61,6 +62,6 @@ pub fn enable_raw_mode() -> Result { } #[allow(clippy::unnecessary_wraps)] // Result required on other platforms -pub fn stdin() -> std::io::Result { Ok(std::io::stdin()) } +pub fn stdin() -> io::Result { Ok(io::stdin()) } pub fn path(filename: &str) -> std::path::PathBuf { std::path::PathBuf::from(filename) } diff --git a/src/xdg.rs b/src/xdg.rs index b75effcb..069bcb36 100644 --- a/src/xdg.rs +++ b/src/xdg.rs @@ -6,19 +6,20 @@ use std::env::var; /// /// The XDG Base Directory Specification is defined here: /// -pub(crate) fn xdg_dirs(xdg_type: &str, def_home_suffix: &str, def_dirs: &str) -> Vec { +pub fn xdg_dirs(xdg_type: &str, def_home_suffix: &str, def_dirs: &str) -> Vec { let (home_key, dirs_key) = (format!("XDG_{xdg_type}_HOME"), format!("XDG_{xdg_type}_DIRS")); let mut dirs = Vec::new(); - // If environment variable `home_key` (e.g. `$XDG_CONFIG_HOME`) is set, add its value to `dirs`. - // Otherwise, if environment variable `$HOME` is set, add `$HOME{def_home_suffix}` - // (e.g. `$HOME/.config`) to `dirs`. + // If environment variable `home_key` (e.g. `$XDG_CONFIG_HOME`) is set, add its + // value to `dirs`. Otherwise, if environment variable `$HOME` is set, add + // `$HOME{def_home_suffix}` (e.g. `$HOME/.config`) to `dirs`. dirs.extend(var(home_key).or_else(|_| var("HOME").map(|d| d + def_home_suffix))); - // If environment variable `dirs_key` (e.g. `XDG_CONFIG_DIRS`) is set, split by `:` and add the - // parts to `dirs`. - // Otherwise, add the split `def_dirs` (e.g. `/etc/xdg:/etc`) and add the parts to `dirs`. + // If environment variable `dirs_key` (e.g. `XDG_CONFIG_DIRS`) is set, split by + // `:` and add the parts to `dirs`. + // Otherwise, add the split `def_dirs` (e.g. `/etc/xdg:/etc`) and add the parts + // to `dirs`. dirs.extend(var(dirs_key).unwrap_or_else(|_| def_dirs.into()).split(':').map(String::from)); dirs.into_iter().map(|p| p + "/kibi").collect() diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000..86923fbc --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" +description = "Scripts to help with kibi's development." +license = "MIT OR Apache-2.0" +repository = "https://github.com/ilai-deutel/kibi" +readme = "README.md" +keywords = ["scripts"] +categories = ["development-tools"] + +[dependencies] +anstream = "0.6.18" +anstyle = "1.0.10" +clap = { version = "4.5.21", features = ["derive"] } +glob = "0.3.1" +regex = { version = "1.11.1" } +tokei = "12.1.2" + +[lints] +workspace = true diff --git a/xtask/README.md b/xtask/README.md new file mode 100644 index 00000000..a0dc0552 --- /dev/null +++ b/xtask/README.md @@ -0,0 +1,13 @@ +# kibi xtask scripts + +Scripts to help with kibi's development. + +## count_loc + +Verify that the number of source code lines falls below the limit. + +Usage: + +```bash +cargo xtask count_loc +``` \ No newline at end of file diff --git a/xtask/src/count_loc.rs b/xtask/src/count_loc.rs new file mode 100644 index 00000000..e96a2da0 --- /dev/null +++ b/xtask/src/count_loc.rs @@ -0,0 +1,69 @@ +use std::path::{Path, PathBuf}; + +use anstream::println; +use glob::glob; +use regex::Regex; +use tokei::LanguageType; + +use crate::{BOLD, GREEN, RED, RESET, Result}; + +pub fn count_loc() -> Result<()> { + let mut results = Vec::new(); + let config = tokei::Config::default(); + let mut longest_path = 0; + for entry in glob("./*.rs")?.chain(glob("./src/**/*.rs")?) { + let path = entry?; + + let source = filter_lines(&path)?; + let stats = LanguageType::Rust.parse_from_str(source, &config); + + longest_path = longest_path.max(path.display().to_string().len()); + results.push((path, stats.code)); + } + print_summary(&results, longest_path, &["unix", "wasi", "windows"]) +} + +/// Filter out lines that contain lints and anything after +/// `#[cfg(test)]` attributes. +pub fn filter_lines(path: &Path) -> Result { + let regex = Regex::new(r"^\s*#!?\[(?:allow|warn|deny)\(")?; + let content = std::fs::read_to_string(path)?; + let lines = content + .lines() + .filter(|line| !regex.is_match(line)) + .take_while(|line| !line.contains("#[cfg(test)]")); + let filtered_content = lines.collect::>().join("\n"); + Ok(filtered_content) +} + +pub fn print_summary(results: &[(PathBuf, usize)], width: usize, platforms: &[&str]) -> Result<()> { + let platform_counts = platforms.iter().map(|platform| filter_count(results, platform)); + let other_count = results.iter().map(|(_, count)| count).sum::() + - platform_counts.clone().sum::(); + for (path, count) in results { + println!("{:width$} {:4}", path.display(), count, width = width); + } + let mut too_high = false; + for (platform, platform_count) in platforms.iter().zip(platform_counts) { + let header = format!("Total ({platform})"); + let total = platform_count + other_count; + if total > 1024 { + too_high = true; + println!("{BOLD}{header:width$} {total:4}{RESET} {RED}(> 1024){RESET}"); + } else { + println!("{BOLD}{header:width$} {total:4}{RESET} {GREEN}(≤ 1024){RESET}"); + } + } + if too_high { + Err("Total count is too high")?; + } + Ok(()) +} + +pub fn filter_count(results: &[(PathBuf, usize)], pattern: &str) -> usize { + results + .iter() + .filter(|(path, _)| path.display().to_string().contains(pattern)) + .map(|(_, count)| count) + .sum::() +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000..0c3692cc --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,43 @@ +#![allow(clippy::multiple_crate_versions)] +use std::process::ExitCode; + +use anstyle::{AnsiColor, Reset, Style}; +use clap::{Parser, Subcommand}; +mod count_loc; + +type Result> = std::result::Result; + +const BOLD: Style = Style::new().bold(); +const RESET: Reset = Reset; +const RED: Style = AnsiColor::Red.on_default(); +const GREEN: Style = AnsiColor::Green.on_default(); + +fn main() -> ExitCode { + let args = Args::parse(); + match args.command.execute() { + Ok(()) => ExitCode::SUCCESS, + Err(err) => { + eprintln!("{RED}{err}{RESET}"); + ExitCode::FAILURE + } + } +} + +#[derive(Debug, Parser)] +struct Args { + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +enum Command { + CountLoc, +} + +impl Command { + fn execute(&self) -> Result<()> { + match self { + Self::CountLoc => count_loc::count_loc(), + } + } +}