diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml deleted file mode 100644 index 563233f..0000000 --- a/.github/workflows/clippy.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Clippy Check - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - clippy: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macOS-latest, ubuntu-latest] - steps: - - uses: actions/checkout@v4 - - uses: actions-rs/toolchain@v1 - with: - components: clippy - toolchain: stable - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b86e39..64c897f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,11 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable + run: rustup update stable && rustup default stable - uses: katyo/publish-crates@v2 with: registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ee47a77..2e74629 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,9 @@ name: leetcode-cli on: push: - branches: [master] + branches: [main] pull_request: - branches: [master] + branches: [main] jobs: build: @@ -14,20 +14,22 @@ jobs: os: [macOS-latest, ubuntu-latest] steps: - name: Checkout the source code - uses: actions/checkout@v4 - - name: Set nightly toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - name: Environment - run: | - if [[ "$(uname)" == 'Darwin' ]]; then - brew install sqlite3 - else - sudo apt-get update -y - sudo apt-get install -y libsqlite3-dev libdbus-1-dev - fi - - name: Build - run: cargo build --release --all-features + uses: actions/checkout@v6 + - name: Install Toolchain + run: rustup update stable && rustup default stable + - uses: taiki-e/install-action@sccache + - uses: taiki-e/install-action@nextest - name: Run tests - run: cargo test --release --all-features + run: cargo nextest run --release --all-features + check: + runs-on: ubuntu-latest + steps: + - name: Checkout the source code + uses: actions/checkout@v6 + - name: Install Toolchain + run: rustup update stable && rustup default stable + - uses: taiki-e/install-action@sccache + - name: Run format check + run: cargo fmt --check + - name: Run clippy + run: cargo clippy --all-features -- -D warnings diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index cb532f8..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,171 +0,0 @@ -## v0.4.1 - -- Search problems by name -- Re-enable chrome plugin - -## v0.3.3 - -- allow more flexible categories by @frrad -- change params type to `Option` @frrad - -## v0.3.2 - -- adds additional tracing by @shmuga -- removes test_mode parameter by @shmuga - -## v0.3.1 - -- pipe handling by @aymanbagabas -- Improve README by @xiaoxiae - -## v0.3.0 - -- Upgrade reqwest to async mode -- Format code using clippy - -## v0.2.23 - -- support color display - -## v0.2.22 - -- Fixed the cache can't update with new added problems - -- Display user friendly errors when pick/edit new added problem. - -- upgrade pyo3 - -- fix leetcode list with empty cache - -## v0.2.21 - -- Make programmable support to be an advanced feature - -## v0.2.20 - -- Support sup/sub style for numbers - -## v0.2.19 - -- Better HTML! - -## v0.2.18 - -- Display stdout for test and execute commands, fix minor spacing in results displayed - -- Fix panic on `pick` command without cache - -## v0.2.17 - -Fix panic on stat command with zero numbers - -## v0.2.16 - -Update versions of diesel and reqwest - -## v0.2.15 - -Allow for custom testcases with the `leetcode test` command, and some minor edits - -## v0.2.14 - -Corrects file suffixes for c\*\* and c# files - -## v0.2.13 - -fix percent length panic - -## v0.2.12 - -fix gt || ge || lt || le - -## v0.2.11 - -added code 14 and transfered `&#ge;`、`&#le` and `'`. - -## v0.2.10 - -add code 15 - -## v0.2.9 - -update ac status after submit successfully - -## v0.2.8 - -show last testcases - -## v0.2.7 - -fixed float bug in result - -## v0.2.6 - -sync config while change current lang - -## v0.2.5 - -update local cache when submission status changes - -## v0.2.4 - -auto fetch question while exec `edit` directly. - -## v0.2.3 - -Programmable leetcode-cli - -## v0.2.2 - -1. optimize logs -2. add tag filter -3. sync configs - -## v0.2.1 - -1. fix cookies error handling -2. dismiss all `unwrap`, `expect` and `panic` -3. add cookie configs - -## v0.2.0 - -1. Add Linux Support - -## v0.1.9 - -1. release submit command -2. deserialize json using outter funcs - -## v0.1.8 - -1. pack mod exports -2. add edit command -3. add test command - -## v0.1.7 - -render html in command-line, and `pick` command - -## v0.1.6 - -complete `stat` command - -## v0.1.5 - -complete `cache` command - -## v0.1.3 - -complete `list` command - -## v0.1.2 - -abstract data cache - -## v0.1.1 - -add list command - -## v0.1.0 - -chrome cookie diff --git a/Cargo.lock b/Cargo.lock index 1f006e7..10672ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,27 +2,23 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -39,7 +35,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", - "anstyle-parse", + "anstyle-parse 0.2.5", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse 1.0.0", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -49,9 +60,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -62,6 +73,15 @@ dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + [[package]] name = "anstyle-query" version = "1.1.1" @@ -73,19 +93,20 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "async-compression" @@ -102,42 +123,21 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "backtrace" -version = "0.3.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide 0.7.4", - "object", - "rustc-demangle", -] - [[package]] name = "base64" version = "0.22.1" @@ -146,9 +146,33 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.6.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[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 = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] [[package]] name = "bumpalo" @@ -157,23 +181,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] -name = "byteorder" -version = "1.5.0" +name = "bytes" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] -name = "bytes" -version = "1.7.1" +name = "cbc" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] [[package]] name = "cc" -version = "1.1.14" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ + "find-msvc-tools", "shlex", ] @@ -189,22 +217,44 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" -version = "4.5.40" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "clap_lex", "strsim", @@ -212,18 +262,30 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.54" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad5b1b4de04fead402672b48897030eec1f3bfe1550776322f59f6d6e6a5677" +checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" dependencies = [ "clap", ] +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" -version = "0.7.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" @@ -233,18 +295,18 @@ checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colored" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -256,6 +318,24 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -265,11 +345,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cssparser" -version = "0.34.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3" +checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98" dependencies = [ "cssparser-macros", "dtoa-short", @@ -280,9 +370,9 @@ dependencies = [ [[package]] name = "cssparser-macros" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +checksum = "10a2a99df6e410a8ff4245aa2006499ea662245f967cc7c0a38c83ef8eb44dbf" dependencies = [ "quote", "syn", @@ -290,9 +380,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ "darling_core", "darling_macro", @@ -300,9 +390,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", @@ -314,9 +404,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", @@ -325,18 +415,24 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_more" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "powerfmt", + "derive_more-impl", ] [[package]] -name = "derive_more" -version = "0.99.18" +name = "derive_more-impl" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", @@ -345,20 +441,22 @@ dependencies = [ [[package]] name = "diesel" -version = "2.2.11" +version = "2.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a917a9209950404d5be011c81d081a2692a822f73c3d6af586f0cab5ff50f614" +checksum = "9940fb8467a0a06312218ed384185cb8536aa10d8ec017d0ce7fad2c1bd882d5" dependencies = [ "diesel_derives", + "downcast-rs", "libsqlite3-sys", + "sqlite-wasm-rs", "time", ] [[package]] name = "diesel_derives" -version = "2.2.3" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" +checksum = "47618bf0fac06bb670c036e48404c26a865e6a71af4114dfd97dfe89936e404e" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", @@ -369,13 +467,24 @@ dependencies = [ [[package]] name = "diesel_table_macro_syntax" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c" dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "dirs" version = "6.0.0" @@ -394,14 +503,31 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + [[package]] name = "dsl_auto_type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" +checksum = "dd122633e4bef06db27737f21d3738fb89c8f6d5360d6d9d7635dda142a7757e" dependencies = [ "darling", "either", @@ -428,9 +554,9 @@ dependencies = [ [[package]] name = "ego-tree" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" +checksum = "b04dc5a38e4f151a79d9f2451ae6037fb6eaf5cba34771f44781f80e508498e3" [[package]] name = "either" @@ -438,15 +564,6 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - [[package]] name = "env_filter" version = "0.1.2" @@ -463,7 +580,7 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ - "anstream", + "anstream 0.6.15", "anstyle", "env_filter", "humantime", @@ -478,9 +595,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys 0.52.0", @@ -492,6 +609,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + [[package]] name = "flate2" version = "1.0.33" @@ -499,7 +622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -508,6 +631,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -533,13 +662,12 @@ dependencies = [ ] [[package]] -name = "futf" -version = "0.1.5" +name = "fsevent-sys" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ - "mac", - "new_debug_unreachable", + "libc", ] [[package]] @@ -553,50 +681,51 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-task", "pin-project-lite", - "pin-utils", + "slab", ] [[package]] -name = "fxhash" -version = "0.2.1" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "byteorder", + "typenum", + "version_check", ] [[package]] name = "getopts" -version = "0.2.21" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ - "unicode-width 0.1.14", + "unicode-width", ] [[package]] @@ -623,35 +752,33 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.29.0" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", + "wasip2", + "wasip3", +] [[package]] -name = "h2" -version = "0.4.6" +name = "hashbrown" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "foldhash", ] [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -660,23 +787,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] [[package]] name = "html5ever" -version = "0.29.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e15626aaf9c351bc696217cbe29cb9b5e86c43f8a46b5e2f5c6c5cf7cb904ce" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" dependencies = [ "log", - "mac", "markup5ever", - "proc-macro2", - "quote", - "syn", ] [[package]] @@ -734,7 +860,6 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", "http", "http-body", "httparse", @@ -745,23 +870,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - [[package]] name = "hyper-tls" version = "0.6.0" @@ -796,14 +904,100 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", - "system-configuration", + "socket2 0.5.10", "tokio", "tower-service", "tracing", - "windows-registry", ] +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -812,29 +1006,66 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", ] [[package]] name = "indexmap" -version = "2.4.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.10.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", ] [[package]] -name = "indoc" -version = "2.0.5" +name = "inout" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] [[package]] name = "ipnet" @@ -866,57 +1097,77 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] -name = "keyring" -version = "3.6.2" +name = "kqueue" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1961983669d57bdfe6c0f3ef8e4c229b5ef751afcc7d87e4271d2f71f6ccfa8b" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ - "log", + "kqueue-sys", + "libc", ] +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "leetcode-cli" -version = "0.4.7" +version = "0.5.4" dependencies = [ + "aes", "anyhow", "async-trait", + "cbc", "clap", "clap_complete", "colored", "diesel", "dirs", "env_logger", - "keyring", "log", "nix", - "openssl", + "notify", + "pbkdf2", "pyo3", - "rand 0.9.1", + "rand", "regex", "reqwest", "scraper", "serde", "serde_json", + "sha1", "thiserror", "tokio", "toml", - "unicode-width 0.2.1", + "unicode-width", ] [[package]] name = "libc" -version = "0.2.172" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libredox" @@ -924,15 +1175,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.10.0", "libc", ] [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "pkg-config", "vcpkg", @@ -940,9 +1191,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -956,28 +1213,19 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" [[package]] name = "markup5ever" -version = "0.14.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c88c6129bd24319e62a0359cb6b958fa7e8be6e19bb1663bc396b90883aca5" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" dependencies = [ "log", - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", "tendril", + "web_atoms", ] [[package]] @@ -986,30 +1234,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -1021,21 +1245,21 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ - "hermit-abi", "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -1056,48 +1280,68 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" -version = "0.30.1" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", ] [[package]] -name = "num-conv" -version = "0.1.0" +name = "notify" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.10.0", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] [[package]] -name = "object" -version = "0.36.3" +name = "notify-types" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" -dependencies = [ - "memchr", -] +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -1115,15 +1359,15 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -1160,6 +1404,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1168,52 +1422,43 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" -version = "0.11.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_macros", - "phf_shared 0.11.2", + "phf_shared", + "serde", ] [[package]] name = "phf_codegen" -version = "0.11.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", + "phf_generator", + "phf_shared", ] [[package]] name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "phf_shared 0.11.2", - "rand 0.8.5", + "fastrand", + "phf_shared", ] [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", + "phf_generator", + "phf_shared", "proc-macro2", "quote", "syn", @@ -1221,18 +1466,9 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ "siphasher", ] @@ -1243,12 +1479,6 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkg-config" version = "0.3.30" @@ -1262,19 +1492,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] -name = "powerfmt" -version = "0.2.0" +name = "potential_utf" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] [[package]] -name = "ppv-lite86" -version = "0.2.20" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy 0.7.35", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "precomputed-hash" @@ -1282,47 +1512,53 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" +checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c" dependencies = [ - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" +checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" +checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b" dependencies = [ "libc", "pyo3-build-config", @@ -1330,9 +1566,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" +checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1342,85 +1578,47 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" +checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362" dependencies = [ "heck", "proc-macro2", - "pyo3-build-config", "quote", "syn", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] -name = "rand" -version = "0.8.5" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.0", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.0", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", + "chacha20", + "getrandom 0.4.2", + "rand_core", ] [[package]] name = "rand_core" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" -dependencies = [ - "getrandom 0.3.1", - "zerocopy 0.8.14", -] +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" [[package]] name = "redox_syscall" @@ -1428,7 +1626,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.10.0", ] [[package]] @@ -1444,9 +1642,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1456,9 +1654,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1473,38 +1671,30 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "async-compression", "base64", "bytes", - "encoding_rs", "futures-core", - "futures-util", - "h2", "http", "http-body", "http-body-util", "hyper", - "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", "log", - "mime", "native-tls", "percent-encoding", "pin-project-lite", "rustls-pki-types", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-util", "tower", "tower-http", "tower-service", @@ -1515,52 +1705,24 @@ dependencies = [ ] [[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.15", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "rustc-hash" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "0.38.34" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.23.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -1570,17 +1732,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-webpki" -version = "0.102.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.20" @@ -1588,18 +1739,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] -name = "ryu" -version = "1.0.18" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1610,9 +1764,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scraper" -version = "0.23.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527e65d9d888567588db4c12da1087598d0f6f8b346cc2c5abc91f05fc2dffe2" +checksum = "bdd0be4d296f048bfb06dd01bbc80ef789ddd2e55583e8d2e6b804942abfabc2" dependencies = [ "cssparser", "ego-tree", @@ -1625,11 +1779,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags", + "bitflags 2.10.0", "core-foundation", "core-foundation-sys", "libc", @@ -1638,9 +1792,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -1648,37 +1802,53 @@ dependencies = [ [[package]] name = "selectors" -version = "0.26.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" +checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cssparser", "derive_more", - "fxhash", "log", "new_debug_unreachable", "phf", "phf_codegen", "precomputed-hash", + "rustc-hash", "servo_arc", "smallvec", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1687,44 +1857,44 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "servo_arc" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", + "stable_deref_trait", ] [[package]] -name = "servo_arc" -version = "0.4.0" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae65c4249478a2647db249fb43e23cec56a2c8974a427e7bd8cb5a1d0964921a" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "stable_deref_trait", + "cfg-if", + "cpufeatures 0.2.17", + "digest", ] [[package]] @@ -1744,9 +1914,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" @@ -1774,10 +1944,29 @@ dependencies = [ ] [[package]] -name = "spin" -version = "0.9.8" +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "sqlite-wasm-rs" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "60bdd87fcb4c9764b024805fb2df5f1d659bea6e629fdbdcdcfc4042b9a640d0" +dependencies = [ + "js-sys", + "once_cell", + "thiserror", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] name = "stable_deref_trait" @@ -1787,26 +1976,24 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "string_cache" -version = "0.8.7" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", - "once_cell", "parking_lot", - "phf_shared 0.10.0", + "phf_shared", "precomputed-hash", - "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "phf_generator", + "phf_shared", "proc-macro2", "quote", ] @@ -1825,9 +2012,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.90" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1844,70 +2031,59 @@ dependencies = [ ] [[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ - "core-foundation-sys", - "libc", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "target-lexicon" -version = "0.13.2" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "tendril" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" dependencies = [ - "futf", - "mac", + "new_debug_unreachable", "utf-8", ] [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1916,73 +2092,66 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "tinyvec" -version = "1.8.0" +name = "tinystr" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.45.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ - "backtrace", "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.4", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -1999,17 +2168,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.11" @@ -2025,44 +2183,42 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ - "serde", + "indexmap", + "serde_core", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "serde", + "serde_core", ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "toml_parser" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_writer" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tower" @@ -2081,17 +2237,22 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags", + "async-compression", + "bitflags 2.10.0", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", "iri-string", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -2135,10 +2296,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "unicode-bidi" -version = "0.3.15" +name = "typenum" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" @@ -2146,44 +2307,23 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - [[package]] 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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" - -[[package]] -name = "unindent" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] -name = "untrusted" -version = "0.9.0" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -2196,6 +2336,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2208,6 +2354,22 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2233,48 +2395,54 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen" -version = "0.2.100" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2282,71 +2450,97 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] -name = "web-sys" -version = "0.3.70" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ - "js-sys", - "wasm-bindgen", + "leb128fmt", + "wasmparser", ] [[package]] -name = "windows-link" -version = "0.1.1" +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] [[package]] -name = "windows-registry" -version = "0.4.0" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.53.0", + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap", + "semver", ] [[package]] -name = "windows-result" -version = "0.3.2" +name = "web-sys" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ - "windows-link", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "windows-strings" -version = "0.3.1" +name = "web_atoms" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" dependencies = [ - "windows-link", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.52.0" @@ -2358,11 +2552,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", ] [[package]] @@ -2383,10 +2586,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -2495,11 +2699,28 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "memchr", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", ] [[package]] @@ -2508,48 +2729,125 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags", + "bitflags 2.10.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", ] [[package]] -name = "zerocopy" -version = "0.7.35" +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] [[package]] -name = "zerocopy" -version = "0.8.14" +name = "writeable" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "zerocopy-derive 0.8.14", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "zerocopy-derive" -version = "0.7.35" +name = "yoke-derive" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", "syn", + "synstructure", ] [[package]] -name = "zerocopy-derive" -version = "0.8.14" +name = "zerofrom" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", "syn", + "synstructure", ] [[package]] @@ -2557,3 +2855,42 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac060176f7020d62c3bcc1cdbcec619d54f48b07ad1963a3f80ce7a0c17755f" diff --git a/Cargo.toml b/Cargo.toml index a29aa42..905b8fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,9 @@ path = "src/bin/lc.rs" [package] name = "leetcode-cli" -version = "0.4.7" +version = "0.5.4" authors = ["clearloop "] -edition = "2021" +edition = "2024" description = "Leetcode command-line interface in rust." repository = "https://github.com/clearloop/leetcode-cli" license = "MIT" @@ -16,37 +16,41 @@ keywords = ["cli", "games", "leetcode"] readme = './README.md' [dependencies] -async-trait = "0.1.88" -tokio = { version = "1.45.1", features = ["full"] } -clap = { version = "4.5.40", features = ["cargo"] } -colored = "3.0.0" +async-trait = "0.1.89" +tokio = { version = "1.52.3", features = ["full"] } +clap = { version = "4.6", features = ["cargo", "derive"] } +colored = "3.1.1" dirs = "6.0.0" env_logger = "0.11.6" -keyring = "3.6.2" -log = "0.4.27" -openssl = "0.10.73" -pyo3 = { version = "0.25.1", optional = true } -rand = "0.9.1" -serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" -toml = "0.8.23" -regex = "1.11.1" -scraper = "0.23.1" -anyhow = "1.0.98" -clap_complete = "4.5.54" -thiserror = "2.0.12" +log = "0.4.31" +pyo3 = { version = "0.29", optional = true } +rand = "0.10.1" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.150" +toml = "1.1.2" +regex = "1.12.3" +scraper = "0.27.0" +anyhow = "1.0.102" +clap_complete = "4.6" +thiserror = "2.0.18" unicode-width = "0.2" +notify = "8.2.0" +aes = "0.8" +cbc = { version = "0.1", features = ["alloc", "block-padding"] } +pbkdf2 = "0.12" +sha1 = "0.10" [dependencies.diesel] -version = "2.2.11" +version = "2.3.9" features = ["sqlite"] [dependencies.reqwest] -version = "0.12.22" -features = ["gzip", "json"] +version = "0.13.4" +default-features = false +features = ["gzip", "json", "native-tls"] [features] pym = ["pyo3"] [target.'cfg(target_family = "unix")'.dependencies] -nix = { version = "0.30.1", features = [ "signal" ] } +nix = { version = "0.31.3", features = ["signal"] } diff --git a/README.md b/README.md index 315587a..43464eb 100644 --- a/README.md +++ b/README.md @@ -7,393 +7,78 @@ [![telegram](https://img.shields.io/badge/telegram-blue?logo=telegram)](https://t.me/+U_5si6PhWykxZTI1) [![LICENSE](https://img.shields.io/crates/l/leetcode-cli.svg)](https://choosealicense.com/licenses/mit/) -## Installing +May the code be with you. -```sh -# Required dependencies: -# -# gcc -# libssl-dev -# libdbus-1-dev -# libsqlite3-dev - -cargo install leetcode-cli -``` - -
-Shell completions - -For Bash and Zsh (by default picks up `$SHELL` from environment) +## Install ```sh -eval "$(leetcode completions)" +# Linux build deps: system SQLite + OpenSSL headers + pkg-config +# (Debian/Ubuntu: libsqlite3-dev libssl-dev pkg-config). macOS ships both. +cargo install leetcode-cli ``` -Copy the line above to `.bash_profile` or `.zshrc` - -You may also obtain specific shell configuration using. +Python filtering scripts (the `--plan` flag) require the optional `pym` feature: ```sh -leetcode completions fish +cargo install leetcode-cli --features pym ``` -If no argument is provided, the shell is inferred from the `SHELL` environment variable. +Nix users can `nix build` / `nix develop` against the bundled [`flake.nix`](./flake.nix). -
- -## Usage +## Quickstart -**Make sure you have logged in to `leetcode.com` with `Firefox`**. See [Cookies](#cookies) for why you need to do this first. +Sign in to LeetCode in **Chrome** first — on macOS and Linux leetcode-cli reads its cookies automatically (on Windows, set them manually). See [Cookies](./docs/cookies.md) for manual setup and environment-variable overrides. ```sh -leetcode 0.4.0 -May the Code be with You 👻 - -USAGE: - leetcode [FLAGS] [SUBCOMMAND] - -FLAGS: - -d, --debug debug mode - -h, --help Prints help information - -V, --version Prints version information - -SUBCOMMANDS: - data Manage Cache [aliases: d] - edit Edit question by id [aliases: e] - exec Submit solution [aliases: x] - list List problems [aliases: l] - pick Pick a problem [aliases: p] - stat Show simple chart about submissions [aliases: s] - test Test question by id [aliases: t] - help Prints this message or the help of the given subcommand(s) +leetcode pick 1 # pick a problem and print its description +leetcode edit 1 # open the solution file in your editor +leetcode test 1 # run the sample test cases +leetcode exec 1 # submit the solution ``` -## Example - -To configure leetcode-cli, create a file at `~/.leetcode/leetcode.toml`): - -```toml -[code] -editor = 'emacs' -# Optional parameter -editor_args = ['-nw'] -# Optional environment variables (ex. [ "XDG_DATA_HOME=...", "XDG_CONFIG_HOME=...", "XDG_STATE_HOME=..." ]) -editor_envs = [] -lang = 'rust' -edit_code_marker = false -start_marker = "" -end_marker = "" -# if include problem description -comment_problem_desc = false -# comment syntax -comment_leading = "" -test = true +Run `leetcode --help` (or `leetcode --help`) for the full, always-current list of commands and flags. The headline ones: -[cookies] -csrf = '' -session = '' -# leetcode.com or leetcode.cn -site = "leetcode.com" - -[storage] -cache = 'Problems' -code = 'code' -root = '~/.leetcode' -scripts = 'scripts' -``` +| Command | Alias | What it does | +| --- | --- | --- | +| `pick` | `p` | Pick a problem by id, `--name`, `--tag`, `--query`, `--plan`, or `--daily` | +| `edit` | `e` | Open a problem's code file; `--lang` overrides the configured language, `--daily` opens today's challenge | +| `test` | `t` | Run test cases; `--watch` re-runs on save, `--daily` targets today's challenge | +| `exec` | `x` | Submit the solution | +| `list` | `l` | List/filter problems by category, tag, id range, or `--query` | +| `stat` | `s` | Show a chart of your submissions | +| `data` | `d` | Manage the local cache (`--update`, `--delete`) | +| `completions` | `c` | Generate shell completions (`bash`, `elvish`, `fish`, `powershell`, `zsh`) |
- Configuration Explanation - -```toml -[code] -editor = 'emacs' -# Optional parameter -editor_args = ['-nw'] -# Optional environment variables (ex. [ "XDG_DATA_HOME=...", "XDG_CONFIG_HOME=...", "XDG_STATE_HOME=..." ]) -editor_envs = [] -lang = 'rust' -edit_code_marker = true -start_marker = "start_marker" -end_marker = "end_marker" -# if include problem description -comment_problem_desc = true -# comment syntax -comment_leading = "//" -test = true - -[cookies] -csrf = '' -session = '' - -[storage] -cache = 'Problems' -code = 'code' -root = '~/.leetcode' -scripts = 'scripts' -``` - -If we change the configuration as shown previously, we will get the following code after `leetcode edit 15`. - -```rust -// Category: algorithms -// Level: Medium -// Percent: 32.90331% - -// Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0. -// -// Notice that the solution set must not contain duplicate triplets. -// -//   -// Example 1: -// -// Input: nums = [-1,0,1,2,-1,-4] -// Output: [[-1,-1,2],[-1,0,1]] -// Explanation: -// nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. -// nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. -// nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. -// The distinct triplets are [-1,0,1] and [-1,-1,2]. -// Notice that the order of the output and the order of the triplets does not matter. -// -// -// Example 2: -// -// Input: nums = [0,1,1] -// Output: [] -// Explanation: The only possible triplet does not sum up to 0. -// -// -// Example 3: -// -// Input: nums = [0,0,0] -// Output: [[0,0,0]] -// Explanation: The only possible triplet sums up to 0. -// -// -//   -// Constraints: -// -// -// 3 <= nums.length <= 3000 -// -10⁵ <= nums[i] <= 10⁵ -// - -// start_marker -impl Solution { -pub fn three_sum(nums: Vec) -> Vec> { - - } - -} -// end_marker - -``` - -
- -
- -Some linting tools/lsps will throw errors unless the necessary libraries are imported. leetcode-cli can generate this boilerplate automatically if the `inject_before` key is set. Similarly, if you want to test out your code locally, you can automate that with `inject_after`. For c++ this might look something like: - -```toml -[code] -inject_before = ["#include", "using namespace std;"] -inject_after = ["int main() {\n Solution solution;\n\n}"] -``` - -#### 1. pick - -```sh -leetcode pick 1 -``` - -```sh -leetcode pick --name "Two Sum" -``` - -```sh -[1] Two Sum is on the run... - - -Given an array of integers, return indices of the two numbers such that they add up to a specific target. - -You may assume that each input would have exactly one solution, and you may not use the same element twice. - --------------------------------------------------- - -Example: - - -Given nums = [2, 7, 11, 15], target = 9, - -Because nums[0] + nums[1] = 2 + 7 = 9, -return [0, 1]. -``` - -#### 2. edit - -```sh -leetcode edit 1 -``` - -```rust -# struct Solution; -impl Solution { - pub fn two_sum(nums: Vec, target: i32) -> Vec { - use std::collections::HashMap; - let mut m: HashMap = HashMap::new(); - - for (i, e) in nums.iter().enumerate() { - if let Some(v) = m.get(&(target - e)) { - return vec![*v, i as i32]; - } - - m.insert(*e, i as i32).unwrap_or_default(); - } - - return vec![]; - } -} -``` - -#### 3. test +Shell completions -```sh -leetcode test 1 -``` +By default the shell is inferred from `$SHELL`: ```sh - - Accepted Runtime: 0 ms - - Your input: [2,7,11,15], 9 - Output: [0,1] - Expected: [0,1] - +eval "$(leetcode completions)" ``` -#### 4. exec +Copy that line into `.bash_profile` or `.zshrc`. Pass a shell explicitly to target another: ```sh -leetcode exec 1 -``` - -```sh - - Success - - Runtime: 0 ms, faster than 100% of Rustonline submissions for Two Sum. - - Memory Usage: 2.4 MB, less than 100% of Rustonline submissions for Two Sum. - - -``` - -## Cookies - -The cookie plugin of leetcode-cli can work on OSX and [Linux][#1]. **If you are on a different platform, there are problems with caching the cookies**, -you can manually input your LeetCode Cookies to the configuration file. - -```toml -[cookies] -csrf = "..." -session = "..." -``` - -For Example, using Firefox (after logging in to LeetCode): - -#### Step 1 - -Open Firefox, press F12, and click `Storage` tab. - -#### Step 2 - -Expand `Cookies` tab on the left and select https://leetcode.com. - -#### Step 2 - -Copy `Value` from `LEETCODE_SESSION` and `csrftoken` to `session` and `csrf` in your configuration file, respectively: - -```toml -[cookies] -csrf = '' -session = '' -``` - -#### Environment variables - -The cookies can also be overridden by environment variables, which might be useful to exclude the sensitive information from the configuration file `leetcode.toml`. To do this, you can leave the `csrf` and `session` fields empty in the configuration file and override cookies settings via the environment variables `LEETCODE_CSRF`, `LEETCODE_SESSION`, and `LEETCODE_SITE`: - -```toml -[cookies] -csrf = '' -session = '' -site = 'leetcode.com' -``` - -Then set the environment variables: - -```bash -export LEETCODE_CSRF='' -export LEETCODE_SESSION='' -export LEETCODE_SITE='leetcode.cn' # or 'leetcode.com' -``` - -Note that `cookies.site` in still required in the `leetcode.toml` to avoid exception during configuration file parsing, but can be overridden using environment variables. - -## Programmable - -If you want to filter LeetCode questions using custom Python scripts, add the following to your the configuration file: - -```toml -[storage] -scripts = "scripts" -``` - -Then write the script: - -```python -# ~/.leetcode/scripts/plan1.py -import json; - -def plan(sps, stags): - ## - # `print` in python is supported, - # if you want to know the data structures of these two args, - # just print them - ## - problems = json.loads(sps) - tags = json.loads(stags) - - ret = [] - tm = {} - for tag in tags: - tm[tag["tag"]] = tag["refs"]; - - for i in problems: - if i["level"] == 1 and str(i["id"]) in tm["linked-list"]: - ret.append(str(i["id"])) - - # return is `List[string]` - return ret +leetcode completions fish ``` -Then run `list` with the filter that you just wrote: + -```sh -leetcode list -p plan1 -``` +## Documentation -That's it! Enjoy! +- [Configuration](./docs/configuration.md) — the full `leetcode.toml` reference: editor, code generation, filename templates, and storage paths. +- [Cookies](./docs/cookies.md) — automatic Chrome cookies, manual setup from any browser, `leetcode.cn` support, and environment-variable overrides. +- [Editors & LSP](./docs/editors.md) — getting rust-analyzer (and other language servers) working with generated solution files. +- [Scripting](./docs/scripting.md) — filtering problems with custom Python plans. -## Contributions +## Contributing -Feel free to add your names and emails in the `authors` field of `Cargo.toml` ! +Feel free to add your name and email to the `authors` field of `Cargo.toml`, and open a [pull request][pr]. -## LICENSE +## License MIT [pr]: https://github.com/clearloop/leetcode-cli/pulls -[#1]: https://github.com/clearloop/leetcode-cli/issues/1 diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 034e848..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,21 +0,0 @@ -# Security Policy - -## Supported Versions - -Use this section to tell people about which versions of your project are -currently being supported with security updates. - -| Version | Supported | -| ------- | ------------------ | -| 5.1.x | :white_check_mark: | -| 5.0.x | :x: | -| 4.0.x | :white_check_mark: | -| < 4.0 | :x: | - -## Reporting a Vulnerability - -Use this section to tell people how to report a vulnerability. - -Tell them where to go, how often they can expect to get an update on a -reported vulnerability, what to expect if the vulnerability is accepted or -declined, etc. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..7bfcdd2 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,128 @@ +# Configuration + +leetcode-cli reads `~/.leetcode/leetcode.toml`. The file is created with defaults the first time you run any command, so the easiest way to start is to run leetcode-cli once and then edit the file. + +It has three sections: `[code]`, `[cookies]`, and `[storage]`. Cookies have their own page — see [Cookies](./cookies.md). + +## `[code]` + +Controls the editor that opens and the code that gets generated. + +```toml +[code] +editor = 'vim' +# Extra arguments passed to the editor +editor_args = ['-nw'] +# Environment variables for the editor process, as "NAME=VALUE" strings +editor_envs = ['XDG_CONFIG_HOME=/home/me/.config'] +# Language of the generated solution file +lang = 'rust' +# Run the sample test cases automatically and write them to a .tests.dat file +test = true +``` + +| Key | Default | Description | +| --- | --- | --- | +| `editor` | `'vim'` | Command used to open the solution file. | +| `editor_args` | — | Extra arguments passed before the file path. | +| `editor_envs` | — | Environment variables for the editor process, each as `"NAME=VALUE"`. | +| `lang` | `'rust'` | Language of the generated file. `leetcode edit --lang ` overrides this per-call and persists it. | +| `test` | `true` | Generate a `.tests.dat` file with the problem's sample cases. | + +### Problem description & code markers + +These let you embed the problem statement as a comment and wrap the editable region in markers so tools (or `inject_*`) can find it. + +```toml +[code] +# Prepend the problem description as a comment +comment_problem_desc = true +# Comment syntax used for the description and the markers +comment_leading = '//' +# Wrap the generated boilerplate in start/end markers +edit_code_marker = true +start_marker = 'start_marker' +end_marker = 'end_marker' +``` + +With the above, `leetcode edit 15` produces: + +```rust +// Category: algorithms +// Level: Medium +// +// Given an integer array nums, return all the triplets ... + +// start_marker +impl Solution { + pub fn three_sum(nums: Vec) -> Vec> { + + } +} +// end_marker +``` + +| Key | Default | Description | +| --- | --- | --- | +| `comment_problem_desc` | `false` | Prepend the problem statement as a comment. | +| `comment_leading` | `''` | Comment prefix used for the description and markers (e.g. `//`, `#`). | +| `edit_code_marker` | `false` | Wrap the generated code in `start_marker`/`end_marker`. | +| `start_marker` / `end_marker` | `''` | Marker text, emitted as `comment_leading + ' ' + marker`. | + +### Injecting code before & after + +Some language servers and linters complain unless the necessary imports are present, and you may want a `main` to run the solution locally. `inject_before` and `inject_after` add lines around the generated boilerplate without affecting what gets submitted to LeetCode. + +```toml +[code] +inject_before = ['#include', 'using namespace std;'] +inject_after = ['int main() {\n Solution solution;\n}'] +``` + +For getting a language server working with generated files, see [Editors & LSP](./editors.md). + +### Filename template + +`pick` controls the file name of generated solution and test files. It is interpolated against the picked problem: + +| Variable | Meaning | +| --- | --- | +| `${fid}` | Frontend problem id (e.g. `1`) | +| `${slug}` | Problem slug (e.g. `two-sum`) | + +```toml +[code] +# Default +pick = '${fid}.${slug}' +``` + +With `lang = 'rust'`, `leetcode edit 1` writes `code/1.two-sum.rs` (and `code/1.two-sum.tests.dat` when `test = true`). Because the template can include `/`, it doubles as a way to lay out one directory per problem — see [Editors & LSP](./editors.md). + +## `[storage]` + +Where leetcode-cli keeps its files. Paths under `root` are created on demand. `~` in `root` expands to your home directory. + +```toml +[storage] +root = '~/.leetcode' +code = 'code' +scripts = 'scripts' +``` + +| Key | Default | Description | +| --- | --- | --- | +| `root` | `'~/.leetcode'` | Base directory for everything below. | +| `code` | `'code'` | Sub-directory (under `root`) for generated solution files. | +| `scripts` | `'scripts'` | Sub-directory for Python filtering scripts — see [Scripting](./scripting.md). | + +## `[cookies]` + +See [Cookies](./cookies.md) for the full story (automatic Chrome reading, manual setup, `leetcode.cn`, and environment overrides). + +```toml +[cookies] +csrf = '' +session = '' +# Either 'leetcode.com' or 'leetcode.cn' +site = 'leetcode.com' +``` diff --git a/docs/cookies.md b/docs/cookies.md new file mode 100644 index 0000000..260f4b1 --- /dev/null +++ b/docs/cookies.md @@ -0,0 +1,46 @@ +# Cookies + +leetcode-cli talks to LeetCode as you, so it needs your session cookies. There are three ways to provide them. + +## 1. Automatic (Chrome) + +If `csrf` and `session` are both empty in `leetcode.toml`, leetcode-cli reads the cookies straight from **Chrome's** cookie store for the configured `site`. Just sign in to LeetCode in Chrome and run any command — there is nothing else to set up. + +This is the only automatic path, and it is Chrome-only. It works on **macOS and Linux**; on **Windows** Chrome's cookie encryption isn't read automatically yet, so use the manual setup below. The same applies if you use a different browser, or Chrome's cookie store can't be read. + +> If you see a "not logged in to Chrome" error, either sign in to LeetCode in Chrome or set the cookies manually. + +## 2. Manual (any browser) + +Copy the two cookie values into `leetcode.toml`: + +```toml +[cookies] +csrf = '' +session = '' +site = 'leetcode.com' +``` + +To find them — for example in Firefox, after logging in to LeetCode: + +1. Press F12 and open the **Storage** tab. +2. Expand **Cookies** and select `https://leetcode.com`. +3. Copy the `Value` of `csrftoken` into `csrf` and `LEETCODE_SESSION` into `session`. + +The same values are available under DevTools in any Chromium browser (Application → Cookies). + +## 3. Environment variables + +To keep secrets out of `leetcode.toml`, leave `csrf` and `session` empty and export them instead. Environment variables override whatever is in the file: + +```sh +export LEETCODE_CSRF='' +export LEETCODE_SESSION='' +export LEETCODE_SITE='leetcode.com' # or 'leetcode.cn' +``` + +`cookies.site` must still be present in `leetcode.toml` (otherwise config parsing fails), but `LEETCODE_SITE` overrides it at runtime. + +## leetcode.com vs leetcode.cn + +`site` accepts exactly two values: `leetcode.com` or `leetcode.cn` (anything else is rejected). Choosing `leetcode.cn` switches every API endpoint to the China site — set it via the config field or `LEETCODE_SITE`. diff --git a/docs/editors.md b/docs/editors.md new file mode 100644 index 0000000..5306aeb --- /dev/null +++ b/docs/editors.md @@ -0,0 +1,62 @@ +# Editors & LSP + +leetcode-cli generates flat solution files (e.g. `code/1.two-sum.rs`). That keeps submissions simple, but a language server like rust-analyzer expects a project it recognizes. Getting full autocomplete, diagnostics, and go-to-definition is an **editor-side** setup, not something leetcode-cli does for you — and there are two clean ways to do it. + +This page uses Rust as the running example; the same ideas apply to other languages and their servers. + +## Option A — single-file mode (recommended) + +rust-analyzer supports [detached / single files](https://github.com/rust-lang/rust-analyzer/pull/8955) that don't belong to a Cargo workspace. This needs no extra files and no change to how leetcode-cli generates code — just tell your editor to treat the directory as detached. + +- **VS Code** — open the `code/` directory; rust-analyzer picks up detached files automatically. You can pin them with `rust-analyzer.linkedProjects` if needed. +- **Neovim / other LSP clients** — point the rust-analyzer root at the `code/` directory (e.g. set the workspace root to the folder rather than searching for a `Cargo.toml`). +- **Helix / Zed** — make sure rust-analyzer is launched with the `code/` directory as its root; both honor rust-analyzer's detached-file handling. + +If your standard library completions don't show up, confirm `rustc` and `rust-analyzer` are recent — detached-file support has improved over time. + +## Option B — a Cargo workspace + +If you'd rather have a real crate (so `cargo check`/`cargo test` work too), put a workspace around the generated files and `include!` them. leetcode-cli's existing config does all the wiring — no patches required. + +1. Create a workspace and point leetcode-cli's storage at it: + + ```sh + cargo new --lib leetcode-solutions + ``` + + ```toml + # ~/.leetcode/leetcode.toml + [storage] + root = '/path/to/leetcode-solutions' + code = 'solutions' + ``` + + Now `leetcode edit 1` writes into `leetcode-solutions/solutions/`, inside a crate rust-analyzer understands. + +2. Pull the solution files into the crate from `src/lib.rs`: + + ```rust + #![allow(dead_code, unused_imports, unused_variables, non_snake_case)] + + mod solutions { + include!("../solutions/1.two-sum.rs"); + include!("../solutions/2.add-two-numbers.rs"); + } + ``` + +You get full rust-analyzer features across every file, and `leetcode test`/`leetcode exec` still extract and submit the solution snippet unchanged. + +### Tip: one directory per problem + +The [`[code] pick`](./configuration.md#filename-template) template can contain a `/`, so you can give each problem its own folder if your editor prefers that layout: + +```toml +[code] +pick = '${fid}.${slug}/solution' +``` + +This writes `code/1.two-sum/solution.rs`, etc. + +## Why leetcode-cli doesn't generate per-problem crates + +Scaffolding a Cargo crate (or any per-language project structure) for every problem would bake an editor concern into the tool, with a config flag and a directory layout to maintain forever. The two approaches above solve the same problem entirely from the editor and existing config — so that's where it belongs. See the discussion in [#204](https://github.com/clearloop/leetcode-cli/issues/204). diff --git a/docs/scripting.md b/docs/scripting.md new file mode 100644 index 0000000..b15a13f --- /dev/null +++ b/docs/scripting.md @@ -0,0 +1,43 @@ +# Scripting + +You can filter LeetCode problems with custom Python scripts and pass the result to `list` or `pick` via `--plan`. + +> Python scripting is gated behind the optional `pym` Cargo feature. A default `cargo install leetcode-cli` does **not** include it — install with `cargo install leetcode-cli --features pym`, otherwise `--plan` is silently ignored. + +## Writing a plan + +Scripts live in the `scripts` directory under your storage root (`~/.leetcode/scripts` by default; configurable via `[storage] scripts`). + +```python +# ~/.leetcode/scripts/plan1.py +import json + +def plan(sps, stags): + # `print` works here — print the two args if you want to inspect their shape. + problems = json.loads(sps) + tags = json.loads(stags) + + tm = {} + for tag in tags: + tm[tag["tag"]] = tag["refs"] + + ret = [] + for i in problems: + if i["level"] == 1 and str(i["id"]) in tm["linked-list"]: + ret.append(str(i["id"])) + + # Return a List[str] of problem ids + return ret +``` + +The module must define `plan(sps, stags)`: +- `sps` — JSON string of all problems. +- `stags` — JSON string of all tags. +- returns a `List[str]` of problem ids to keep. + +## Running it + +```sh +leetcode list -p plan1 # filter the list down to the plan's ids +leetcode pick -p plan1 # pick from the filtered set +``` diff --git a/flake.nix b/flake.nix index 936e777..98e6b94 100644 --- a/flake.nix +++ b/flake.nix @@ -50,9 +50,6 @@ ]; buildInputs = with pkgs; [ - openssl - dbus - sqlite ] ++ darwinBuildInputs; package = naersk'.buildPackage rec { @@ -64,10 +61,6 @@ inherit buildInputs nativeBuildInputs; - buildNoDefaultFeatures = true; - - buildFeatures = "git"; - meta = with pkgs.lib; { description = "Leet your code in command-line."; homepage = "https://github.com/clearloop/leetcode-cli"; @@ -78,7 +71,7 @@ # Env vars # a nightly compiler is required unless we use this cheat code. - RUSTC_BOOTSTRAP = 0; + RUSTC_BOOTSTRAP = 1; # CFG_RELEASE = "${rustPlatform.rust.rustc.version}-stable"; CFG_RELEASE_CHANNEL = "stable"; @@ -104,6 +97,8 @@ PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; RUST_BACKTRACE = "full"; LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs; + RUSTC_BOOTSTRAP = 1; + CFG_RELEASE_CHANNEL = "stable"; }; } ); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 2a19081..92e3f22 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -6,5 +6,6 @@ components = [ "rustfmt", "clippy", "rust-analyzer", + "rust-src" ] profile = "minimal" diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 3250bf6..8ec75a9 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -22,15 +22,13 @@ pub fn conn(p: String) -> SqliteConnection { } /// Condition submit or test -#[derive(Clone, Debug)] -#[derive(Default)] +#[derive(Clone, Debug, Default)] pub enum Run { Test, #[default] Submit, } - /// Requests if data not download #[derive(Clone)] pub struct Cache(pub LeetCode); @@ -188,7 +186,7 @@ impl Cache { Err(Error::CookieError) } else { Err(Error::PremiumError) - } + }; } Some(true) => (), } @@ -358,7 +356,11 @@ impl Cache { .await?; let run_res: RunCode = serde_json::from_str(&text).map_err(|e| { - anyhow!("JSON error: {e}, please double check your session and csrf config.") + anyhow!( + "Failed to decode run code result, could be caused by cookie expiration, \ + csrf token mismatch, or network issue:\n {e}, raw response:\n {text}\n, \ + please report this issue at https://github.com/clearloop/leetcode-cli/issues/new" + ) })?; trace!("Run code result {:#?}", run_res); diff --git a/src/cache/models.rs b/src/cache/models.rs index 9b28482..79f4a70 100644 --- a/src/cache/models.rs +++ b/src/cache/models.rs @@ -1,11 +1,11 @@ //! Leetcode data models -use unicode_width::UnicodeWidthStr; -use unicode_width::UnicodeWidthChar; use super::schemas::{problems, tags}; use crate::helper::HTML; use colored::Colorize; use serde::{Deserialize, Serialize}; use serde_json::Number; +use unicode_width::UnicodeWidthChar; +use unicode_width::UnicodeWidthStr; /// Tag model #[derive(Clone, Insertable, Queryable, Serialize, Debug)] diff --git a/src/cache/parser.rs b/src/cache/parser.rs index 329455d..24226e0 100644 --- a/src/cache/parser.rs +++ b/src/cache/parser.rs @@ -15,7 +15,12 @@ pub fn problem(problems: &mut Vec, v: Value) -> Option<()> { // Handle on leetcode-com Some(s) => s as i32, // Handle on leetcode-cn - None => fid_obj.as_str()?.split(' ').last()?.parse::().ok()?, + None => fid_obj + .as_str()? + .split(' ') + .next_back()? + .parse::() + .ok()?, }; problems.push(Problem { @@ -132,7 +137,7 @@ pub fn user(v: Value) -> Option> { pub use ss::ssr; /// string or squence mod ss { - use serde::{de, Deserialize, Deserializer}; + use serde::{Deserialize, Deserializer, de}; use std::fmt; use std::marker::PhantomData; diff --git a/src/cli.rs b/src/cli.rs index 63107a2..a53d39e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,13 +1,10 @@ //! Clap Commanders use crate::{ - cmds::{ - completion_handler, Command, CompletionCommand, DataCommand, EditCommand, ExecCommand, - ListCommand, PickCommand, StatCommand, TestCommand, - }, + cmd::{CompletionsArgs, DataArgs, EditArgs, ExecArgs, ListArgs, PickArgs, StatArgs, TestArgs}, err::Error, - flag::{Debug, Flag}, }; -use clap::crate_version; +use clap::{CommandFactory, Parser, Subcommand}; +use env_logger::Env; use log::LevelFilter; /// This should be called before calling any cli method or printing any output. @@ -23,30 +20,62 @@ pub fn reset_signal_pipe_handler() { } } +/// May the Code be with You +#[derive(Parser)] +#[command(name = "leetcode", version, about = "May the Code be with You 👻")] +#[command(arg_required_else_help = true)] +pub struct Cli { + /// Debug mode + #[arg(short, long)] + pub debug: bool, + + #[command(subcommand)] + pub command: Option, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Manage Cache + #[command(visible_alias = "d", display_order = 1)] + Data(DataArgs), + + /// Edit question + #[command(visible_alias = "e", display_order = 2)] + Edit(EditArgs), + + /// Submit solution + #[command(visible_alias = "x", display_order = 3)] + Exec(ExecArgs), + + /// List problems + #[command(visible_alias = "l", display_order = 4)] + List(ListArgs), + + /// Pick a problem + #[command(visible_alias = "p", display_order = 5)] + Pick(PickArgs), + + /// Show simple chart about submissions + #[command(visible_alias = "s", display_order = 6)] + Stat(StatArgs), + + /// Test a question + #[command(visible_alias = "t", display_order = 7)] + Test(TestArgs), + + /// Generate shell Completions + #[command(visible_alias = "c", display_order = 8)] + Completions(CompletionsArgs), +} + /// Get matches pub async fn main() -> Result<(), Error> { reset_signal_pipe_handler(); - let mut cmd = clap::Command::new("leetcode") - .version(crate_version!()) - .about("May the Code be with You 👻") - .subcommands(vec![ - DataCommand::usage().display_order(1), - EditCommand::usage().display_order(2), - ExecCommand::usage().display_order(3), - ListCommand::usage().display_order(4), - PickCommand::usage().display_order(5), - StatCommand::usage().display_order(6), - TestCommand::usage().display_order(7), - CompletionCommand::usage().display_order(8), - ]) - .arg(Debug::usage()) - .arg_required_else_help(true); - - let m = cmd.clone().get_matches(); - - if m.get_flag("debug") { - Debug::handler()?; + let cli = Cli::parse(); + + if cli.debug { + env_logger::Builder::from_env(Env::default().default_filter_or("debug")).init(); } else { env_logger::Builder::new() .filter_level(LevelFilter::Info) @@ -54,15 +83,18 @@ pub async fn main() -> Result<(), Error> { .init(); } - match m.subcommand() { - Some(("data", sub_m)) => Ok(DataCommand::handler(sub_m).await?), - Some(("edit", sub_m)) => Ok(EditCommand::handler(sub_m).await?), - Some(("exec", sub_m)) => Ok(ExecCommand::handler(sub_m).await?), - Some(("list", sub_m)) => Ok(ListCommand::handler(sub_m).await?), - Some(("pick", sub_m)) => Ok(PickCommand::handler(sub_m).await?), - Some(("stat", sub_m)) => Ok(StatCommand::handler(sub_m).await?), - Some(("test", sub_m)) => Ok(TestCommand::handler(sub_m).await?), - Some(("completions", sub_m)) => Ok(completion_handler(sub_m, &mut cmd)?), - _ => Err(Error::MatchError), + match cli.command { + Some(Commands::Data(args)) => args.run().await, + Some(Commands::Edit(args)) => args.run().await, + Some(Commands::Exec(args)) => args.run().await, + Some(Commands::List(args)) => args.run().await, + Some(Commands::Pick(args)) => args.run().await, + Some(Commands::Stat(args)) => args.run().await, + Some(Commands::Test(args)) => args.run().await, + Some(Commands::Completions(args)) => { + let mut cmd = Cli::command(); + args.run(&mut cmd) + } + None => Err(Error::MatchError), } } diff --git a/src/cmd/completions.rs b/src/cmd/completions.rs new file mode 100644 index 0000000..94c626b --- /dev/null +++ b/src/cmd/completions.rs @@ -0,0 +1,35 @@ +//! Completions command +use crate::err::Error; +use clap::{Args, Command as ClapCommand}; +use clap_complete::{Generator, Shell, generate}; + +/// Completions command arguments +#[derive(Args)] +pub struct CompletionsArgs { + /// Shell type [possible values: bash, elvish, fish, powershell, zsh] + #[arg(value_parser = clap::value_parser!(Shell))] + pub shell: Option, +} + +fn get_completions_string( + generator: G, + cmd: &mut ClapCommand, +) -> Result { + let mut v: Vec = Vec::new(); + let name = cmd.get_name().to_string(); + generate(generator, cmd, name, &mut v); + Ok(String::from_utf8(v)?) +} + +impl CompletionsArgs { + /// Generate and print shell completions + pub fn run(&self, cmd: &mut ClapCommand) -> Result<(), Error> { + let shell = self + .shell + .or_else(Shell::from_env) + .ok_or(Error::MatchError)?; + let completions = get_completions_string(shell, cmd)?; + println!("{}", completions); + Ok(()) + } +} diff --git a/src/cmd/data.rs b/src/cmd/data.rs new file mode 100644 index 0000000..47444fa --- /dev/null +++ b/src/cmd/data.rs @@ -0,0 +1,66 @@ +//! Cache manager +use crate::{Error, cache::Cache, helper::Digit}; +use clap::Args; +use colored::Colorize; + +/// Data command arguments +#[derive(Args)] +pub struct DataArgs { + /// Delete cache + #[arg(short, long)] + pub delete: bool, + + /// Update cache + #[arg(short, long)] + pub update: bool, +} + +impl DataArgs { + /// `data` handler + pub async fn run(&self) -> Result<(), Error> { + use std::fs::File; + use std::path::Path; + + let cache = Cache::new()?; + let path = cache.0.conf.storage.cache()?; + let f = File::open(&path)?; + let len = format!("{}K", f.metadata()?.len() / 1000); + + let out = format!( + " {}{}", + Path::new(&path) + .file_name() + .ok_or(Error::NoneError)? + .to_string_lossy() + .to_string() + .digit(65 - (len.len() as i32)) + .bright_green(), + len + ); + + let mut title = "\n Cache".digit(63); + title.push_str("Size"); + title.push_str("\n "); + title.push_str(&"-".repeat(65)); + + let mut flags = 0; + if self.delete { + flags += 1; + cache.clean()?; + println!("{}", "ok!".bright_green()); + } + + if self.update { + flags += 1; + cache.update().await?; + println!("{}", "ok!".bright_green()); + } + + if flags == 0 { + println!("{}", title.bright_black()); + println!("{}\n", out); + } + + Ok(()) + } +} diff --git a/src/cmds/edit.rs b/src/cmd/edit.rs similarity index 72% rename from src/cmds/edit.rs rename to src/cmd/edit.rs index f611d6c..13033cf 100644 --- a/src/cmds/edit.rs +++ b/src/cmd/edit.rs @@ -1,85 +1,43 @@ //! Edit command -use super::Command; use crate::{Error, Result}; use anyhow::anyhow; -use async_trait::async_trait; -use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command as ClapCommand}; +use clap::Args; use std::collections::HashMap; -/// Abstract `edit` command -/// -/// ```sh -/// leetcode-edit -/// Edit question by id -/// -/// USAGE: -/// leetcode edit -/// -/// FLAGS: -/// -h, --help Prints help information -/// -V, --version Prints version information -/// -/// ARGS: -/// question id -/// ``` -pub struct EditCommand; - -#[async_trait] -impl Command for EditCommand { - /// `edit` usage - fn usage() -> ClapCommand { - ClapCommand::new("edit") - .about("Edit question") - .visible_alias("e") - .arg( - Arg::new("lang") - .short('l') - .long("lang") - .num_args(1) - .help("Edit with specific language"), - ) - .arg( - Arg::new("id") - .num_args(1) - .value_parser(clap::value_parser!(i32)) - .help("question id"), - ) - .arg( - Arg::new("daily") - .short('d') - .long("daily") - .help("Edit today's daily challenge") - .action(ArgAction::SetTrue), - ) - .group( - ArgGroup::new("question-id") - .args(["id", "daily"]) - .multiple(false) - .required(true), - ) - } +/// Edit command arguments +#[derive(Args)] +#[command(group = clap::ArgGroup::new("question-id").args(&["id", "daily"]).required(true))] +pub struct EditArgs { + /// Question id + #[arg(value_parser = clap::value_parser!(i32))] + pub id: Option, + + /// Edit today's daily challenge + #[arg(short = 'd', long)] + pub daily: bool, + + /// Edit with specific language + #[arg(short, long)] + pub lang: Option, +} +impl EditArgs { /// `edit` handler - async fn handler(m: &ArgMatches) -> Result<()> { - use crate::{cache::models::Question, Cache}; + pub async fn run(&self) -> Result<()> { + use crate::{Cache, cache::models::Question}; use std::fs::File; use std::io::Write; use std::path::Path; let cache = Cache::new()?; - let daily = m.get_one::("daily").unwrap_or(&false); - let daily_id = if *daily { + let daily_id = if self.daily { Some(cache.get_daily_problem_id().await?) } else { None }; - let id = m - .get_one::("id") - .copied() - .or(daily_id) - .ok_or(Error::NoneError)?; + let id = self.id.or(daily_id).ok_or(Error::NoneError)?; let problem = cache.get_problem(id)?; let mut conf = cache.to_owned().0.conf; @@ -88,11 +46,8 @@ impl Command for EditCommand { let p_desc_comment = problem.desc_comment(&conf); // condition language - if m.contains_id("lang") { - conf.code.lang = m - .get_one::("lang") - .ok_or(Error::NoneError)? - .to_string(); + if let Some(ref lang) = self.lang { + conf.code.lang = lang.clone(); conf.sync()?; } diff --git a/src/cmd/exec.rs b/src/cmd/exec.rs new file mode 100644 index 0000000..81bf557 --- /dev/null +++ b/src/cmd/exec.rs @@ -0,0 +1,38 @@ +//! Exec command +use crate::{Error, Result}; +use clap::Args; + +/// Exec command arguments +#[derive(Args)] +#[command(group = clap::ArgGroup::new("question-id").args(&["id", "daily"]).required(true))] +pub struct ExecArgs { + /// Question id + #[arg(value_parser = clap::value_parser!(i32))] + pub id: Option, + + /// Submit today's daily challenge + #[arg(short = 'd', long)] + pub daily: bool, +} + +impl ExecArgs { + /// `exec` handler + pub async fn run(&self) -> Result<()> { + use crate::cache::{Cache, Run}; + + let cache = Cache::new()?; + + let daily_id = if self.daily { + Some(cache.get_daily_problem_id().await?) + } else { + None + }; + + let id = self.id.or(daily_id).ok_or(Error::NoneError)?; + + let res = cache.exec_problem(id, Run::Submit, None).await?; + + println!("{}", res); + Ok(()) + } +} diff --git a/src/cmd/list.rs b/src/cmd/list.rs new file mode 100644 index 0000000..7460d78 --- /dev/null +++ b/src/cmd/list.rs @@ -0,0 +1,171 @@ +//! list subcommand - List leetcode problems +use crate::{cache::Cache, err::Error, helper::Digit}; +use clap::Args; + +static CATEGORY_HELP: &str = r#"Filter problems by category name +[algorithms, database, shell, concurrency] +"#; + +static QUERY_HELP: &str = r#"Filter questions by conditions: +Uppercase means negative +e = easy E = m+h +m = medium M = e+h +h = hard H = e+m +d = done D = not done +l = locked L = not locked +s = starred S = not starred"#; + +static LIST_AFTER_HELP: &str = r#"EXAMPLES: + leetcode list List all questions + leetcode list array List questions that has "array" in name, and this is letter non-sensitive + leetcode list -c database List questions that in database category + leetcode list -q eD List questions that with easy level and not done + leetcode list -t linked-list List questions that under tag "linked-list" + leetcode list -r 50 100 List questions that has id in between 50 and 100 +"#; + +/// List command arguments +#[derive(Args)] +#[command(after_help = LIST_AFTER_HELP)] +pub struct ListArgs { + /// Keyword in select query + pub keyword: Option, + + /// Filter problems by category name + #[arg(short, long, help = CATEGORY_HELP)] + pub category: Option, + + /// Invoking python scripts to filter questions + #[arg(short, long)] + pub plan: Option, + + /// Filter questions by conditions + #[arg(short, long, help = QUERY_HELP)] + pub query: Option, + + /// Filter questions by id range + #[arg(short, long, num_args = 2.., value_parser = clap::value_parser!(i32))] + pub range: Vec, + + /// Show statistics of listed problems + #[arg(short, long)] + pub stat: bool, + + /// Filter questions by tag + #[arg(short, long)] + pub tag: Option, +} + +impl ListArgs { + /// `list` command handler + pub async fn run(&self) -> Result<(), Error> { + trace!("Input list command..."); + + let cache = Cache::new()?; + let mut ps = cache.get_problems()?; + + // if cache doesn't exist, request a new copy + if ps.is_empty() { + cache.download_problems().await?; + return Box::pin(self.run()).await; + } + + // filtering... + // pym scripts + #[cfg(feature = "pym")] + { + if let Some(ref plan) = self.plan { + let ids = crate::pym::exec(plan)?; + crate::helper::squash(&mut ps, ids)?; + } + } + + // filter tag + if let Some(ref tag) = self.tag { + let ids = cache.get_tagged_questions(tag).await?; + crate::helper::squash(&mut ps, ids)?; + } + + // filter category + if let Some(ref category) = self.category { + ps.retain(|x| x.category == *category); + } + + // filter query + if let Some(ref query) = self.query { + crate::helper::filter(&mut ps, query.to_string()); + } + + // filter range + if self.range.len() >= 2 { + ps.retain(|x| self.range[0] <= x.fid && x.fid <= self.range[1]); + } + + // retain if keyword exists + if let Some(ref keyword) = self.keyword { + let lowercase_kw = keyword.to_lowercase(); + ps.retain(|x| x.name.to_lowercase().contains(&lowercase_kw)); + } + + // output problem lines sorted by [problem number] like + // [ 1 ] Two Sum + // [ 2 ] Add Two Numbers + ps.sort_unstable_by_key(|p| p.fid); + + let out: Vec = ps.iter().map(ToString::to_string).collect(); + println!("{}", out.join("\n")); + + // one more thing, filter stat + if self.stat { + let mut listed = 0; + let mut locked = 0; + let mut starred = 0; + let mut ac = 0; + let mut notac = 0; + let mut easy = 0; + let mut medium = 0; + let mut hard = 0; + + for p in ps { + listed += 1; + if p.starred { + starred += 1; + } + if p.locked { + locked += 1; + } + + match p.status.as_str() { + "ac" => ac += 1, + "notac" => notac += 1, + _ => {} + } + + match p.level { + 1 => easy += 1, + 2 => medium += 1, + 3 => hard += 1, + _ => {} + } + } + + let remain = listed - ac - notac; + println!( + " + Listed: {} Locked: {} Starred: {} + Accept: {} Not-Ac: {} Remain: {} + Easy : {} Medium: {} Hard: {}", + listed.digit(4), + locked.digit(4), + starred.digit(4), + ac.digit(4), + notac.digit(4), + remain.digit(4), + easy.digit(4), + medium.digit(4), + hard.digit(4), + ); + } + Ok(()) + } +} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs new file mode 100644 index 0000000..c594b32 --- /dev/null +++ b/src/cmd/mod.rs @@ -0,0 +1,32 @@ +//! All subcommands in leetcode-cli +//! +//! ```sh +//! SUBCOMMANDS: +//! data Manage Cache [aliases: d] +//! edit Edit question by id [aliases: e] +//! exec Submit solution [aliases: x] +//! list List problems [aliases: l] +//! pick Pick a problem [aliases: p] +//! stat Show simple chart about submissions [aliases: s] +//! test Test a question [aliases: t] +//! completions Generate shell completions [aliases: c] +//! help Prints this message or the help of the given subcommand(s) +//! ``` + +mod completions; +mod data; +mod edit; +mod exec; +mod list; +mod pick; +mod stat; +mod test; + +pub use completions::CompletionsArgs; +pub use data::DataArgs; +pub use edit::EditArgs; +pub use exec::ExecArgs; +pub use list::ListArgs; +pub use pick::PickArgs; +pub use stat::StatArgs; +pub use test::TestArgs; diff --git a/src/cmd/pick.rs b/src/cmd/pick.rs new file mode 100644 index 0000000..9297a93 --- /dev/null +++ b/src/cmd/pick.rs @@ -0,0 +1,174 @@ +//! Pick command +use crate::cache::models::Problem; +use crate::err::Error; +use clap::Args; + +static QUERY_HELP: &str = r#"Filter questions by conditions: +Uppercase means negative +e = easy E = m+h +m = medium M = e+h +h = hard H = e+m +d = done D = not done +l = locked L = not locked +s = starred S = not starred"#; + +/// Pick command arguments +#[derive(Args)] +pub struct PickArgs { + /// Problem id + #[arg(value_parser = clap::value_parser!(i32))] + pub id: Option, + + /// Problem name + #[arg(short = 'n', long)] + pub name: Option, + + /// Invoking python scripts to filter questions + #[arg(short = 'p', long)] + pub plan: Option, + + /// Filter questions by conditions + #[arg(short, long, help = QUERY_HELP)] + pub query: Option, + + /// Filter questions by tag + #[arg(short, long)] + pub tag: Option, + + /// Pick today's daily challenge + #[arg(short = 'd', long)] + pub daily: bool, +} + +impl PickArgs { + /// `pick` handler + pub async fn run(&self) -> Result<(), Error> { + use crate::cache::Cache; + use rand::RngExt; + + let cache = Cache::new()?; + let mut problems = cache.get_problems()?; + if problems.is_empty() { + cache.download_problems().await?; + return Box::pin(self.run()).await; + } + + // filtering... + // pym scripts + #[cfg(feature = "pym")] + { + if let Some(ref plan) = self.plan { + let ids = crate::pym::exec(plan)?; + crate::helper::squash(&mut problems, ids)?; + } + } + + // tag filter + if let Some(ref tag) = self.tag { + let ids = cache.clone().get_tagged_questions(tag).await?; + crate::helper::squash(&mut problems, ids)?; + } + + // query filter + if let Some(ref query) = self.query { + crate::helper::filter(&mut problems, query.to_string()); + } + + let daily_id = if self.daily { + Some(cache.get_daily_problem_id().await?) + } else { + None + }; + + let fid = if let Some(ref quesname) = self.name { + // check for name specified, or closest name + closest_named_problem(&problems, quesname).unwrap_or(1) + } else { + self.id.or(daily_id).unwrap_or_else(|| { + // Pick random without specify id + let problem = &problems[rand::rng().random_range(0..problems.len())]; + problem.fid + }) + }; + + let r = cache.get_question(fid).await; + + match r { + Ok(q) => println!("{}", q.desc()), + Err(e) => { + eprintln!("{:?}", e); + if let Error::Reqwest(_) = e { + Box::pin(self.run()).await?; + } + } + } + + Ok(()) + } +} + +// Returns the closest problem according to a scoring algorithm +// taking into account both the longest common subsequence and the size +// problem string (to compensate for smaller strings having smaller lcs). +// Returns None if there are no problems in the problem list +fn closest_named_problem(problems: &Vec, lookup_name: &str) -> Option { + let max_name_size: usize = problems.iter().map(|p| p.name.len()).max()?; + // Init table to the max name length of all the problems to share + // the same table allocation + let mut table: Vec = vec![0; (max_name_size + 1) * (lookup_name.len() + 1)]; + + // this is guaranteed because of the earlier max None propegation + assert!(!problems.is_empty()); + let mut max_score = 0; + let mut current_problem = &problems[0]; + for problem in problems { + // In case bug becomes bugged, always return the matching string + if problem.name == lookup_name { + return Some(problem.fid); + } + + let this_lcs = longest_common_subsequence(&mut table, &problem.name, lookup_name); + let this_score = this_lcs * (max_name_size - problem.name.len()); + + if this_score > max_score { + max_score = this_score; + current_problem = problem; + } + } + + Some(current_problem.fid) +} + +// Longest commong subsequence DP approach O(nm) space and time. Table must be at least +// (text1.len() + 1) * (text2.len() + 1) length or greater and is mutated every call +fn longest_common_subsequence(table: &mut [usize], text1: &str, text2: &str) -> usize { + assert!(table.len() >= (text1.len() + 1) * (text2.len() + 1)); + let height: usize = text1.len() + 1; + let width: usize = text2.len() + 1; + + // initialize base cases to 0 + for i in 0..height { + table[i * width + (width - 1)] = 0; + } + for j in 0..width { + table[((height - 1) * width) + j] = 0; + } + + let mut i: usize = height - 1; + let mut j: usize; + for c0 in text1.chars().rev() { + i -= 1; + j = width - 1; + for c1 in text2.chars().rev() { + j -= 1; + if c0.to_lowercase().next() == c1.to_lowercase().next() { + table[i * width + j] = 1 + table[(i + 1) * width + j + 1]; + } else { + let a = table[(i + 1) * width + j]; + let b = table[i * width + j + 1]; + table[i * width + j] = std::cmp::max(a, b); + } + } + } + table[0] +} diff --git a/src/cmds/stat.rs b/src/cmd/stat.rs similarity index 80% rename from src/cmds/stat.rs rename to src/cmd/stat.rs index 4b463da..4b079c0 100644 --- a/src/cmds/stat.rs +++ b/src/cmd/stat.rs @@ -1,36 +1,15 @@ //! status command -use super::Command; -use async_trait::async_trait; -use clap::{ArgMatches, Command as ClapCommand}; +use clap::Args; use colored::Colorize; -/// Abstract statues command -/// -/// ```sh -/// leetcode-stat -/// Show simple chart about submissions -/// -/// USAGE: -/// leetcode stat -/// -/// FLAGS: -/// -h, --help Prints help information -/// -V, --version Prints version information -/// ``` -pub struct StatCommand; - -#[async_trait] -impl Command for StatCommand { - /// `stat` usage - fn usage() -> ClapCommand { - ClapCommand::new("stat") - .about("Show simple chart about submissions") - .visible_alias("s") - } +/// Stat command arguments +#[derive(Args)] +pub struct StatArgs {} +impl StatArgs { /// `stat` handler - async fn handler(_m: &ArgMatches) -> Result<(), crate::err::Error> { - use crate::{helper::Digit, Cache}; + pub async fn run(&self) -> Result<(), crate::err::Error> { + use crate::{Cache, helper::Digit}; let cache = Cache::new()?; let res = cache.get_problems()?; diff --git a/src/cmd/test.rs b/src/cmd/test.rs new file mode 100644 index 0000000..a2d9c2a --- /dev/null +++ b/src/cmd/test.rs @@ -0,0 +1,109 @@ +//! Test command +use crate::{Error, Result}; +use clap::Args; + +/// Test command arguments +#[derive(Args)] +#[command(group = clap::ArgGroup::new("question-id").args(&["id", "daily"]).required(true))] +pub struct TestArgs { + /// Question id + #[arg(value_parser = clap::value_parser!(i32))] + pub id: Option, + + /// Custom testcase + pub testcase: Option, + + /// Test today's daily challenge + #[arg(short = 'd', long)] + pub daily: bool, + + /// Watch for file changes and test automatically + #[arg(short, long)] + pub watch: bool, +} + +impl TestArgs { + /// `test` handler + pub async fn run(&self) -> Result<()> { + use crate::cache::{Cache, Run}; + use crate::helper::code_path; + use notify::{Config as NotifyConfig, RecommendedWatcher, RecursiveMode, Watcher}; + use std::path::Path; + use std::sync::mpsc::channel; + use std::time::{Duration, Instant}; + + let cache = Cache::new()?; + let daily_id = if self.daily { + Some(cache.get_daily_problem_id().await?) + } else { + None + }; + + let id = self.id.or(daily_id).ok_or(Error::NoneError)?; + + let case_str: Option = self.testcase.as_ref().map(|case| case.replace("\\n", "\n")); + + if self.watch { + let problem = cache.get_problem(id)?; + let path_str = code_path(&problem, None)?; + let path = Path::new(&path_str); + let parent = path + .parent() + .ok_or_else(|| anyhow::anyhow!("Could not get parent directory"))?; + + let (tx, rx) = channel(); + let mut watcher = RecommendedWatcher::new(tx, NotifyConfig::default()) + .map_err(|e| anyhow::anyhow!("Failed to create watcher: {}", e))?; + + // Watch parent directory to handle file creation and atomic saves (which may delete/rename) + watcher + .watch(parent, RecursiveMode::NonRecursive) + .map_err(|e| anyhow::anyhow!("Failed to watch parent directory: {}", e))?; + + if path.exists() { + println!("Watching for changes in {}...", path_str); + let res = cache.exec_problem(id, Run::Test, case_str.clone()).await?; + println!("{}", res); + } else { + println!("File {} does not exist. Waiting for creation...", path_str); + } + + let mut last_run = Instant::now() - Duration::from_secs(1); + while let Ok(res_event) = rx.recv() { + match res_event { + Ok(event) if event.paths.iter().any(|p| p == path) => { + if event.kind.is_create() { + println!("File created, watching for changes..."); + } else if event.kind.is_modify() { + // Debounce: ignore events within 500ms of the last run + if last_run.elapsed() < Duration::from_millis(500) { + continue; + } + + // Wait for atomic save to complete (e.g. file content flush) + std::thread::sleep(Duration::from_millis(200)); + // Drain any subsequent events generated during the sleep + while rx.try_recv().is_ok() {} + + if !path.exists() { + continue; + } + println!("File changed, testing again..."); + match cache.exec_problem(id, Run::Test, case_str.clone()).await { + Ok(res) => println!("{}", res), + Err(e) => println!("Error: {}", e), + } + last_run = Instant::now(); + } + } + Err(e) => println!("watch error: {:?}", e), + _ => {} + } + } + } else { + let res = cache.exec_problem(id, Run::Test, case_str).await?; + println!("{}", res); + } + Ok(()) + } +} diff --git a/src/cmds/completions.rs b/src/cmds/completions.rs deleted file mode 100644 index bc675de..0000000 --- a/src/cmds/completions.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Completions command - -use super::Command; -use crate::err::Error; -use async_trait::async_trait; -use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand}; -use clap_complete::{generate, Generator, Shell}; - -/// Abstract shell completions command -/// -/// ```sh -/// Generate shell Completions - -/// USAGE: -/// leetcode completions - -/// ARGUMENTS: -/// [possible values: bash, elvish, fish, powershell, zsh] -/// ``` -pub struct CompletionCommand; - -#[async_trait] -impl Command for CompletionCommand { - /// `pick` usage - fn usage() -> ClapCommand { - ClapCommand::new("completions") - .about("Generate shell Completions") - .visible_alias("c") - .arg( - Arg::new("shell") - .action(ArgAction::Set) - .value_parser(clap::value_parser!(Shell)), - ) - } - - async fn handler(_m: &ArgMatches) -> Result<(), Error> { - // defining custom handler to print the completions. Handler method signature limits taking - // other params. We need &ArgMatches and &mut ClapCommand to generate completions. - println!("Don't use this handler. Does not implement the functionality to print completions. Use completions_handler() below."); - Ok(()) - } -} - -fn get_completions_string(gen: G, cmd: &mut ClapCommand) -> Result { - let mut v: Vec = Vec::new(); - let name = cmd.get_name().to_string(); - generate(gen, cmd, name, &mut v); - Ok(String::from_utf8(v)?) -} - -pub fn completion_handler(m: &ArgMatches, cmd: &mut ClapCommand) -> Result<(), Error> { - let shell = *m.get_one::("shell").unwrap_or( - // if shell value is not provided try to get from the environment - &Shell::from_env().ok_or(Error::MatchError)?, - ); - let completions = get_completions_string(shell, cmd)?; - println!("{}", completions); - Ok(()) -} diff --git a/src/cmds/data.rs b/src/cmds/data.rs deleted file mode 100644 index cb66e39..0000000 --- a/src/cmds/data.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Cache managger -use super::Command; -use crate::{cache::Cache, helper::Digit, Error}; -use async_trait::async_trait; -use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand}; -use colored::Colorize; - -/// Abstract `data` command -/// -/// ```sh -/// leetcode-data -/// Manage Cache -/// -/// USAGE: -/// leetcode data [FLAGS] -/// -/// FLAGS: -/// -d, --delete Delete cache -/// -u, --update Update cache -/// -h, --help Prints help information -/// -V, --version Prints version information -/// ``` -pub struct DataCommand; - -#[async_trait] -impl Command for DataCommand { - /// `data` command usage - fn usage() -> ClapCommand { - ClapCommand::new("data") - .about("Manage Cache") - .visible_alias("d") - .arg( - Arg::new("delete") - .display_order(1) - .short('d') - .long("delete") - .help("Delete cache") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("update") - .display_order(2) - .short('u') - .long("update") - .help("Update cache") - .action(ArgAction::SetTrue), - ) - } - - /// `data` handler - async fn handler(m: &ArgMatches) -> Result<(), Error> { - use std::fs::File; - use std::path::Path; - - let cache = Cache::new()?; - let path = cache.0.conf.storage.cache()?; - let f = File::open(&path)?; - let len = format!("{}K", f.metadata()?.len() / 1000); - - let out = format!( - " {}{}", - Path::new(&path) - .file_name() - .ok_or(Error::NoneError)? - .to_string_lossy() - .to_string() - .digit(65 - (len.len() as i32)) - .bright_green(), - len - ); - - let mut title = "\n Cache".digit(63); - title.push_str("Size"); - title.push_str("\n "); - title.push_str(&"-".repeat(65)); - - let mut flags = 0; - if m.get_flag("delete") { - flags += 1; - cache.clean()?; - println!("{}", "ok!".bright_green()); - } - - if m.get_flag("update") { - flags += 1; - cache.update().await?; - println!("{}", "ok!".bright_green()); - } - - if flags == 0 { - println!("{}", title.bright_black()); - println!("{}\n", out); - } - - Ok(()) - } -} diff --git a/src/cmds/exec.rs b/src/cmds/exec.rs deleted file mode 100644 index d6538f8..0000000 --- a/src/cmds/exec.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Exec command -use super::Command; -use crate::{Error, Result}; -use async_trait::async_trait; -use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command as ClapCommand}; - -/// Abstract Exec Command -/// -/// ```sh -/// leetcode-exec -/// Submit solution -/// -/// USAGE: -/// leetcode exec -/// -/// FLAGS: -/// -h, --help Prints help information -/// -V, --version Prints version information -/// -/// ARGS: -/// question id -/// ``` -pub struct ExecCommand; - -#[async_trait] -impl Command for ExecCommand { - /// `exec` usage - fn usage() -> ClapCommand { - ClapCommand::new("exec") - .about("Submit solution") - .visible_alias("x") - .arg( - Arg::new("id") - .num_args(1) - .required(true) - .value_parser(clap::value_parser!(i32)) - .help("question id"), - ) - .arg( - Arg::new("daily") - .short('d') - .long("daily") - .help("Exec today's daily challenge") - .action(ArgAction::SetTrue), - ) - .group( - ArgGroup::new("question-id") - .args(["id", "daily"]) - .multiple(false) - .required(true), - ) - } - - /// `exec` handler - async fn handler(m: &ArgMatches) -> Result<()> { - use crate::cache::{Cache, Run}; - - let cache = Cache::new()?; - - let daily = m.get_one::("daily").unwrap_or(&false); - let daily_id = if *daily { - Some(cache.get_daily_problem_id().await?) - } else { - None - }; - - let id = m - .get_one::("id") - .copied() - .or(daily_id) - .ok_or(Error::NoneError)?; - - let res = cache.exec_problem(id, Run::Submit, None).await?; - - println!("{}", res); - Ok(()) - } -} diff --git a/src/cmds/list.rs b/src/cmds/list.rs deleted file mode 100644 index ae6df47..0000000 --- a/src/cmds/list.rs +++ /dev/null @@ -1,258 +0,0 @@ -//! list subcomAmand - List leetcode problems -//! -//! ```sh -//! leetcode-list -//! List problems -//! -//! USAGE: -//! leetcode list [FLAGS] [OPTIONS] [keyword] -//! -//! FLAGS: -//! -h, --help Prints help information -//! -s, --stat Show statistics of listed problems -//! -V, --version Prints version information -//! -//! OPTIONS: -//! -c, --category Filter problems by category name -//! [algorithms, database, shell, concurrency] -//! -q, --query Filter questions by conditions: -//! Uppercase means negative -//! e = easy E = m+h -//! m = medium M = e+h -//! h = hard H = e+m -//! d = done D = not done -//! l = locked L = not locked -//! s = starred S = not starred -//! -//! ARGS: -//! Keyword in select query -//! -//! EXAMPLES: -//! leetcode list List all questions -//! leetcode list array List questions that has "array" in name -//! leetcode list -c database List questions that in database category -//! leetcode list -q eD List questions that with easy level and not done -//! ``` -use super::Command; -use crate::{cache::Cache, err::Error, helper::Digit}; -use async_trait::async_trait; -use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand}; -/// Abstract `list` command -/// -/// ## handler -/// + try to request cache -/// + prints the list -/// + if cache doesn't exist, download problems list -/// + ... -pub struct ListCommand; - -static CATEGORY_HELP: &str = r#"Filter problems by category name -[algorithms, database, shell, concurrency] -"#; - -static QUERY_HELP: &str = r#"Filter questions by conditions: -Uppercase means negative -e = easy E = m+h -m = medium M = e+h -h = hard H = e+m -d = done D = not done -l = locked L = not locked -s = starred S = not starred"#; - -static LIST_AFTER_HELP: &str = r#"EXAMPLES: - leetcode list List all questions - leetcode list array List questions that has "array" in name, and this is letter non-sensitive - leetcode list -c database List questions that in database category - leetcode list -q eD List questions that with easy level and not done - leetcode list -t linked-list List questions that under tag "linked-list" - leetcode list -r 50 100 List questions that has id in between 50 and 100 -"#; - -/// implement Command trait for `list` -#[async_trait] -impl Command for ListCommand { - /// `list` command usage - fn usage() -> ClapCommand { - ClapCommand::new("list") - .about("List problems") - .visible_alias("l") - .arg( - Arg::new("category") - .short('c') - .long("category") - .num_args(1) - .help(CATEGORY_HELP), - ) - .arg( - Arg::new("plan") - .short('p') - .long("plan") - .num_args(1) - .help("Invoking python scripts to filter questions"), - ) - .arg( - Arg::new("query") - .short('q') - .long("query") - .num_args(1) - .help(QUERY_HELP), - ) - .arg( - Arg::new("range") - .short('r') - .long("range") - .num_args(2..) - .value_parser(clap::value_parser!(i32)) - .help("Filter questions by id range"), - ) - .after_help(LIST_AFTER_HELP) - .arg( - Arg::new("stat") - .short('s') - .long("stat") - .help("Show statistics of listed problems") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("tag") - .short('t') - .long("tag") - .num_args(1) - .help("Filter questions by tag"), - ) - .arg( - Arg::new("keyword") - .num_args(1) - .help("Keyword in select query"), - ) - } - - /// `list` command handler - /// - /// List commands contains "-c", "-q", "-s" flags. - /// + matches with `-c` will override the default keyword. - /// + `-qs` - async fn handler(m: &ArgMatches) -> Result<(), Error> { - trace!("Input list command..."); - - let cache = Cache::new()?; - let mut ps = cache.get_problems()?; - - // if cache doesn't exist, request a new copy - if ps.is_empty() { - cache.download_problems().await?; - return Self::handler(m).await; - } - - // filtering... - // pym scripts - #[cfg(feature = "pym")] - { - if m.contains_id("plan") { - let ids = crate::pym::exec(m.get_one::("plan").unwrap_or(&"".to_string()))?; - crate::helper::squash(&mut ps, ids)?; - } - } - - // filter tag - if m.contains_id("tag") { - let ids = cache - .get_tagged_questions(m.get_one::("tag").unwrap_or(&"".to_string())) - .await?; - crate::helper::squash(&mut ps, ids)?; - } - - // filter category - if m.contains_id("category") { - ps.retain(|x| { - x.category - == *m - .get_one::("category") - .unwrap_or(&"algorithms".to_string()) - }); - } - - // filter query - if m.contains_id("query") { - let query = m.get_one::("query").ok_or(Error::NoneError)?; - crate::helper::filter(&mut ps, query.to_string()); - } - - // filter range - if m.contains_id("range") { - let num_range: Vec = m - .get_many::("range") - .ok_or(Error::NoneError)? - .copied() - .collect(); - ps.retain(|x| num_range[0] <= x.fid && x.fid <= num_range[1]); - } - - // retain if keyword exists - if let Some(keyword) = m.get_one::("keyword") { - let lowercase_kw = keyword.to_lowercase(); - ps.retain(|x| x.name.to_lowercase().contains(&lowercase_kw)); - } - - // output problem lines sorted by [problem number] like - // [ 1 ] Two Sum - // [ 2 ] Add Two Numbers - ps.sort_unstable_by_key(|p| p.fid); - - let out: Vec = ps.iter().map(ToString::to_string).collect(); - println!("{}", out.join("\n")); - - // one more thing, filter stat - if m.contains_id("stat") { - let mut listed = 0; - let mut locked = 0; - let mut starred = 0; - let mut ac = 0; - let mut notac = 0; - let mut easy = 0; - let mut medium = 0; - let mut hard = 0; - - for p in ps { - listed += 1; - if p.starred { - starred += 1; - } - if p.locked { - locked += 1; - } - - match p.status.as_str() { - "ac" => ac += 1, - "notac" => notac += 1, - _ => {} - } - - match p.level { - 1 => easy += 1, - 2 => medium += 1, - 3 => hard += 1, - _ => {} - } - } - - let remain = listed - ac - notac; - println!( - " - Listed: {} Locked: {} Starred: {} - Accept: {} Not-Ac: {} Remain: {} - Easy : {} Medium: {} Hard: {}", - listed.digit(4), - locked.digit(4), - starred.digit(4), - ac.digit(4), - notac.digit(4), - remain.digit(4), - easy.digit(4), - medium.digit(4), - hard.digit(4), - ); - } - Ok(()) - } -} diff --git a/src/cmds/mod.rs b/src/cmds/mod.rs deleted file mode 100644 index 1124532..0000000 --- a/src/cmds/mod.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! All subcommands in leetcode-cli -//! -//! ```sh -//! SUBCOMMANDS: -//! data Manage Cache [aliases: d] -//! edit Edit question by id [aliases: e] -//! list List problems [aliases: l] -//! pick Pick a problem [aliases: p] -//! stat Show simple chart about submissions [aliases: s] -//! test Edit question by id [aliases: t] -//! help Prints this message or the help of the given subcommand(s) -//! ``` -use crate::err::Error; -use async_trait::async_trait; -use clap::{ArgMatches, Command as ClapCommand}; - -/// Abstract commands' trait. -#[async_trait] -pub trait Command { - /// Usage of the specific command - fn usage() -> ClapCommand; - - /// The handler will deal [args, options,...] from the command-line - async fn handler(m: &ArgMatches) -> Result<(), Error>; -} - -mod completions; -mod data; -mod edit; -mod exec; -mod list; -mod pick; -mod stat; -mod test; -pub use completions::{completion_handler, CompletionCommand}; -pub use data::DataCommand; -pub use edit::EditCommand; -pub use exec::ExecCommand; -pub use list::ListCommand; -pub use pick::PickCommand; -pub use stat::StatCommand; -pub use test::TestCommand; diff --git a/src/cmds/pick.rs b/src/cmds/pick.rs deleted file mode 100644 index aba8166..0000000 --- a/src/cmds/pick.rs +++ /dev/null @@ -1,244 +0,0 @@ -//! Pick command -use super::Command; -use crate::cache::models::Problem; -use crate::err::Error; -use async_trait::async_trait; -use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand}; -/// Abstract pick command -/// -/// ```sh -/// leetcode-pick -/// Pick a problem -/// -/// USAGE: -/// leetcode pick [OPTIONS] [id] -/// -/// FLAGS: -/// -h, --help Prints help information -/// -V, --version Prints version information -/// -/// OPTIONS: -/// -q, --query Filter questions by conditions: -/// Uppercase means negative -/// e = easy E = m+h -/// m = medium M = e+h -/// h = hard H = e+m -/// d = done D = not done -/// l = locked L = not locked -/// s = starred S = not starred -/// -/// ARGS: -/// Problem id -/// ``` -pub struct PickCommand; - -static QUERY_HELP: &str = r#"Filter questions by conditions: -Uppercase means negative -e = easy E = m+h -m = medium M = e+h -h = hard H = e+m -d = done D = not done -l = locked L = not locked -s = starred S = not starred"#; - -#[async_trait] -impl Command for PickCommand { - /// `pick` usage - fn usage() -> ClapCommand { - ClapCommand::new("pick") - .about("Pick a problem") - .visible_alias("p") - .arg( - Arg::new("name") - .short('n') - .long("name") - .value_parser(clap::value_parser!(String)) - .help("Problem name") - .num_args(1), - ) - .arg( - Arg::new("id") - .value_parser(clap::value_parser!(i32)) - .help("Problem id") - .num_args(1), - ) - .arg( - Arg::new("plan") - .short('p') - .long("plan") - .num_args(1) - .help("Invoking python scripts to filter questions"), - ) - .arg( - Arg::new("query") - .short('q') - .long("query") - .num_args(1) - .help(QUERY_HELP), - ) - .arg( - Arg::new("tag") - .short('t') - .long("tag") - .num_args(1) - .help("Filter questions by tag"), - ) - .arg( - Arg::new("daily") - .short('d') - .long("daily") - .help("Pick today's daily challenge") - .action(ArgAction::SetTrue), - ) - } - - /// `pick` handler - async fn handler(m: &ArgMatches) -> Result<(), Error> { - use crate::cache::Cache; - use rand::Rng; - - let cache = Cache::new()?; - let mut problems = cache.get_problems()?; - if problems.is_empty() { - cache.download_problems().await?; - Self::handler(m).await?; - return Ok(()); - } - - // filtering... - // pym scripts - #[cfg(feature = "pym")] - { - if m.contains_id("plan") { - let ids = crate::pym::exec(m.get_one::("plan").unwrap_or(&"".to_string()))?; - crate::helper::squash(&mut problems, ids)?; - } - } - - // tag filter - if m.contains_id("tag") { - let ids = cache - .clone() - .get_tagged_questions(m.get_one::("tag").unwrap_or(&"".to_string())) - .await?; - crate::helper::squash(&mut problems, ids)?; - } - - // query filter - if m.contains_id("query") { - let query = m.get_one::("query").ok_or(Error::NoneError)?; - crate::helper::filter(&mut problems, query.to_string()); - } - - let daily = m.get_one::("daily").unwrap_or(&false); - let daily_id = if *daily { - Some(cache.get_daily_problem_id().await?) - } else { - None - }; - - let fid = match m.contains_id("name") { - // check for name specified, or closest name - true => { - match m.get_one::("name") { - Some(quesname) => closest_named_problem(&problems, quesname).unwrap_or(1), - None => { - // Pick random without specify id - let problem = &problems[rand::rng().random_range(0..problems.len())]; - problem.fid - } - } - } - false => { - m.get_one::("id") - .copied() - .or(daily_id) - .unwrap_or_else(|| { - // Pick random without specify id - let problem = &problems[rand::rng().random_range(0..problems.len())]; - problem.fid - }) - } - }; - - let r = cache.get_question(fid).await; - - match r { - Ok(q) => println!("{}", q.desc()), - Err(e) => { - eprintln!("{:?}", e); - if let Error::Reqwest(_) = e { - Self::handler(m).await?; - } - } - } - - Ok(()) - } -} - -// Returns the closest problem according to a scoring algorithm -// taking into account both the longest common subsequence and the size -// problem string (to compensate for smaller strings having smaller lcs). -// Returns None if there are no problems in the problem list -fn closest_named_problem(problems: &Vec, lookup_name: &str) -> Option { - let max_name_size: usize = problems.iter().map(|p| p.name.len()).max()?; - // Init table to the max name length of all the problems to share - // the same table allocation - let mut table: Vec = vec![0; (max_name_size + 1) * (lookup_name.len() + 1)]; - - // this is guaranteed because of the earlier max None propegation - assert!(!problems.is_empty()); - let mut max_score = 0; - let mut current_problem = &problems[0]; - for problem in problems { - // In case bug becomes bugged, always return the matching string - if problem.name == lookup_name { - return Some(problem.fid); - } - - let this_lcs = longest_common_subsequence(&mut table, &problem.name, lookup_name); - let this_score = this_lcs * (max_name_size - problem.name.len()); - - if this_score > max_score { - max_score = this_score; - current_problem = problem; - } - } - - Some(current_problem.fid) -} - -// Longest commong subsequence DP approach O(nm) space and time. Table must be at least -// (text1.len() + 1) * (text2.len() + 1) length or greater and is mutated every call -fn longest_common_subsequence(table: &mut [usize], text1: &str, text2: &str) -> usize { - assert!(table.len() >= (text1.len() + 1) * (text2.len() + 1)); - let height: usize = text1.len() + 1; - let width: usize = text2.len() + 1; - - // initialize base cases to 0 - for i in 0..height { - table[i * width + (width - 1)] = 0; - } - for j in 0..width { - table[((height - 1) * width) + j] = 0; - } - - let mut i: usize = height - 1; - let mut j: usize; - for c0 in text1.chars().rev() { - i -= 1; - j = width - 1; - for c1 in text2.chars().rev() { - j -= 1; - if c0.to_lowercase().next() == c1.to_lowercase().next() { - table[i * width + j] = 1 + table[(i + 1) * width + j + 1]; - } else { - let a = table[(i + 1) * width + j]; - let b = table[i * width + j + 1]; - table[i * width + j] = std::cmp::max(a, b); - } - } - } - table[0] -} diff --git a/src/cmds/test.rs b/src/cmds/test.rs deleted file mode 100644 index 6d36920..0000000 --- a/src/cmds/test.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Test command -use super::Command; -use crate::{Error, Result}; -use async_trait::async_trait; -use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command as ClapCommand}; - -/// Abstract Test Command -/// -/// ```sh -/// leetcode-test -/// Edit question by id -/// -/// USAGE: -/// leetcode test -/// -/// FLAGS: -/// -h, --help Prints help information -/// -V, --version Prints version information -/// -/// ARGS: -/// question id -/// ``` -pub struct TestCommand; - -#[async_trait] -impl Command for TestCommand { - /// `test` usage - fn usage() -> ClapCommand { - ClapCommand::new("test") - .about("Test a question") - .visible_alias("t") - .arg( - Arg::new("id") - .num_args(1) - .value_parser(clap::value_parser!(i32)) - .help("question id"), - ) - .arg( - Arg::new("testcase") - .num_args(1) - .required(false) - .help("custom testcase"), - ) - .arg( - Arg::new("daily") - .short('d') - .long("daily") - .help("Test today's daily challenge") - .action(ArgAction::SetTrue), - ) - .group( - ArgGroup::new("question-id") - .args(["id", "daily"]) - .multiple(false) - .required(true), - ) - } - - /// `test` handler - async fn handler(m: &ArgMatches) -> Result<()> { - use crate::cache::{Cache, Run}; - - let cache = Cache::new()?; - - let daily = m.get_one::("daily").unwrap_or(&false); - let daily_id = if *daily { - Some(cache.get_daily_problem_id().await?) - } else { - None - }; - - let id = m - .get_one::("id") - .copied() - .or(daily_id) - .ok_or(Error::NoneError)?; - - let testcase = m.get_one::("testcase"); - let case_str: Option = match testcase { - Some(case) => Option::from(case.replace("\\n", "\n")), - _ => None, - }; - let res = cache.exec_problem(id, Run::Test, case_str).await?; - - println!("{}", res); - Ok(()) - } -} diff --git a/src/config/code.rs b/src/config/code.rs index b842657..6e9be74 100644 --- a/src/config/code.rs +++ b/src/config/code.rs @@ -1,12 +1,20 @@ //! Code in config use serde::{Deserialize, Serialize}; +const PICK_DEFAULT: &str = "${fid}.${slug}"; fn default_pick() -> String { - "${fid}.${slug}".into() + PICK_DEFAULT.into() } -fn default_submission() -> String { - "${fid}.${slug}.${sid}.${ac}".into() +fn is_default_pick(t: &str) -> bool { + t == PICK_DEFAULT +} + +fn is_default_string(t: &str) -> bool { + t.is_empty() +} +fn is_default_bool(t: &bool) -> bool { + !t } /// Code config @@ -18,27 +26,25 @@ pub struct Code { pub editor_args: Option>, #[serde(rename(serialize = "editor-envs"), alias = "editor-envs", default)] pub editor_envs: Option>, - #[serde(default, skip_serializing)] + #[serde(default, skip_serializing_if = "is_default_bool")] pub edit_code_marker: bool, - #[serde(default, skip_serializing)] + #[serde(default, skip_serializing_if = "is_default_string")] pub start_marker: String, - #[serde(default, skip_serializing)] + #[serde(default, skip_serializing_if = "is_default_string")] pub end_marker: String, #[serde(rename(serialize = "inject_before"), alias = "inject_before", default)] pub inject_before: Option>, #[serde(rename(serialize = "inject_after"), alias = "inject_after", default)] pub inject_after: Option>, - #[serde(default, skip_serializing)] + #[serde(default, skip_serializing_if = "is_default_bool")] pub comment_problem_desc: bool, - #[serde(default, skip_serializing)] + #[serde(default, skip_serializing_if = "is_default_string")] pub comment_leading: String, - #[serde(default, skip_serializing)] + #[serde(default, skip_serializing_if = "is_default_bool")] pub test: bool, pub lang: String, - #[serde(default = "default_pick", skip_serializing)] + #[serde(default = "default_pick", skip_serializing_if = "is_default_pick")] pub pick: String, - #[serde(default = "default_submission", skip_serializing)] - pub submission: String, } impl Default for Code { @@ -57,7 +63,6 @@ impl Default for Code { test: true, lang: "rust".into(), pick: "${fid}.${slug}".into(), - submission: "${fid}.${slug}.${sid}.${ac}".into(), } } } diff --git a/src/config/cookies.rs b/src/config/cookies.rs index 8492780..afc59eb 100644 --- a/src/config/cookies.rs +++ b/src/config/cookies.rs @@ -78,10 +78,10 @@ impl Cookies { if let Ok(session) = std::env::var(LEETCODE_SESSION_ENV) { self.session = session; } - if let Ok(site) = std::env::var(LEETCODE_SITE_ENV) { - if let Ok(leetcode_site) = LeetcodeSite::from_str(&site) { - self.site = leetcode_site; - } + if let Ok(site) = std::env::var(LEETCODE_SITE_ENV) + && let Ok(leetcode_site) = LeetcodeSite::from_str(&site) + { + self.site = leetcode_site; } self } diff --git a/src/config/mod.rs b/src/config/mod.rs index f3f5e40..de8bffc 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -6,8 +6,8 @@ //! + Edit leetcode.toml at `~/.leetcode/leetcode.toml` directly //! + Use `leetcode config` to update it use crate::{ - config::{code::Code, cookies::Cookies, storage::Storage, sys::Sys}, Error, Result, + config::{code::Code, cookies::Cookies, storage::Storage, sys::Sys}, }; use serde::{Deserialize, Serialize}; use std::{fs, path::Path}; diff --git a/src/config/storage.rs b/src/config/storage.rs index 0395b1f..e3ee69f 100644 --- a/src/config/storage.rs +++ b/src/config/storage.rs @@ -3,12 +3,12 @@ use crate::{Error, Result}; use serde::{Deserialize, Serialize}; use std::{fs, path::PathBuf}; +/// Name of the local sqlite cache file under `root`. +const CACHE: &str = "Problems"; + /// Locate code files -/// -/// + cache -> the path to cache #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Storage { - cache: String, code: String, root: String, scripts: Option, @@ -17,7 +17,6 @@ pub struct Storage { impl Default for Storage { fn default() -> Self { Self { - cache: "Problems".into(), code: "code".into(), scripts: Some("scripts".into()), root: "~/.leetcode".into(), @@ -44,7 +43,7 @@ impl Storage { fs::DirBuilder::new().recursive(true).create(&root)?; } - Ok(root.join("Problems").to_string_lossy().to_string()) + Ok(root.join(CACHE).to_string_lossy().to_string()) } /// get code path diff --git a/src/err.rs b/src/err.rs index cf256cf..a89ecd6 100644 --- a/src/err.rs +++ b/src/err.rs @@ -1,5 +1,4 @@ //! Errors in leetcode-cli -use crate::cmds::{Command, DataCommand}; use anyhow::anyhow; use colored::Colorize; @@ -67,10 +66,6 @@ pub enum Error { Io(#[from] std::io::Error), #[error(transparent)] Anyhow(#[from] anyhow::Error), - #[error(transparent)] - Keyring(#[from] keyring::Error), - #[error(transparent)] - OpenSSL(#[from] openssl::error::ErrorStack), #[cfg(feature = "pym")] #[error(transparent)] Pyo3(#[from] pyo3::PyErr), @@ -79,12 +74,9 @@ pub enum Error { impl std::convert::From for Error { fn from(err: diesel::result::Error) -> Self { match err { - diesel::result::Error::NotFound => { - DataCommand::usage().print_help().unwrap_or(()); - Error::Anyhow(anyhow!( - "NotFound, you may update cache, and try it again\r\n" - )) - } + diesel::result::Error::NotFound => Error::Anyhow(anyhow!( + "NotFound, you may update cache with `leetcode data -u`, and try it again\r\n" + )), _ => Error::Anyhow(anyhow!("{err}")), } } diff --git a/src/flag.rs b/src/flag.rs deleted file mode 100644 index 5517153..0000000 --- a/src/flag.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Flags in leetcode-cli -//! -//! ```sh -//! FLAGS: -//! -d, --debug debug mode -//! -h, --help Prints help information -//! -V, --version Prints version information -//! ``` -use crate::err::Error; -use clap::{Arg, ArgAction}; -use env_logger::Env; - -/// Abstract flag trait -pub trait Flag { - fn usage() -> Arg; - fn handler() -> Result<(), Error>; -} - -/// Debug logger -pub struct Debug; - -impl Flag for Debug { - fn usage() -> Arg { - Arg::new("debug") - .short('d') - .long("debug") - .help("debug mode") - .action(ArgAction::SetTrue) - } - - fn handler() -> Result<(), Error> { - env_logger::Builder::from_env(Env::default().default_filter_or("debug")).init(); - - Ok(()) - } -} diff --git a/src/helper.rs b/src/helper.rs index 321828c..af43954 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -154,13 +154,9 @@ mod html { }); let frag = Html::parse_fragment(&res); - - let res = frag - .root_element() + frag.root_element() .text() - .fold(String::new(), |acc, e| acc + e); - - res + .fold(String::new(), |acc, e| acc + e) } } } @@ -191,7 +187,7 @@ mod file { } } - use crate::{cache::models::Problem, Error}; + use crate::{Error, cache::models::Problem}; /// Generate test cases path by fid pub fn test_cases_path(problem: &Problem) -> crate::Result { diff --git a/src/lib.rs b/src/lib.rs index 39a89e6..4b91e83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,233 +1,4 @@ -//! # leetcode-cli -//! [![doc](https://img.shields.io/badge/current-docs-green.svg)](https://docs.rs/leetcode-cli/) -//! [![Crates.io](https://img.shields.io/crates/v/leetcode-cli.svg)](https://crates.io/crates/leetcode-cli) -//! [![Crates.io](https://img.shields.io/crates/d/leetcode-cli.svg)](https://crates.io/crates/leetcode-cli) -//! [![LICENSE](https://img.shields.io/crates/l/leetcode-cli.svg)](https://choosealicense.com/licenses/mit/) -//! -//! ## Installing -//! -//! ```sh -//! cargo install leetcode-cli -//! ``` -//! -//! ## Usage -//! -//! **Please make sure you have logined in `leetcode.com` with `chrome`**, more info plz checkout [this](#cookies) -//! -//! ```sh -//! leetcode 0.3.10 -//! May the Code be with You 👻 -//! -//! USAGE: -//! leetcode [FLAGS] [SUBCOMMAND] -//! -//! FLAGS: -//! -d, --debug debug mode -//! -h, --help Prints help information -//! -V, --version Prints version information -//! -//! SUBCOMMANDS: -//! data Manage Cache [aliases: d] -//! edit Edit question by id [aliases: e] -//! exec Submit solution [aliases: x] -//! list List problems [aliases: l] -//! pick Pick a problem [aliases: p] -//! stat Show simple chart about submissions [aliases: s] -//! test Edit question by id [aliases: t] -//! help Prints this message or the help of the given subcommand(s) -//! ``` -//! -//! ## Example -//! -//! For example, if your config is: -//! -//! ```toml -//! [code] -//! lang = "rust" -//! editor = "emacs" -//! ``` -//! -//! #### 1. pick -//! -//! ```sh -//! leetcode pick 1 -//! ``` -//! -//! ```sh -//! [1] Two Sum is on the run... -//! -//! -//! Given an array of integers, return indices of the two numbers such that they add up to a specific target. -//! -//! You may assume that each input would have exactly one solution, and you may not use the same element twice. -//! -//! -------------------------------------------------- -//! -//! Example: -//! -//! -//! Given nums = [2, 7, 11, 15], target = 9, -//! -//! Because nums[0] + nums[1] = 2 + 7 = 9, -//! return [0, 1]. -//! ``` -//! -//! #### 2. edit -//! -//! ```sh -//! leetcode edit 1 -//! ``` -//! -//! ```rust -//! # struct Solution; -//! impl Solution { -//! pub fn two_sum(nums: Vec, target: i32) -> Vec { -//! use std::collections::HashMap; -//! let mut m: HashMap = HashMap::new(); -//! -//! for (i, e) in nums.iter().enumerate() { -//! if let Some(v) = m.get(&(target - e)) { -//! return vec![*v, i as i32]; -//! } -//! -//! m.insert(*e, i as i32).unwrap_or_default(); -//! } -//! -//! return vec![]; -//! } -//! } -//! ``` -//! -//! #### 3. test -//! -//! ```sh -//! leetcode test 1 -//! ``` -//! -//! ```sh -//! -//! Accepted Runtime: 0 ms -//! -//! Your input: [2,7,11,15], 9 -//! Output: [0,1] -//! Expected: [0,1] -//! -//! ``` -//! -//! #### 4. submit -//! -//! ```sh -//! leetcode submit 1 -//! ``` -//! -//! ```sh -//! -//! Success -//! -//! Runtime: 0 ms, faster than 100% of Rustonline submissions for Two Sum. -//! -//! Memory Usage: 2.4 MB, less than 100% of Rustonline submissions for Two Sum. -//! -//! -//! ``` -//! -//! ## Cookies -//! -//! The cookie plugin of leetcode-cil can work on OSX and [Linux][#1], **If you are on other platforms or your cookies just don't want to be catched**, you can handwrite your LeetCode Cookies to `~/.leetcode/leetcode.toml` -//! -//! ```toml -//! # Make sure `leetcode.toml` file is placed at `~/.leetcode/leetcode.toml` -//! [cookies] -//! csrf = "..." -//! session = "..." -//! ``` -//! -//! For Example, if you're using chrome to login to leetcode.com. -//! -//! -//! #### Step 1 -//! -//! Open chrome and paste the link below to the `chrome linkbar`. -//! -//! ```sh -//! chrome://settings/cookies/detail?site=leetcode.com -//! ``` -//! -//! #### Step 2 -//! -//! Copy the contents of `LEETCODE_SESSION` and `csrftoken`. -//! -//! #### Step 3 -//! -//! Paste them to `session` and `csrf`. -//! -//! ```toml -//! # Make sure `leetcode.toml` file is placed at `~/.leetcode/leetcode.toml` -//! [cookies] -//! csrf = "${csrftoken}" -//! session = "${LEETCODE_SESSION}" -//! ``` -//! -//! -//! ## Programmable -//! -//! If we want to filter leetcode questions using our own python scripts, what should we do? -//! -//! For example, our config is: -//! -//! ```toml -//! # Make sure `leetcode.toml` file is placed at `~/.leetcode/leetcode.toml` -//! [storage] -//! scripts = "scripts" -//! ``` -//! -//! We write our python scripts: -//! -//! ```python -//! # ~/.leetcode/scripts/plan1.py -//! import json; -//! -//! def plan(sps, stags): -//! ## -//! # `print` in python is supported, -//! # if you want to know the data structures of these two args, -//! # just print them -//! ## -//! problems = json.loads(sps) -//! tags = json.loads(stags) -//! -//! ret = [] -//! tm = {} -//! for tag in tags: -//! tm[tag["tag"]] = tag["refs"]; -//! -//! for i in problems: -//! if i["level"] == 1 and str(i["id"]) in tm["linked-list"]: -//! ret.append(str(i["id"])) -//! -//! # return is `List[string]` -//! return ret -//! ``` -//! -//! Then we can run filter as what we write now: -//! -//! ```sh -//! leetcode list -p plan1 -//! ``` -//! -//! Well done, enjoy it! -//! -//! -//! ## PR -//! -//! PR is welcome, [here][pr] it is. -//! -//! ## LICENSE -//! MIT -//! -//! -//! [pr]: https://github.com/clearloop/leetcode-cli/pulls -//! [#1]: https://github.com/clearloop/leetcode-cli/issues/1 +#![doc = include_str!("../README.md")] #[macro_use] extern crate log; #[macro_use] @@ -236,10 +7,9 @@ extern crate diesel; // show docs pub mod cache; pub mod cli; -pub mod cmds; +pub mod cmd; pub mod config; pub mod err; -pub mod flag; pub mod helper; pub mod plugins; #[cfg(feature = "pym")] diff --git a/src/plugins/chrome.rs b/src/plugins/chrome.rs index 0825300..d948a13 100644 --- a/src/plugins/chrome.rs +++ b/src/plugins/chrome.rs @@ -1,33 +1,17 @@ -use crate::{cache, Error, Result}; -use anyhow::anyhow; -use diesel::prelude::*; -use keyring::Entry; -use openssl::{hash, pkcs5, symm}; -use std::{collections::HashMap, fmt::Display}; - -/// LeetCode Cookies Schema -mod schema { - table! { - cookies (host_key) { - encrypted_value -> Binary, - host_key -> Text, - name -> Text, - } - } -} - -/// Please make sure the order -/// -/// The order between table and struct must be same. -#[derive(Queryable, Debug, Clone)] -struct Cookies { - pub encrypted_value: Vec, - #[allow(dead_code)] - pub host_key: String, - pub name: String, -} - -/// Spawn cookies to cookie format +//! Read and decrypt LeetCode cookies straight from Chrome's cookie store. +//! +//! Replaces the archived `rookie` crate. leetcode-cli only needs two cookies +//! (`LEETCODE_SESSION`, `csrftoken`) from one browser, so this reuses the SQLite +//! stack already pulled in by `diesel` to read the cookie DB and implements just +//! Chrome's value decryption — no second SQLite client and no dbus linkage. +use crate::{Error, Result}; +use cbc::cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7}; +use diesel::{prelude::*, sql_query, sql_types, sqlite::SqliteConnection}; +use std::{collections::HashMap, fmt::Display, path::PathBuf}; + +type Aes128CbcDec = cbc::Decryptor; + +/// Resolved LeetCode identity. #[derive(Debug)] pub struct Ident { pub csrf: String, @@ -44,7 +28,7 @@ impl Display for Ident { } } -/// Get cookies from chrome storage +/// Get cookies, preferring the configured ones and falling back to Chrome. pub fn cookies() -> Result { let ccfg = crate::config::Config::locate()?.cookies; if !ccfg.csrf.is_empty() && !ccfg.session.is_empty() { @@ -54,100 +38,157 @@ pub fn cookies() -> Result { }); } - // If doesn't config SESSION and csrftoken - use self::schema::cookies::dsl::*; - trace!("Derive cookies from google chrome..."); - - let home = dirs::home_dir().ok_or(Error::NoneError)?; - let p = match std::env::consts::OS { - "macos" => home.join("Library/Application Support/Google/Chrome/Default/Cookies"), - "linux" => home.join(".config/google-chrome/Default/Cookies"), - _ => panic!("Opps...only works on OSX or Linux now..."), - }; - - debug!("Chrome Cookies path is {:?}", &p); - let mut conn = cache::conn(p.to_string_lossy().to_string()); - let res = cookies - .filter(host_key.like(format!("#{}", ccfg.site))) - .load::(&mut conn) - .expect("Loading cookies from google chrome failed."); - - debug!("res {:?}", &res); - if res.is_empty() { - return Err(Error::CookieError); - } - - // Get system password - let ring = Entry::new("Chrome Safe Storage", "Chrome")?; - let pass = ring.get_password().expect("Get Password failed"); - - // Decode cookies - let mut m: HashMap = HashMap::new(); - for c in res { - if (c.name == "csrftoken") || (c.name == "LEETCODE_SESSION") { - m.insert(c.name, decode_cookies(&pass, c.encrypted_value)?); - } - } - + let jar = chrome_cookies(&ccfg.site.to_string())?; Ok(Ident { - csrf: m.get("csrftoken").ok_or(Error::ChromeNotLogin)?.to_string(), - session: m + csrf: jar + .get("csrftoken") + .ok_or(Error::ChromeNotLogin)? + .to_string(), + session: jar .get("LEETCODE_SESSION") .ok_or(Error::ChromeNotLogin)? .to_string(), }) } -/// Decode cookies from chrome -fn decode_cookies(pass: &str, v: Vec) -> Result { - let mut key = [0_u8; 16]; - match std::env::consts::OS { - "macos" => { - pkcs5::pbkdf2_hmac( - pass.as_bytes(), - b"saltysalt", - 1003, - hash::MessageDigest::sha1(), - &mut key, - ) - .expect("pbkdf2 hmac went error."); - } - "linux" => { - pkcs5::pbkdf2_hmac( - b"peanuts", - b"saltysalt", - 1, - hash::MessageDigest::sha1(), - &mut key, - ) - .expect("pbkdf2 hmac went error."); +#[derive(QueryableByName)] +struct CookieRow { + #[diesel(sql_type = sql_types::Text)] + name: String, + #[diesel(sql_type = sql_types::Binary)] + encrypted_value: Vec, +} + +/// Read Chrome's cookie DB for `domain` and decrypt the matching values. +fn chrome_cookies(domain: &str) -> Result> { + let db = cookie_db_path().ok_or(Error::ChromeNotLogin)?; + + // Chrome keeps the DB locked while running; read from a snapshot copy. + let tmp = std::env::temp_dir().join(format!("leetcode-cli-{}.sqlite", std::process::id())); + std::fs::copy(&db, &tmp)?; + let mut conn = SqliteConnection::establish(&tmp.to_string_lossy()) + .map_err(|e| anyhow::anyhow!("open chrome cookies: {e}"))?; + let rows: Vec = + sql_query("SELECT name, encrypted_value FROM cookies WHERE host_key LIKE ?") + .bind::(format!("%{domain}%")) + .load(&mut conn) + .map_err(|e| anyhow::anyhow!("read chrome cookies: {e}"))?; + drop(conn); + let _ = std::fs::remove_file(&tmp); + + let key = encryption_key()?; + let mut jar = HashMap::new(); + for row in rows { + if (row.name == "csrftoken" || row.name == "LEETCODE_SESSION") + && let Ok(value) = decrypt(&row.encrypted_value, &key) + { + jar.insert(row.name, value); } - _ => return Err(anyhow!("only supports OSX or Linux for now").into()), } + Ok(jar) +} + +/// Decrypt a Chrome `encrypted_value` (AES-128-CBC, `v10`/`v11`). +fn decrypt(blob: &[u8], key: &[u8]) -> Result { + if blob.len() <= 3 || (&blob[..3] != b"v10" && &blob[..3] != b"v11") { + return Err(anyhow::anyhow!("unsupported cookie encryption").into()); + } + let iv = [0x20u8; 16]; // Chrome uses 16 spaces as the IV. + let plain = Aes128CbcDec::new_from_slices(key, &iv) + .map_err(|e| anyhow::anyhow!("cipher init: {e}"))? + .decrypt_padded_vec_mut::(&blob[3..]) + .map_err(|e| anyhow::anyhow!("decrypt cookie: {e}"))?; + Ok(finalize(plain)) +} + +/// Chrome (v104+) prepends a 32-byte SHA-256 of the domain to the plaintext; +/// strip it when the leading bytes aren't printable. +fn finalize(plain: Vec) -> String { + if let Ok(s) = std::str::from_utf8(&plain) + && !s.chars().any(|c| c.is_control()) + { + return s.to_string(); + } + if plain.len() > 32 { + return String::from_utf8_lossy(&plain[32..]).into_owned(); + } + String::from_utf8_lossy(&plain).into_owned() +} + +/// PBKDF2-HMAC-SHA1 over the OS storage password (Chrome's KDF). +#[cfg(any(target_os = "macos", target_os = "linux"))] +fn derive(password: &[u8], iterations: u32) -> Vec { + pbkdf2::pbkdf2_hmac_array::(password, b"saltysalt", iterations).to_vec() +} + +#[cfg(target_os = "macos")] +fn cookie_db_path() -> Option { + Some(dirs::home_dir()?.join("Library/Application Support/Google/Chrome/Default/Cookies")) +} + +#[cfg(target_os = "macos")] +fn encryption_key() -> Result> { + let out = std::process::Command::new("security") + .args([ + "find-generic-password", + "-w", + "-a", + "Chrome", + "-s", + "Chrome Safe Storage", + ]) + .output()?; + let pw = String::from_utf8_lossy(&out.stdout); + let pw = pw.trim(); + if pw.is_empty() { + return Err(Error::ChromeNotLogin); + } + Ok(derive(pw.as_bytes(), 1003)) +} - chrome_decrypt(v, key) +#[cfg(target_os = "linux")] +fn cookie_db_path() -> Option { + let cfg = dirs::config_dir()?; + ["google-chrome", "chromium"] + .iter() + .map(|b| cfg.join(b).join("Default/Cookies")) + .find(|p| p.exists()) } -/// Decrypt chrome cookie value with aes-128-cbc -fn chrome_decrypt(v: Vec, key: [u8; 16]) -> Result { - // : \u16 - let iv = vec![32_u8; 16]; - let mut decrypter = symm::Crypter::new( - symm::Cipher::aes_128_cbc(), - symm::Mode::Decrypt, - &key, - Some(&iv), - )?; +#[cfg(target_os = "linux")] +fn encryption_key() -> Result> { + // Prefer the Secret Service password; fall back to Chrome's default. + let pw = std::process::Command::new("secret-tool") + .args(["lookup", "application", "chrome"]) + .output() + .ok() + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| "peanuts".to_string()); + Ok(derive(pw.as_bytes(), 1)) +} - let data_len = v.len() - 3; - let block_size = symm::Cipher::aes_128_cbc().block_size(); - let mut plaintext = vec![0; data_len + block_size]; +#[cfg(target_os = "windows")] +fn cookie_db_path() -> Option { + Some(dirs::data_local_dir()?.join("Google/Chrome/User Data/Default/Network/Cookies")) +} - decrypter.pad(false); +#[cfg(target_os = "windows")] +fn encryption_key() -> Result> { + // Windows uses DPAPI + AES-256-GCM (and app-bound encryption on Chrome + // 127+), a separate code path that isn't implemented yet. Until then, + // Windows users should set `csrf`/`session` in the config manually. + Err(Error::ChromeNotLogin) +} - let count = decrypter.update(&v[3..], &mut plaintext)?; - decrypter.finalize(&mut plaintext[count..])?; - plaintext.retain(|x| x >= &20_u8); +// for headless installations stub functions are defined +// can add to stubs with cfg(any(...)) +#[cfg(target_os = "android")] +fn cookie_db_path() -> Option { + None +} - Ok(String::from_utf8_lossy(&plaintext.to_vec()).to_string()) +#[cfg(target_os = "android")] +fn encryption_key() -> Result> { + Err(Error::ChromeNotLogin) } diff --git a/src/plugins/leetcode.rs b/src/plugins/leetcode.rs index ec8a924..4e67b8c 100644 --- a/src/plugins/leetcode.rs +++ b/src/plugins/leetcode.rs @@ -1,11 +1,11 @@ use self::req::{Json, Mode, Req}; use crate::{ - config::{self, Config}, Result, + config::{self, Config}, }; use reqwest::{ - header::{HeaderMap, HeaderName, HeaderValue}, Client, ClientBuilder, Response, + header::{HeaderMap, HeaderName, HeaderValue}, }; use std::{collections::HashMap, str::FromStr, time::Duration}; @@ -86,13 +86,15 @@ impl LeetCode { json.insert("variables", r#"{"slug": "$slug"}"#.replace("$slug", slug)); json.insert( "query", - ["query getTopicTag($slug: String!) {", + [ + "query getTopicTag($slug: String!) {", " topicTag(slug: $slug) {", " questions {", " questionId", " }", " }", - "}"] + "}", + ] .join("\n"), ); @@ -149,13 +151,15 @@ impl LeetCode { json.insert("operationName", "daily".to_string()); json.insert( "query", - ["query daily {", + [ + "query daily {", " activeDailyCodingChallengeQuestion {", " question {", " questionFrontendId", " }", " }", - "}"] + "}", + ] .join("\n"), ); } @@ -163,13 +167,15 @@ impl LeetCode { json.insert("operationName", "questionOfToday".to_string()); json.insert( "query", - ["query questionOfToday {", + [ + "query questionOfToday {", " todayRecord {", " question {", " questionFrontendId", " }", " }", - "}"] + "}", + ] .join("\n"), ); } @@ -195,7 +201,8 @@ impl LeetCode { let mut json: Json = HashMap::new(); json.insert( "query", - ["query getQuestionDetail($titleSlug: String!) {", + [ + "query getQuestionDetail($titleSlug: String!) {", " question(titleSlug: $titleSlug) {", " content", " stats", @@ -206,7 +213,8 @@ impl LeetCode { " metaData", " translatedContent", " }", - "}"] + "}", + ] .join("\n"), ); @@ -269,7 +277,7 @@ impl LeetCode { mod req { use super::LeetCode; use crate::err::Error; - use reqwest::{header::HeaderMap, Client, Response}; + use reqwest::{Client, Response, header::HeaderMap}; use std::collections::HashMap; /// Standardize json format diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 0102ca0..fdc651e 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,10 +1,10 @@ -//! Leetcode-cil plugins +//! Leetcode-cli plugins //! //! + chrome cookie parser //! + leetcode API //! //! ## login to `leetcode.com` -//! Leetcode-cli use chrome cookie directly, do not need to login, please make sure you have loggined in `leetcode.com` before usnig `leetcode-cli` +//! Leetcode-cli uses chrome cookie directly, do not need to login, please make sure you have logged in `leetcode.com` before using `leetcode-cli` //! // FIXME: Read cookies from local storage. (issue #122) diff --git a/src/pym.rs b/src/pym.rs index 43dd81b..2062704 100644 --- a/src/pym.rs +++ b/src/pym.rs @@ -1,14 +1,14 @@ //! This module is for python scripts. //! //! Seems like some error exists now, welocome pr to fix this : ) -use crate::{cache::Cache, helper::load_script, Result}; +use crate::{Result, cache::Cache, helper::load_script}; use pyo3::prelude::*; use std::ffi::CString; /// Exec python scripts as filter pub fn exec(module: &str) -> Result> { - pyo3::prepare_freethreaded_python(); - let script = load_script(&module)?; + Python::initialize(); + let script = load_script(module)?; let cache = Cache::new()?; // args @@ -16,7 +16,7 @@ pub fn exec(module: &str) -> Result> { let stags = serde_json::to_string(&cache.get_tags()?)?; // pygil - Python::with_gil(|py| { + Python::attach(|py| { let script_cstr = CString::new(script.as_str())?; let filename_cstr = CString::new("plan.py")?; let module_name_cstr = CString::new("plan")?;