diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2817dd8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,63 @@ +name: Test and Release + +on: + push: + branches: [main, master] + tags: ["v*"] # Triggers on version tags like v1.0.0 + pull_request: + branches: [main, master] + +env: + CARGO_TERM_COLOR: always + BIN_NAME: spring-tui # Change this to your actual binary name + +jobs: + test: + name: Build & Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build Release + run: cargo build --release --verbose + + - name: Prepare Binary + shell: bash + run: | + mkdir -p dist-binaries + if [ "${{ matrix.os }}" = "windows-latest" ]; then + cp target/release/$BIN_NAME.exe dist-binaries/${BIN_NAME}-windows.exe + elif [ "${{ matrix.os }}" = "macos-latest" ]; then + cp target/release/$BIN_NAME dist-binaries/${BIN_NAME}-macos + else + cp target/release/$BIN_NAME dist-binaries/${BIN_NAME}-linux + fi + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: binaries-${{ matrix.os }} + path: dist-binaries/* + + release: + name: Create Release + needs: test + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: all-binaries + merge-multiple: true + + - name: Release + uses: softprops/action-gh-release@v2 + with: + files: all-binaries/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce569ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +HELP.md +.gradle +build +.vscode/ +backup +.history/ +*.code-workspace +dev.json +ReadMe.md +target +bins diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3aa386f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3499 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + +[[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.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[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.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.10.0", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[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 = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "lab", + "phf", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "deflate64" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" + +[[package]] +name = "deltae" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.114", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "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 2.0.114", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "euclid" +version = "0.22.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instability" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kasuari" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" +dependencies = [ + "hashbrown", + "portable-atomic", + "thiserror 2.0.18", +] + +[[package]] +name = "lab" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[package]] +name = "line-clipping" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lzma-rust2" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69" +dependencies = [ + "crc", + "sha2", +] + +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" + +[[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 = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +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-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[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.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "ratatui" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +dependencies = [ + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-macros", + "ratatui-termwiz", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +dependencies = [ + "bitflags 2.10.0", + "compact_str", + "hashbrown", + "indoc", + "itertools", + "kasuari", + "lru", + "strum", + "thiserror 2.0.18", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +dependencies = [ + "cfg-if", + "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" +dependencies = [ + "ratatui-core", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +dependencies = [ + "ratatui-core", + "termwiz", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +dependencies = [ + "bitflags 2.10.0", + "hashbrown", + "indoc", + "instability", + "itertools", + "line-clipping", + "ratatui-core", + "strum", + "time", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spring-tui" +version = "0.0.1" +dependencies = [ + "anyhow", + "clap", + "crossterm", + "dirs", + "ratatui", + "reqwest", + "serde", + "serde_json", + "serde_yaml", + "thiserror 2.0.18", + "tokio", + "toml", + "zip", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "terminfo" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" +dependencies = [ + "fnv", + "nom", + "phf", + "phf_codegen", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" +dependencies = [ + "anyhow", + "base64", + "bitflags 2.10.0", + "fancy-regex", + "filedescriptor", + "finl_unicode", + "fixedbitset", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix", + "num-derive", + "num-traits", + "ordered-float", + "pest", + "pest_derive", + "phf", + "sha2", + "signal-hook", + "siphasher", + "terminfo", + "termios", + "thiserror 1.0.69", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "wezterm-bidi", + "wezterm-blob-leases", + "wezterm-color-types", + "wezterm-dynamic", + "wezterm-input-types", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "time" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +dependencies = [ + "deranged", + "js-sys", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-path" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3015e6ce46d5ad8751e4a772543a30c7511468070e98e64e20165f8f81155b64" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "atomic", + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vtparse" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +dependencies = [ + "utf8parse", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wezterm-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" +dependencies = [ + "log", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-blob-leases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" +dependencies = [ + "getrandom 0.3.4", + "mac_address", + "sha2", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "wezterm-color-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" +dependencies = [ + "csscolorparser", + "deltae", + "lazy_static", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-dynamic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +dependencies = [ + "log", + "ordered-float", + "strsim", + "thiserror 1.0.69", + "wezterm-dynamic-derive", +] + +[[package]] +name = "wezterm-dynamic-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wezterm-input-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" +dependencies = [ + "bitflags 1.3.2", + "euclid", + "lazy_static", + "serde", + "wezterm-dynamic", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-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 = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "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]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zip" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" +dependencies = [ + "aes", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "generic-array", + "getrandom 0.3.4", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "typed-path", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1966f8ac2c1f76987d69a74d0e0f929241c10e78136434e3be70ff7f58f64214" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0fcc08c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "spring-tui" +version = "0.0.1" +edition = "2024" + +[dependencies] +anyhow = "1.0.100" +thiserror = "2.0.18" +clap = { version = "4.5.56", features = ["derive"] } +tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros"] } +reqwest = { version = "0.13.1", features = ["json"] } +ratatui = { version = "0.30.0", optional = true } +crossterm = { version = "0.29.0", optional = true } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +zip = "7.2.0" # keep only if downloading/extracting +dirs = "6.0.0" # only if you store defaults +serde_yaml = "0.9" +toml = "0.9.11" + +[features] +default = ["tui"] +tui = ["dep:ratatui","dep:crossterm"] + +[target.aarch64-apple-darwin] +linker = "aarch64-apple-darwin21.1-clang" +ar = "aarch64-apple-darwin21.1-ar" \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..62589ed --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 36f8649..16d421c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,225 @@ -# Spring Tui + -A tui replacement for spring initialzr + + + + + + + + + + +
+
+ + Spring Initializr Logo + + +

spring-tui

+ +

+ Terminal UI for generating Spring Boot projects using Spring Initializr +
+ Explore Spring Initializr +
+
+ View Repo + · + Report Bug + · + Request Feature +

+ +
+ + +
+ Table of Contents +
    +
  1. + About The Project + +
  2. +
  3. + Getting Started + +
  4. +
  5. Usage
  6. +
  7. Roadmap
  8. +
  9. Contributing
  10. +
  11. License
  12. +
  13. Contact
  14. +
  15. Acknowledgments
  16. +
+
+ + + +## About The Project + +![Product Name Screen Shot][product-screenshot] + +spring-tui is a terminal UI that lets you configure and generate Spring Boot projects using Spring Initializr. +It provides a fast keyboard driven workflow for selecting options and dependencies, and can export a config file for later reuse. + +

(back to top)

+ +### Built With + +- [Rust](https://www.rust-lang.org/) +- [ratatui](https://github.com/ratatui-org/ratatui) +- [crossterm](https://github.com/crossterm-rs/crossterm) +- [reqwest](https://github.com/seanmonstar/reqwest) +- [serde](https://github.com/serde-rs/serde) + +

(back to top)

+ + + +## Getting Started + +Follow these steps to build and run the app locally. + +### Prerequisites + +- Rust toolchain + - https://www.rust-lang.org/tools/install + +### Installation + +1. Clone the repo + ```sh + git clone https://github.com/punixcorn/spring-tui.git + ``` +2. Build + ```sh + cargo build + ``` +3. Run the TUI + ```sh + cargo run + ``` + +

(back to top)

+ + + +## Usage + +Run from a config file: + +```sh +./target/debug/spring-tui --file config.yaml +``` + +Config formats supported: YAML, JSON, TOML. + +Example files: + +- [rake-service-config.yaml](./example/rake-service-config.yaml) +- [security-config.toml](./example/security-config.toml) + +TUI controls: +| Keybinding| Action| +|-|-| +| Tab| switches between Config and Dependencies panes | +| Up |and Down navigate items | +| Enter| selects or edits the current field | +| Shift + c| opens configuration menu | + +

(back to top)

+ + + +## Roadmap + +- [x] TUI configuration workflow +- [x] Dependency search and selection +- [x] Export configuration file +- [x] Config import from TUI +- [x] Project extraction after download +- [ ] Default config for most settings at ~/.config +- [ ] Ui improvments + +See the [open issues](https://github.com/punixcorn/spring-tui/issues) for a full list of proposed features and known issues. + +

(back to top)

+ + + +## Contributing + +Contributions are welcome. + +1. Fork the project +2. Create your feature branch (`git checkout -b feature/my-change`) +3. Commit your changes (`git commit -m "Add feature"`) +4. Push to the branch (`git push origin feature/my-change`) +5. Open a pull request + +Please keep changes focused and include relevant screenshots for UI changes. + +

(back to top)

+ + + +## License + +Distributed under the Apache License 2.0. See [LICENSE.txt](LICENSE.txt) for more information. + +

(back to top)

+ + + +## Contact + +Project Link: https://github.com/punixcorn/spring-tui + +

(back to top)

+ + + +## Acknowledgments + +- [Spring Initializr](https://start.spring.io) +- [ratatui](https://github.com/ratatui-org/ratatui) +- [crossterm](https://github.com/crossterm-rs/crossterm) + +

(back to top)

+ + + + +[contributors-shield]: https://img.shields.io/github/contributors/punixcorn/spring-tui.svg?style=for-the-badge +[contributors-url]: https://github.com/punixcorn/spring-tui/graphs/contributors +[forks-shield]: https://img.shields.io/github/forks/punixcorn/spring-tui.svg?style=for-the-badge +[forks-url]: https://github.com/punixcorn/spring-tui/network/members +[stars-shield]: https://img.shields.io/github/stars/punixcorn/spring-tui.svg?style=for-the-badge +[stars-url]: https://github.com/punixcorn/spring-tui/stargazers +[issues-shield]: https://img.shields.io/github/issues/punixcorn/spring-tui.svg?style=for-the-badge +[issues-url]: https://github.com/punixcorn/spring-tui/issues +[license-shield]: https://img.shields.io/github/license/punixcorn/spring-tui.svg?style=for-the-badge +[license-url]: https://github.com/punixcorn/spring-tui/blob/main/LICENSE.txt +[product-screenshot]: ./assets/spring-tui-demo.png diff --git a/assets/spring-tui-demo.png b/assets/spring-tui-demo.png new file mode 100644 index 0000000..35772e3 Binary files /dev/null and b/assets/spring-tui-demo.png differ diff --git a/assets/spring.png b/assets/spring.png new file mode 100644 index 0000000..f677b38 Binary files /dev/null and b/assets/spring.png differ diff --git a/example/rake-service-config.json b/example/rake-service-config.json new file mode 100644 index 0000000..69edafa --- /dev/null +++ b/example/rake-service-config.json @@ -0,0 +1,16 @@ +{ + "projectType": "maven-project", + "language": "kotlin", + "platformVersion": "", + "packaging": "jar", + "configurationFileFormat": "properties", + "javaVersion": 21, + "groupId": "org.turntabl", + "artifactId": "snake", + "name": "snakeapp", + "description": "Demo Snake java app", + "packageName": "org.turntabl.snake", + "dependencies": "security,web", + "bootVersion": "3.5.10", + "version": "0.0.1-SNAPSHOT" +} diff --git a/example/rake-service-config.yaml b/example/rake-service-config.yaml new file mode 100644 index 0000000..eb9cc22 --- /dev/null +++ b/example/rake-service-config.yaml @@ -0,0 +1,14 @@ +projectType: maven-project +language: kotlin +platformVersion: "" +packaging: jar +configurationFileFormat: properties +javaVersion: 21 +groupId: org.turntabl +artifactId: rake +name: rakeservice +description: Demo Rake Service for my garden Application +packageName: org.turntabl.rake +dependencies: security,web +bootVersion: 3.5.10 +version: 0.0.1-SNAPSHOT diff --git a/example/security-config.toml b/example/security-config.toml new file mode 100644 index 0000000..4b07fbd --- /dev/null +++ b/example/security-config.toml @@ -0,0 +1,14 @@ +projectType = "gradle-project" +language = "java" +platformVersion = "" +packaging = "jar" +configurationFileFormat = "properties" +javaVersion = 17 +groupId = "org.turntabl" +artifactId = "hopper" +name = "hopper" +description = "Demo hopper game built in Spring boot" +packageName = "org.turntabl.hopper" +dependencies = "security" +bootVersion = "4.0.2" +version = "0.0.1-SNAPSHOT" diff --git a/makefile b/makefile new file mode 100644 index 0000000..cf145cc --- /dev/null +++ b/makefile @@ -0,0 +1,10 @@ +run: + cargo run +test: + cargo test +build: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \ + cargo build --release --target aarch64-unknown-linux-gnu + CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc \ + cargo build --release --target x86_64-pc-windows-gnu + cargo build --release --target x86_64-unknown-linux-gnu \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..d8b3b06 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,50 @@ +mod util; +use crate::types; +use crate::types::api::{InitializrCapabilities, InitializrDependencies}; +use crate::api::util::{get_base_url, get_headers}; +use std::error::Error; + +pub async fn get_dependencies() -> Result> { + let client = reqwest::Client::new(); + + let response = client + .get(get_base_url() + "dependencies") + .headers(get_headers()) + .send() + .await?; + + if response.status().is_success() { + let deps = response.json::().await?; + Ok(deps) + } else { + let error_text = response.text().await.unwrap_or_default(); + let error_response: Result = serde_json::from_str(&error_text); + if let Ok(err) = error_response { + Err(format!("Download failed: {}",err.message).into()) + } else { + Err(format!("Download failed: {}", error_text).into()) + } + } +} + +pub async fn get_capabilities() -> Result> { + let client = reqwest::Client::new(); + let response = client + .get(get_base_url()) + .headers(get_headers()) + .send() + .await?; + + if response.status().is_success() { + let capabilities = response.json::().await?; + Ok(capabilities) + } else { + let error_text = response.text().await.unwrap_or_default(); + let error_response: Result = serde_json::from_str(&error_text); + if let Ok(err) = error_response { + Err(format!("Download failed: {}",err.message).into()) + } else { + Err(format!("Download failed: {}", error_text).into()) + } + } +} diff --git a/src/api/util.rs b/src/api/util.rs new file mode 100644 index 0000000..afc877b --- /dev/null +++ b/src/api/util.rs @@ -0,0 +1,18 @@ +use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, USER_AGENT}; + +pub fn get_headers() -> HeaderMap { + let mut headers = HeaderMap::new(); + + headers.insert( + ACCEPT, + HeaderValue::from_static("application/vnd.initializr.v2.3+json"), + ); + + headers.insert(USER_AGENT, HeaderValue::from_static("spring-tui/0.0.1")); + + headers +} + +pub fn get_base_url() -> String { + String::from("https://start.spring.io/") +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..ce5a08f --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,68 @@ +use clap::Parser; + +#[derive(Parser)] +#[command( + name = "spring-tui", + version = env!("CARGO_PKG_VERSION"), + author = "hyunwo.oh@turntabl.io", + about = "tui for generating Spring Boot projects using Spring Initializr", + long_about = "A fast and interactive terminal-based tool for generating Spring Boot projects. \n\ + provides both a rich tui interface and cli options for automated workflows. \n\ + export configurations to yaml/json/toml and generate projects from config files." +)] +pub struct Args { + /// Path to configuration file (YAML, JSON, or TOML) to generate project from + #[arg( + short, + long, + value_name = "path-to-config", + help = "Generate project from config file", + long_help = "Specify a configuration file in yaml, json, or toml format to automatically \ + generate a Spring Boot project with predefined settings" + )] + pub file: Option, + + // /// Directory path for downloading generated projects + // #[arg( + // short, + // long, + // value_name = "DIR", + // help = "Set default download directory", + // long_help = "Override the default download directory for generated Spring Boot projects. \ + // The generated ZIP file will be saved to this location" + // )] + // dir: Option, + + /// Automatically extract the generated project ZIP file + #[arg( + short, + long, + help = "Extract project after generation", + long_help = "Automatically extract the downloaded ZIP file into the target directory. \ + only applies when generating from a config file (--file option)" + )] + pub extract: bool, +} + + +#[derive(Default)] +pub struct CliResult { + pub file: Option, + pub extract: Option, +} + +pub fn parse() -> Option { + let args = Args::parse(); + + if args.file.is_none() { + return None; + } + + let extract = args.file.as_ref().map(|_| args.extract); + + Some(CliResult { + file: args.file, + extract, + }) +} + diff --git a/src/config/config_parser.rs b/src/config/config_parser.rs new file mode 100644 index 0000000..496d021 --- /dev/null +++ b/src/config/config_parser.rs @@ -0,0 +1,11 @@ +#![allow(unused_variables)] +#![allow(dead_code)] +#![allow(unused_imports)] + +use crate::config::util; +use serde::{Deserialize, Serialize}; +use crate::types::generic::SprintInitConfig; + +pub fn parse_config(path: String) -> anyhow::Result { + util::parse_config(path.as_str()) +} diff --git a/src/config/global_config.rs b/src/config/global_config.rs new file mode 100644 index 0000000..d72e494 --- /dev/null +++ b/src/config/global_config.rs @@ -0,0 +1,21 @@ +#![allow(unused_variables)] +#![allow(dead_code)] +#![allow(unused_imports)] + +use crate::config::util; +use serde::{Deserialize, Serialize}; +use std::{fs, path::PathBuf}; +use crate::types::config::GlobalConfig; + +/// Read GlobalConfig from config file +pub fn get_config() -> anyhow::Result { + let path = match util::get_application_config_path() { + Some(data) => data, + _ => anyhow::bail!("fuck"), + }; + + util::parse_config(path.as_str()) +} + +/// set the global config ( overwrite or edit variables ) +pub fn set(config: &GlobalConfig) {} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..a4b89de --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,11 @@ +mod config_parser; +mod global_config; +mod util; +use crate::generator; + +pub async fn create_project_from_config(path: &str, extract: bool) -> anyhow::Result<()> { + let config = config_parser::parse_config(path.to_string())?; + generator::generate_project(&config, extract) + .await + .map_err(|e| anyhow::anyhow!(e.to_string())) +} diff --git a/src/config/util.rs b/src/config/util.rs new file mode 100644 index 0000000..b4b6ff8 --- /dev/null +++ b/src/config/util.rs @@ -0,0 +1,29 @@ +use serde::de::DeserializeOwned; +use std::fs; +use std::path::PathBuf; +use crate::types::config::{ConfigMetadata, FileType}; + +pub fn parse_config(path: &str) -> anyhow::Result { + let content = fs::read_to_string(path)?; + let metadata = ConfigMetadata::from_path(path.to_string())?; + + match metadata.file_type { + FileType::Yaml => Ok(serde_yaml::from_str(&content)?), + FileType::Json => Ok(serde_json::from_str(&content)?), + FileType::Toml => Ok(toml::from_str(&content)?), + } +} + +pub fn get_application_config_path() -> Option { + let home = std::env::var("HOME").unwrap_or_else(|_| ".".into()); + let config_dir = PathBuf::from(home).join(".config/spring-tui"); + + for ext in ["json", "toml", "yml", "yaml"] { + let path = config_dir.join(format!("config.{}", ext)); + if path.exists() { + return path.to_str().map(String::from); + } + } + + None +} diff --git a/src/generator/mod.rs b/src/generator/mod.rs new file mode 100644 index 0000000..0a74d0a --- /dev/null +++ b/src/generator/mod.rs @@ -0,0 +1,139 @@ +// make does the logic of coverting the Types into the desired url to be downloaded +// creates a struct SprintInitConfig and parses it into the url from InitializrCapabilities.links based on the build +/* InitializrCapabilities { + links: { + "gradle-project-kotlin": Link { + href: "https://start.spring.io/starter.zip?type=gradle-project-kotlin{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName,configurationFileFormat}", + templated: Some( + true, + ), + }, + "gradle-project": Link { + href: "https://start.spring.io/starter.zip?type=gradle-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName,configurationFileFormat}", + templated: Some( + true, + ), + }, + "gradle-build": Link { + href: "https://start.spring.io/build.gradle?type=gradle-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName,configurationFileFormat}", + templated: Some( + true, + ), + }, + "maven-project": Link { + href: "https://start.spring.io/starter.zip?type=maven-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName,configurationFileFormat}", + templated: Some( + true, + ), + }, + "maven-build": Link { + href: "https://start.spring.io/pom.xml?type=maven-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName,configurationFileFormat}", + templated: Some( + true, + ), + }, +*/ + + +use crate::types::generic::SprintInitConfig; +use crate::{api, types}; + +pub fn download_url(config: &SprintInitConfig, base_url: &str) -> String { + let mut url = base_url.to_string(); + + // Append query parameters based on the config + url.push_str(&format!("&dependencies={}", config.dependencies)); + url.push_str(&format!("&packaging={}", config.packaging)); + url.push_str(&format!("&javaVersion={}", config.java_version)); + url.push_str(&format!("&language={}", config.language)); + url.push_str(&format!("&bootVersion={}", config.boot_version)); + url.push_str(&format!("&groupId={}", config.group_id)); + url.push_str(&format!("&artifactId={}", config.artifact_id)); + url.push_str(&format!("&version={}", config.version)); + url.push_str(&format!("&name={}", config.name)); + url.push_str(&format!("&description={}", config.description)); + url.push_str(&format!("&packageName={}", config.package_name)); + url.push_str(&format!("&configurationFileFormat={}", config.configuration_file_format)); + + url +} + +pub async fn generate_project(config: &SprintInitConfig, extract_project: bool) -> Result<(), Box> { + // Get capabilities from API + let capabilities = api::get_capabilities().await?; + + // Get the base URL from the capabilities based on project type + let base_url = capabilities + .links + .get(&config.project_type) + .ok_or("Unsupported project type")? + .href + .split('{') + .next() + .ok_or("Invalid URL format")?; + + let download_link = download_url(config, base_url); + // println!("Download URL: {}", download_link); + + // Download the project + let client = reqwest::Client::new(); + let response = client.get(&download_link).send().await?; + + if response.status().is_success() { + let bytes = response.bytes().await?; + + // Save to file + let file_path = format!("{}.zip", config.artifact_id); + std::fs::write(&file_path, bytes)?; + // println!("Project downloaded to: {}", file_path); + + // Extract the zip + if extract_project { + let file = std::fs::File::open(&file_path)?; + let mut archive = zip::ZipArchive::new(file)?; + archive.extract(".")?; + println!("Project extracted successfully!"); + } + + Ok(()) + } else { + let error_text = response.text().await.unwrap_or_default(); + let error_response: Result = serde_json::from_str(&error_text); + if let Ok(err) = error_response { + Err(format!("Download failed: {}",err.message).into()) + } else { + Err(format!("Download failed: {}", error_text).into()) + } + } +} + +pub fn generate_project_config_file( + config: &SprintInitConfig, + extension: types::config::FileType, + custom_filename: Option, +) -> Result<(), Box> { + let ext_str = match extension { + types::config::FileType::Yaml => "yaml", + types::config::FileType::Json => "json", + types::config::FileType::Toml => "toml", + }; + + let filename = if let Some(name) = custom_filename { + if name.ends_with(&format!(".{}", ext_str)) { + name + } else { + format!("{}.{}", name, ext_str) + } + } else { + format!("config.{}", ext_str) + }; + + let content = match extension { + types::config::FileType::Yaml => serde_yaml::to_string(config)?, + types::config::FileType::Json => serde_json::to_string_pretty(config)?, + types::config::FileType::Toml => toml::to_string_pretty(config)?, + }; + + std::fs::write(filename, content)?; + Ok(()) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c43c456 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +pub mod api; +pub mod cli; +pub mod config; +pub mod generator; +pub mod types; + +#[cfg(feature = "tui")] +pub mod tui; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0453770 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,51 @@ +mod api; +mod types; +mod cli; +mod config; +mod tui; +mod generator; +use std::io::Write; + +#[tokio::main] +#[allow(unused_variables)] +async fn main() -> Result<(), Box> { + match cli::parse() { + None => { + // Run TUI + tui::run().await?; + } + Some(result) => { + // if let Some(dir) = result.dir { + // config::modify_global_config(dir.as_str()); + // } + if let Some(file) = result.file { + let file_clone = file.clone(); + let spinner_handle = tokio::spawn(async move { + let chars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + let mut i = 0; + loop { + print!("\r\x1b[32m{}\x1b[0m Generating project from {}...", chars[i], file_clone); + std::io::stdout().flush().unwrap(); + i = (i + 1) % chars.len(); + tokio::time::sleep(std::time::Duration::from_millis(80)).await; + } + }); + + let res = config::create_project_from_config(file.as_str(), result.extract.unwrap_or(false)).await; + + spinner_handle.abort(); + // Clear the spinner line + print!("\r\x1b[2K"); + std::io::stdout().flush().unwrap(); + + match res { + Ok(_) => { println!("\x1b[32m✓ Project generated successfully!\x1b[0m"); }, + Err(e) => { + println!("\x1b[31m✗ Error generating project from config: {}\x1b[0m", e); + } + } + } + } + } + Ok(()) +} diff --git a/src/tui/mod.rs b/src/tui/mod.rs new file mode 100644 index 0000000..0f955be --- /dev/null +++ b/src/tui/mod.rs @@ -0,0 +1,1033 @@ +use crate::api; +use crate::generator; +use crate::types::api::{InitializrCapabilities, InitializrDependencies}; +use crate::types::generic::SprintInitConfig; +use crate::types::config::FileType; +use crossterm::{ + event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use ratatui::{ + backend::{Backend, CrosstermBackend}, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style}, + text::{Line, Span}, + widgets::{Block, Borders, BorderType, Clear, List, ListItem, ListState, Paragraph, Wrap}, + Frame, Terminal, +}; +use std::collections::BTreeSet; +use std::io; + +// --- Theme Colors --- +const BG_COLOR: Color = Color::Rgb(30, 33, 39); // Dark background +const PANEL_BG: Color = Color::Rgb(30, 33, 39); // Panel background +const ACCENT_COLOR: Color = Color::Rgb(109, 179, 63); // Spring Green +const TEXT_COLOR: Color = Color::White; +const MUTED_COLOR: Color = Color::DarkGray; +const INPUT_BG: Color = Color::Rgb(40, 44, 52); + +#[derive(Clone, Copy, PartialEq)] +enum Field { + ProjectType, + Language, + BootVersion, + Packaging, + JavaVersion, + GroupId, + ArtifactId, + Name, + Description, + PackageName, + ConfigurationFormat, + Generate, + Export, +} + +#[derive(Clone, Copy, PartialEq)] +enum ActivePane { + Config, + Dependencies, +} + +struct App { + capabilities: InitializrCapabilities, + dependencies: InitializrDependencies, + config: SprintInitConfig, + current_field: Field, + active_pane: ActivePane, + list_state: ListState, + deps_list_state: ListState, + input_mode: bool, + input_buffer: String, + deps_search: String, + selected_deps: BTreeSet, + status_message: String, + show_popup: bool, + show_export_popup: bool, + export_filename: String, + export_format_idx: usize, // 0=Yaml, 1=Json, 2=Toml + export_focus_filename: bool, + extract_project: bool, + show_config_popup: bool, + show_message_popup: bool, + message_popup_title: String, + message_popup_text: String, + message_popup_is_error: bool, +} + +impl App { + fn new(capabilities: InitializrCapabilities, dependencies: InitializrDependencies) -> Self { + let config = SprintInitConfig { + project_type: capabilities + .project_type + .as_ref() + .and_then(|pt| pt.default.clone()) + .unwrap_or_else(|| "maven-project".to_string()), + language: capabilities + .language + .as_ref() + .and_then(|l| l.default.clone()) + .unwrap_or_else(|| "java".to_string()), + platform_version: "".to_string(), + packaging: capabilities + .packaging + .as_ref() + .and_then(|p| p.default.clone()) + .unwrap_or_else(|| "jar".to_string()), + configuration_file_format: "properties".to_string(), + java_version: capabilities + .java_version + .as_ref() + .and_then(|jv| jv.default.as_ref()) + .and_then(|v| v.parse().ok()) + .unwrap_or(17), + group_id: capabilities + .group_id + .as_ref() + .map(|g| g.default.clone()) + .unwrap_or_else(|| "com.example".to_string()), + artifact_id: capabilities + .artifact_id + .as_ref() + .map(|a| a.default.clone()) + .unwrap_or_else(|| "demo".to_string()), + name: capabilities + .name + .as_ref() + .map(|n| n.default.clone()) + .unwrap_or_else(|| "demo".to_string()), + description: capabilities + .description + .as_ref() + .map(|d| d.default.clone()) + .unwrap_or_else(|| "Demo project for Spring Boot".to_string()), + package_name: capabilities + .package_name + .as_ref() + .map(|p| p.default.clone()) + .unwrap_or_else(|| "com.example.demo".to_string()), + dependencies: "".to_string(), + boot_version: capabilities + .boot_version + .as_ref() + .and_then(|bv| bv.default.clone()) + .unwrap_or_else(|| "3.2.0".to_string()), + version: capabilities + .version + .as_ref() + .map(|v| v.default.clone()) + .unwrap_or_else(|| "0.0.1-SNAPSHOT".to_string()), + }; + + let mut list_state = ListState::default(); + list_state.select(Some(0)); + let mut deps_list_state = ListState::default(); + deps_list_state.select(Some(0)); +// status_message: "<󰌒Tab> Switch Pane  <> Navigate  <󰌑Enter> Select/Edit  <󰘲Shift+c> Config Menu".to_string(), + App { + capabilities, + dependencies, + config, + current_field: Field::ProjectType, + active_pane: ActivePane::Config, + list_state, + deps_list_state, + input_mode: false, + input_buffer: String::new(), + deps_search: String::new(), + selected_deps: BTreeSet::new(), + status_message: " Switch Pane  <> Navigate  Select/Edit  Config Menu".to_string(), + show_popup: false, + show_export_popup: false, + export_filename: "config".to_string(), + export_format_idx: 0, + export_focus_filename: true, + extract_project: false, + show_config_popup: false, + show_message_popup: false, + message_popup_title: String::new(), + message_popup_text: String::new(), + message_popup_is_error: false, + } + } + + fn toggle_pane(&mut self) { + self.active_pane = match self.active_pane { + ActivePane::Config => ActivePane::Dependencies, + ActivePane::Dependencies => ActivePane::Config, + }; + } + + fn next_dependency(&mut self) { + let len = self.dependency_options().len(); + if len > 0 { + let i = self.deps_list_state.selected().unwrap_or(0); + self.deps_list_state.select(Some((i + 1) % len)); + } + } + + fn previous_dependency(&mut self) { + let len = self.dependency_options().len(); + if len > 0 { + let i = self.deps_list_state.selected().unwrap_or(0); + self.deps_list_state.select(Some(if i == 0 { len - 1 } else { i - 1 })); + } + } + + fn next_field(&mut self) { + self.current_field = match self.current_field { + Field::ProjectType => Field::Language, + Field::Language => Field::BootVersion, + Field::BootVersion => Field::GroupId, // Jump to metadata start + Field::GroupId => Field::ArtifactId, + Field::ArtifactId => Field::Name, + Field::Name => Field::Description, + Field::Description => Field::PackageName, + Field::PackageName => Field::Packaging, // Layout order + Field::Packaging => Field::JavaVersion, + Field::JavaVersion => Field::ConfigurationFormat, + Field::ConfigurationFormat => Field::Export, + Field::Export => Field::Generate, + Field::Generate => Field::ProjectType, + }; + self.list_state.select(Some(0)); + self.show_popup = false; + } + + fn previous_field(&mut self) { + self.current_field = match self.current_field { + Field::ProjectType => Field::Generate, + Field::Language => Field::ProjectType, + Field::BootVersion => Field::Language, + Field::GroupId => Field::BootVersion, + Field::ArtifactId => Field::GroupId, + Field::Name => Field::ArtifactId, + Field::Description => Field::Name, + Field::PackageName => Field::Description, + Field::Packaging => Field::PackageName, + Field::JavaVersion => Field::Packaging, + Field::ConfigurationFormat => Field::JavaVersion, + Field::Export => Field::ConfigurationFormat, + Field::Generate => Field::Export, + }; + self.list_state.select(Some(0)); + self.show_popup = false; + } + + fn get_current_options(&self) -> Vec { + match self.current_field { + Field::ProjectType => self.capabilities.project_type.as_ref().map(|pt| pt.values.iter().map(|v| v.id.clone()).collect()).unwrap_or_default(), + Field::Language => self.capabilities.language.as_ref().map(|l| l.values.iter().map(|v| v.id.clone()).collect()).unwrap_or_default(), + Field::BootVersion => self.capabilities.boot_version.as_ref().map(|bv| bv.values.iter().map(|v| v.id.clone()).collect()).unwrap_or_default(), + Field::Packaging => self.capabilities.packaging.as_ref().map(|p| p.values.iter().map(|v| v.id.clone()).collect()).unwrap_or_default(), + Field::JavaVersion => self.capabilities.java_version.as_ref().map(|jv| jv.values.iter().map(|v| v.id.clone()).collect()).unwrap_or_default(), + Field::ConfigurationFormat => vec!["properties".to_string(), "yaml".to_string()], + _ => vec![], + } + } + + fn select_option(&mut self) { + if let Some(selected) = self.list_state.selected() { + let options = self.get_current_options(); + if selected < options.len() { + let value = options[selected].clone(); + match self.current_field { + Field::ProjectType => self.config.project_type = value, + Field::Language => self.config.language = value, + Field::BootVersion => self.config.boot_version = value, + Field::Packaging => self.config.packaging = value, + Field::JavaVersion => { + if let Ok(v) = value.parse() { + self.config.java_version = v; + } + } + Field::ConfigurationFormat => self.config.configuration_file_format = value, + _ => {} + } + self.show_popup = false; + self.refresh_package_name(); + } + } + } + + fn start_edit(&mut self) { + match self.current_field { + Field::GroupId => { self.input_mode = true; self.input_buffer = self.config.group_id.clone(); } + Field::ArtifactId => { self.input_mode = true; self.input_buffer = self.config.artifact_id.clone(); } + Field::Name => { self.input_mode = true; self.input_buffer = self.config.name.clone(); } + Field::Description => { self.input_mode = true; self.input_buffer = self.config.description.clone(); } + Field::PackageName => { self.input_mode = true; self.input_buffer = self.config.package_name.clone(); } + _ => {} + } + } + + fn finish_edit(&mut self) { + if self.input_mode { + match self.current_field { + Field::GroupId => self.config.group_id = self.input_buffer.clone(), + Field::ArtifactId => self.config.artifact_id = self.input_buffer.clone(), + Field::Name => self.config.name = self.input_buffer.clone(), + Field::Description => self.config.description = self.input_buffer.clone(), + Field::PackageName => self.config.package_name = self.input_buffer.clone(), + _ => {} + } + if matches!(self.current_field, Field::GroupId | Field::ArtifactId) { + self.refresh_package_name(); + } + self.input_mode = false; + self.input_buffer.clear(); + } + } + + fn refresh_package_name(&mut self) { + if self.current_field != Field::PackageName { + self.config.package_name = format!("{}.{}", self.config.group_id, self.config.artifact_id).replace("-", ""); + } + } + + fn dependency_options(&self) -> Vec { + let mut list: Vec = self.dependencies.dependencies.keys().cloned().collect(); + list.sort(); + if self.deps_search.trim().is_empty() { + return list; + } + let needle = self.deps_search.to_lowercase(); + list.into_iter().filter(|name| name.to_lowercase().contains(&needle)).collect() + } + + fn toggle_dependency(&mut self) { + let options = self.dependency_options(); + if let Some(selected) = self.deps_list_state.selected() { + if selected < options.len() { + let name = options[selected].clone(); + if self.selected_deps.contains(&name) { + self.selected_deps.remove(&name); + } else { + self.selected_deps.insert(name); + } + self.config.dependencies = self.selected_deps.iter().cloned().collect::>().join(","); + } + } + } +} + +// --- RENDERING --- + +fn ui(f: &mut Frame<'_>, app: &mut App) { + // Global Background + let size = f.area(); + f.render_widget(Block::default().style(Style::default().bg(BG_COLOR)), size); + + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), // Header + Constraint::Min(10), // Main Content + Constraint::Length(3), // Footer + ]) + .split(size); + + render_header(f, chunks[0]); + render_main_content(f, app, chunks[1]); + render_footer(f, app, chunks[2]); + + if app.show_popup && !app.input_mode { + render_popup(f, app); + } + if app.show_export_popup { + render_export_popup(f, app); + } + if app.show_config_popup { + render_config_popup(f, app); + } + if app.show_message_popup { + render_message_popup(f, app); + } + if app.input_mode { + render_input_dialog(f, app); + } +} + +fn render_header(f: &mut Frame<'_>, area: Rect) { + let title = Paragraph::new(Line::from(vec![ + Span::styled(" spring ", Style::default().fg(TEXT_COLOR).bg(ACCENT_COLOR).add_modifier(Modifier::BOLD)), + Span::styled(" initializr ", Style::default().fg(TEXT_COLOR).add_modifier(Modifier::BOLD)), + ])) + .block(Block::default().borders(Borders::BOTTOM).border_style(Style::default().fg(INPUT_BG))) + .alignment(Alignment::Center); + f.render_widget(title, area); +} + +fn render_main_content(f: &mut Frame<'_>, app: &mut App, area: Rect) { + let cols = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(60), Constraint::Percentage(40)]) + .split(area); + + render_config_column(f, app, cols[0]); + render_dependencies_column(f, app, cols[1]); +} + +fn render_config_column(f: &mut Frame<'_>, app: &mut App, area: Rect) { + let rows = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints([ + Constraint::Length(5), // Project & Language + Constraint::Length(4), // Boot Version + Constraint::Min(10), // Metadata + ]) + .split(area); + + // Row 1: Project & Language + let row1 = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(rows[0]); + + // Project + let p_opts: Vec<(String, String)> = app + .capabilities + .project_type + .as_ref() + .map(|x| x.values.iter().map(|v| (v.id.clone(), v.name.clone())).collect()) + .unwrap_or_default(); + draw_radio_section(f, app, "Project", &p_opts, &app.config.project_type, Field::ProjectType, row1[0]); + + // Language + let l_opts: Vec<(String, String)> = app + .capabilities + .language + .as_ref() + .map(|x| x.values.iter().map(|v| (v.id.clone(), v.name.clone())).collect()) + .unwrap_or_default(); + draw_radio_section(f, app, "Language", &l_opts, &app.config.language, Field::Language, row1[1]); + + // Row 2: Boot Version + let b_opts: Vec<(String, String)> = app + .capabilities + .boot_version + .as_ref() + .map(|x| x.values.iter().map(|v| (v.id.clone(), v.name.clone())).collect()) + .unwrap_or_default(); + draw_radio_section(f, app, "Spring Boot", &b_opts, &app.config.boot_version, Field::BootVersion, rows[1]); + + // Row 3: Metadata + render_metadata_section(f, app, rows[2]); +} + +fn render_metadata_section(f: &mut Frame<'_>, app: &mut App, area: Rect) { + let block = Block::default() + .title(Span::styled("Project Metadata", Style::default().fg(TEXT_COLOR).add_modifier(Modifier::BOLD))) + .style(Style::default().bg(PANEL_BG)) + .padding(ratatui::widgets::Padding::new(0, 0, 0, 2)); + f.render_widget(block.clone(), area); + + let inner_area = block.inner(area); + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), // Group + Constraint::Length(3), // Artifact + Constraint::Length(3), // Name + Constraint::Length(3), // Desc + Constraint::Length(3), // Package + Constraint::Length(4), // Packaging & Java + Constraint::Length(4), // Config Format + ]) + .split(inner_area); + + draw_text_input(f, app, "Group", &app.config.group_id, Field::GroupId, chunks[0]); + draw_text_input(f, app, "Artifact", &app.config.artifact_id, Field::ArtifactId, chunks[1]); + draw_text_input(f, app, "Name", &app.config.name, Field::Name, chunks[2]); + draw_text_input(f, app, "Description", &app.config.description, Field::Description, chunks[3]); + draw_text_input(f, app, "Package name", &app.config.package_name, Field::PackageName, chunks[4]); + + // Packaging & Java split + let row_last = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(chunks[5]); + + let pack_opts: Vec<(String, String)> = app + .capabilities + .packaging + .as_ref() + .map(|x| x.values.iter().map(|v| (v.id.clone(), v.name.clone())).collect()) + .unwrap_or_default(); + draw_radio_section(f, app, "Packaging", &pack_opts, &app.config.packaging, Field::Packaging, row_last[0]); + + let j_opts: Vec<(String, String)> = app + .capabilities + .java_version + .as_ref() + .map(|x| x.values.iter().map(|v| (v.id.clone(), v.name.clone())).collect()) + .unwrap_or_default(); + draw_radio_section(f, app, "Java", &j_opts, &app.config.java_version.to_string(), Field::JavaVersion, row_last[1]); + + // Configuration Format + let config_opts = vec![ + ("properties".to_string(), "Properties".to_string()), + ("yaml".to_string(), "YAML".to_string()), + ]; + draw_radio_section(f, app, "Config Format", &config_opts, &app.config.configuration_file_format, Field::ConfigurationFormat, chunks[6]); +} + +fn draw_radio_section(f: &mut Frame<'_>, app: &App, title: &str, options: &[(String, String)], current_val: &str, field: Field, area: Rect) { + let is_active_field = app.current_field == field && app.active_pane == ActivePane::Config; + + // Title styling + let title_style = if is_active_field { Style::default().fg(ACCENT_COLOR).add_modifier(Modifier::BOLD) } else { Style::default().fg(TEXT_COLOR).add_modifier(Modifier::BOLD) }; + + let block = Block::default().padding(ratatui::widgets::Padding::new(0, 1, 0, 0)); + f.render_widget(block, area); + + // Render Title manually at top + f.render_widget(Paragraph::new(title).style(title_style), Rect { x: area.x, y: area.y, width: area.width, height: 1 }); + + // Render Options + // Since we want to mimic the UI, we list them. + // If it's a long list (like Boot versions), we wrap or truncate for the view. + let mut spans = Vec::new(); + for (id, name) in options.iter().take(6) { // Limit display to avoid overflow + let is_selected = id == current_val; + let radio = if is_selected { "(●)" } else { "( )" }; + let style = if is_selected { Style::default().fg(TEXT_COLOR).add_modifier(Modifier::BOLD) } else { Style::default().fg(MUTED_COLOR) }; + let radio_style = if is_selected { Style::default().fg(ACCENT_COLOR) } else { Style::default().fg(MUTED_COLOR) }; + + spans.push(Span::styled(radio, radio_style)); + spans.push(Span::raw(" ")); + spans.push(Span::styled(name, style)); + spans.push(Span::raw(" ")); + } + + let p = Paragraph::new(Line::from(spans)).wrap(Wrap { trim: true }); + f.render_widget(p, Rect { x: area.x, y: area.y + 2, width: area.width, height: area.height.saturating_sub(2) }); +} + +fn draw_text_input(f: &mut Frame<'_>, app: &App, label: &str, value: &str, field: Field, area: Rect) { + let is_focused = app.current_field == field && app.active_pane == ActivePane::Config; + + let chunks = Layout::default().direction(Direction::Horizontal) + .constraints([Constraint::Length(15), Constraint::Min(10)]).split(area); + + // Label + let label_style = if is_focused { Style::default().fg(ACCENT_COLOR) } else { Style::default().fg(TEXT_COLOR) }; + f.render_widget(Paragraph::new(label).style(label_style).alignment(Alignment::Left), chunks[0]); + + // Input Box + let border_style = if is_focused { Style::default().fg(ACCENT_COLOR) } else { Style::default().fg(INPUT_BG) }; + let text_style = if is_focused { Style::default().fg(Color::White) } else { Style::default().fg(Color::Gray) }; + + let input_block = Block::default() + .borders(Borders::BOTTOM) + .border_style(border_style); + + let val_display = if is_focused && app.input_mode { + format!("{}█", app.input_buffer) // Cursor + } else { + value.to_string() + }; + + // Compact the height to 2 to ensure the bottom border (underline) is right below the text + let mut input_area = chunks[1]; + input_area.height = 2; + + f.render_widget(Paragraph::new(val_display).block(input_block).style(text_style), input_area); +} + +fn render_dependencies_column(f: &mut Frame<'_>, app: &mut App, area: Rect) { + let border_style = if app.active_pane == ActivePane::Dependencies { + Style::default().fg(ACCENT_COLOR).add_modifier(Modifier::BOLD) + } else { + Style::default().fg(INPUT_BG) + }; + let block = Block::default() + .borders(Borders::LEFT) + .border_style(border_style) + .padding(ratatui::widgets::Padding::new(1, 1, 1, 1)); + f.render_widget(block.clone(), area); + let inner = block.inner(area); + + let chunks = Layout::default().direction(Direction::Vertical) + .constraints([Constraint::Length(2), Constraint::Length(3), Constraint::Min(5)]).split(inner); + + // Header + f.render_widget(Paragraph::new("Dependencies").style(Style::default().fg(TEXT_COLOR).add_modifier(Modifier::BOLD)), chunks[0]); + + // Search Bar + let search_style = if !app.deps_search.is_empty() { Style::default().fg(Color::White) } else { Style::default().fg(MUTED_COLOR) }; + let search_txt = if app.deps_search.is_empty() { "Press 'Tab' then type to search..." } else { &app.deps_search }; + f.render_widget( + Paragraph::new(format!(" {}", search_txt)) + .block(Block::default().borders(Borders::ALL).border_style(Style::default().fg(INPUT_BG)).border_type(BorderType::Rounded) + .padding(ratatui::widgets::Padding::new(1,1,0,0))) + .style(search_style), + chunks[1] + ); + + // List + let options = app.dependency_options(); + let items: Vec = options.iter().map(|name| { + let is_selected = app.selected_deps.contains(name); + // let check = if is_selected { " [x] " } else { " [ ] " }; + let check = if is_selected { " 󱧕 " } else { " 󰏖 " }; + let style = if is_selected { Style::default().fg(ACCENT_COLOR) } else { Style::default().fg(TEXT_COLOR) }; + ListItem::new(format!("{}{}", check, name)).style(style) + }).collect(); + + let list = List::new(items) + .highlight_style(Style::default().bg(INPUT_BG).add_modifier(Modifier::BOLD)) + .highlight_symbol("󰁕 "); + + f.render_stateful_widget(list, chunks[2], &mut app.deps_list_state); +} + +fn render_footer(f: &mut Frame<'_>, app: &App, area: Rect) { + let block = Block::default().style(Style::default().bg(INPUT_BG)); + f.render_widget(block, area); + + let is_gen_selected = app.current_field == Field::Generate; + let is_exp_selected = app.current_field == Field::Export; + + let gen_style = if is_gen_selected { + Style::default().bg(ACCENT_COLOR).fg(Color::White).add_modifier(Modifier::BOLD) + } else { + Style::default().bg(MUTED_COLOR).fg(Color::Black) + }; + + let exp_style = if is_exp_selected { + Style::default().bg(ACCENT_COLOR).fg(Color::White).add_modifier(Modifier::BOLD) + } else { + Style::default().bg(MUTED_COLOR).fg(Color::Black) + }; + + let gen_txt = if is_gen_selected { " GENERATE (Enter) " } else { " GENERATE " }; + let exp_txt = if is_exp_selected { " EXPORT (Enter) " } else { " EXPORT " }; + + let chunks = Layout::default().direction(Direction::Horizontal) + .constraints([ + Constraint::Min(40), + Constraint::Length(18), // Export + Constraint::Length(20) // Generate + ]).split(area); + + // Split left section vertically for keybindings label + status + let left_section = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), // "keybindings" label + Constraint::Length(1), // status message + ]) + .split(chunks[0]); + + f.render_widget( + Paragraph::new(" keybindings") + .alignment(Alignment::Center) + .style(Style::default().fg(MUTED_COLOR).add_modifier(Modifier::DIM)), + left_section[0] + ); + + f.render_widget( + Paragraph::new(app.status_message.as_str()) + .alignment(Alignment::Center) + .style(Style::default().fg(TEXT_COLOR)), + left_section[1] + ); + + f.render_widget(Paragraph::new(exp_txt).alignment(Alignment::Center).style(exp_style) + .block(Block::default().borders(Borders::ALL).border_style(Style::default().fg(BG_COLOR))), chunks[1]); + + f.render_widget(Paragraph::new(gen_txt).alignment(Alignment::Center).style(gen_style) + .block(Block::default().borders(Borders::ALL).border_style(Style::default().fg(BG_COLOR))), chunks[2]); +} + +fn render_popup(f: &mut Frame<'_>, app: &mut App) { + let options = app.get_current_options(); + if options.is_empty() { return; } + + let area = centered_rect(60, 20, f.area()); + f.render_widget(Clear, area); + f.render_widget(Block::default().borders(Borders::ALL).style(Style::default().bg(BG_COLOR).fg(ACCENT_COLOR)).title(" Select Option "), area); + + let items: Vec = options.iter().map(|opt| ListItem::new(format!(" {}", opt))).collect(); + let list = List::new(items) + .block(Block::default().borders(Borders::ALL).border_type(BorderType::Rounded)) + .highlight_style(Style::default().bg(ACCENT_COLOR).fg(Color::Black)); + + f.render_stateful_widget(list, area.inner(ratatui::layout::Margin { vertical: 2, horizontal: 2 }), &mut app.list_state); +} + +fn render_export_popup(f: &mut Frame<'_>, app: &mut App) { + let area = centered_rect(60, 40, f.area()); + f.render_widget(Clear, area); + let block = Block::default() + .borders(Borders::ALL) + .style(Style::default().bg(BG_COLOR).fg(TEXT_COLOR)) + .title(" Export Configuration "); + f.render_widget(block.clone(), area); + + let inner = block.inner(area); + let chunks = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints([ + Constraint::Length(2), // Filename Label + Constraint::Length(2), // Input + Constraint::Length(2), // Format Label + Constraint::Length(3), // options + Constraint::Min(1), + Constraint::Length(1), // Help + ]) + .split(inner); + + // Filename + let name_label_style = if app.export_focus_filename { Style::default().fg(ACCENT_COLOR) } else { Style::default().fg(TEXT_COLOR) }; + f.render_widget(Paragraph::new("Filename (no extension):").style(name_label_style), chunks[0]); + + let name_style = if app.export_focus_filename { Style::default().fg(Color::White) } else { Style::default().fg(MUTED_COLOR) }; + let border_style = if app.export_focus_filename { Style::default().fg(ACCENT_COLOR) } else { Style::default().fg(INPUT_BG) }; + + let name_cursor = if app.export_focus_filename { format!("{}█", app.export_filename) } else { app.export_filename.clone() }; + f.render_widget(Paragraph::new(name_cursor).block(Block::default().borders(Borders::BOTTOM).border_style(border_style)).style(name_style), chunks[1]); + + // Format + let fmt_label_style = if !app.export_focus_filename { Style::default().fg(ACCENT_COLOR) } else { Style::default().fg(TEXT_COLOR) }; + f.render_widget(Paragraph::new("Format:").style(fmt_label_style), chunks[2]); + + let fmts = ["YAML", "JSON", "TOML"]; + let mut spans = Vec::new(); + for (i, fmt) in fmts.iter().enumerate() { + let is_focused = !app.export_focus_filename; + let is_selected = i == app.export_format_idx; + + let radio = if is_selected { "(●)" } else { "( )" }; + let mut color = if is_selected { ACCENT_COLOR } else { MUTED_COLOR }; + if is_focused && is_selected { color = Color::Yellow; } // Highlight selection if focused + if is_focused && !is_selected { color = Color::White; } // Highlight potential selection options + + spans.push(Span::styled(radio, Style::default().fg(color))); + spans.push(Span::raw(" ")); + spans.push(Span::styled(*fmt, Style::default().fg(color))); + spans.push(Span::raw(" ")); + } + + f.render_widget(Paragraph::new(Line::from(spans)), chunks[3]); + + // Help + f.render_widget(Paragraph::new("Tab: Focus | ← →: Format | Enter: Save | Esc: Cancel").alignment(Alignment::Center).style(Style::default().fg(MUTED_COLOR)), chunks[5]); +} + +fn render_config_popup(f: &mut Frame<'_>, app: &mut App) { + let area = centered_rect(40, 20, f.area()); + f.render_widget(Clear, area); + let block = Block::default() + .borders(Borders::ALL) + .style(Style::default().bg(BG_COLOR).fg(TEXT_COLOR)) + .title(" TUI Configuration "); + f.render_widget(block.clone(), area); + + let inner = block.inner(area); + let chunks = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints([ + Constraint::Length(3), // Option + Constraint::Min(1), + Constraint::Length(1), // Help + ]) + .split(inner); + + let check = if app.extract_project { "[x]" } else { "[ ]" }; + let color = if app.extract_project { ACCENT_COLOR } else { TEXT_COLOR }; + + let line = Line::from(vec![ + Span::styled(check, Style::default().fg(color).add_modifier(Modifier::BOLD)), + Span::raw(" Extract project zip after download"), + ]); + + f.render_widget(Paragraph::new(line), chunks[0]); + f.render_widget(Paragraph::new("Space/Enter: Toggle | Esc: Close").alignment(Alignment::Center).style(Style::default().fg(MUTED_COLOR)), chunks[2]); +} + +fn render_message_popup(f: &mut Frame<'_>, app: &mut App) { + let area = centered_rect(50, 20, f.area()); + f.render_widget(Clear, area); + let title_color = if app.message_popup_is_error { Color::Red } else { ACCENT_COLOR }; + + let block = Block::default() + .borders(Borders::ALL) + .style(Style::default().bg(BG_COLOR).fg(TEXT_COLOR)) + .title(Span::styled(&app.message_popup_title, Style::default().fg(title_color).add_modifier(Modifier::BOLD))); + + let inner_area = block.inner(area); + f.render_widget(block, area); + + let p = Paragraph::new(app.message_popup_text.as_str()) + .wrap(Wrap { trim: true }) + .alignment(Alignment::Center) + .style(Style::default().fg(TEXT_COLOR)); + + // Vertically center text + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Min(1), + Constraint::Length(1), // Help footer + ]) + .split(inner_area); + + f.render_widget(p, chunks[0]); + f.render_widget(Paragraph::new("Press Enter to Close").alignment(Alignment::Center).style(Style::default().fg(MUTED_COLOR)), chunks[1]); +} + +fn render_input_dialog(_f: &mut Frame<'_>, _app: &mut App) { + // Input is rendered inline in draw_text_input, but we need to block other rendering if strictly modal. + // However, for this UI, inline editing looks better. We just consume keys. + // Visual feedback is already in draw_text_input via cursor char. +} + +fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ]).split(r); + + Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ]).split(popup_layout[1])[1] +} + +// --- RUN LOOP --- + +pub async fn run() -> Result<(), Box> { + println!("Fetching Spring Initializr capabilities..."); + let capabilities = api::get_capabilities().await?; + println!("Fetching available dependencies..."); + let dependencies = api::get_dependencies().await?; + println!("Starting TUI..."); + + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + let mut app = App::new(capabilities, dependencies); + let res = run_app(&mut terminal, &mut app).await; + + disable_raw_mode()?; + execute!(terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?; + terminal.show_cursor()?; + + if let Err(err) = res { + println!("Error: {:?}", err); + } + Ok(()) +} + +async fn run_app(terminal: &mut Terminal, app: &mut App) -> Result<(), Box> +where + ::Error: std::error::Error + Send + Sync + 'static, +{ + loop { + terminal.draw(|f| ui(f, app))?; + + if let Event::Key(key) = event::read()? { + if app.input_mode { + match key.code { + KeyCode::Enter => app.finish_edit(), + KeyCode::Esc => { app.input_mode = false; app.input_buffer.clear(); } + KeyCode::Char(c) => app.input_buffer.push(c), + KeyCode::Backspace => { app.input_buffer.pop(); } + _ => {} + } + } else if app.show_popup { + match key.code { + KeyCode::Esc => app.show_popup = false, + KeyCode::Down | KeyCode::Char('j') => { + let i = app.list_state.selected().unwrap_or(0); + let len = app.get_current_options().len(); + if len > 0 { app.list_state.select(Some((i + 1) % len)); } + } + KeyCode::Up | KeyCode::Char('k') => { + let i = app.list_state.selected().unwrap_or(0); + let len = app.get_current_options().len(); + if len > 0 { app.list_state.select(Some(if i == 0 { len - 1 } else { i - 1 })); } + } + KeyCode::Enter => app.select_option(), + _ => {} + } + } else if app.show_export_popup { + match key.code { + KeyCode::Esc => app.show_export_popup = false, + KeyCode::Tab => app.export_focus_filename = !app.export_focus_filename, + KeyCode::Left | KeyCode::Char('h') => { + if !app.export_focus_filename { + if app.export_format_idx > 0 { app.export_format_idx -= 1; } + } + } + KeyCode::Right | KeyCode::Char('l') => { + if !app.export_focus_filename { + if app.export_format_idx < 2 { app.export_format_idx += 1; } + } + } + KeyCode::Char(c) => { + if app.export_focus_filename { + app.export_filename.push(c); + } + } + KeyCode::Backspace => { + if app.export_focus_filename { + app.export_filename.pop(); + } + } + KeyCode::Enter => { + let fmt = match app.export_format_idx { + 0 => FileType::Yaml, + 1 => FileType::Json, + _ => FileType::Toml, + }; + let name = if app.export_filename.is_empty() { None } else { Some(app.export_filename.clone()) }; + + match generator::generate_project_config_file(&app.config, fmt, name) { + Ok(_) => { + app.status_message = "Config Exported!".to_string(); + app.show_message_popup = true; + app.message_popup_title = "Success".to_string(); + app.message_popup_text = "Configuration file exported successfully.".to_string(); + app.message_popup_is_error = false; + }, + Err(e) => { + app.status_message = format!("Export Error: {}", e); + app.show_message_popup = true; + app.message_popup_title = "Export Failed".to_string(); + app.message_popup_text = e.to_string(); + app.message_popup_is_error = true; + }, + } + app.show_export_popup = false; + } + _ => {} + } + } else if app.show_config_popup { + match key.code { + KeyCode::Esc | KeyCode::Char('C') => app.show_config_popup = false, + KeyCode::Enter | KeyCode::Char(' ') => app.extract_project = !app.extract_project, + _ => {} + } + } else if app.show_message_popup { + match key.code { + KeyCode::Esc | KeyCode::Enter | KeyCode::Char(' ') => app.show_message_popup = false, + _ => {} + } + } else { + match key.code { + KeyCode::Char('q') | KeyCode::Char('Q') => return Ok(()), + KeyCode::Char('C') => app.show_config_popup = true, + KeyCode::Tab => app.toggle_pane(), + KeyCode::Down | KeyCode::Char('j') => { + match app.active_pane { + ActivePane::Config => app.next_field(), + ActivePane::Dependencies => app.next_dependency(), + } + } + KeyCode::Up | KeyCode::Char('k') => { + match app.active_pane { + ActivePane::Config => app.previous_field(), + ActivePane::Dependencies => app.previous_dependency(), + } + } + KeyCode::Char(' ') => { + if app.active_pane == ActivePane::Dependencies { + app.toggle_dependency(); + } + } + KeyCode::Enter => { + if app.current_field == Field::Generate && app.active_pane == ActivePane::Config { + app.status_message = "Generating...".to_string(); + terminal.draw(|f| ui(f, app))?; + match generator::generate_project(&app.config, app.extract_project).await { + Ok(_) => { + app.status_message = "Success! Saved.".to_string(); + app.show_message_popup = true; + app.message_popup_title = "Success".to_string(); + app.message_popup_text = format!("Project '{}' generated successfully!", app.config.artifact_id); + app.message_popup_is_error = false; + }, + Err(e) => { + app.status_message = format!("Error: {}", e); + app.show_message_popup = true; + app.message_popup_title = "Generation Failed".to_string(); + app.message_popup_text = e.to_string(); + app.message_popup_is_error = true; + }, + } + } else if app.current_field == Field::Export && app.active_pane == ActivePane::Config { + app.show_export_popup = true; + app.export_focus_filename = true; + } else if app.active_pane == ActivePane::Config { + if !app.get_current_options().is_empty() { + app.show_popup = true; + app.list_state.select(Some(0)); + } else { + app.start_edit(); + } + } else if app.active_pane == ActivePane::Dependencies { + app.toggle_dependency(); + } + } + KeyCode::Backspace => { + if app.active_pane == ActivePane::Dependencies && !app.deps_search.is_empty() { + app.deps_search.pop(); + app.deps_list_state.select(Some(0)); + } + } + KeyCode::Char(c) => { + if !c.is_control() { + if app.active_pane == ActivePane::Dependencies { + app.deps_search.push(c); + app.deps_list_state.select(Some(0)); + } else if c == 'g' || c == 'G' { + // optional shortcuts for Config pane? + // Let's keep 'g' for generate if in Config pane + if app.active_pane == ActivePane::Config { + app.current_field = Field::Generate; + } + } + } + } + _ => {} + } + } + } + } +} \ No newline at end of file diff --git a/src/types/api.rs b/src/types/api.rs new file mode 100644 index 0000000..f61c50d --- /dev/null +++ b/src/types/api.rs @@ -0,0 +1,73 @@ +use serde::Deserialize; +use std::collections::HashMap; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(dead_code)] +pub struct InitializrDependencies { + pub boot_version: String, + pub dependencies: HashMap, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(dead_code)] +pub struct Dependency { + pub group_id: String, + pub artifact_id: String, + pub scope: String, + pub bom: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InitializrCapabilities { + #[serde(rename = "_links")] + pub links: HashMap, + pub packaging: Option, + pub java_version: Option, + pub language: Option, + pub boot_version: Option, + #[serde(rename = "type")] + pub project_type: Option, + pub group_id: Option, + pub artifact_id: Option, + pub version: Option, + pub name: Option, + pub description: Option, + pub package_name: Option, +} + +#[derive(Debug, Deserialize)] +#[allow(dead_code)] +pub struct Link { + pub href: String, + pub templated: Option, +} + +#[derive(Debug, Deserialize)] +#[allow(dead_code)] +pub struct CapabilityGroup { + #[serde(rename = "type")] + pub capability_type: String, + pub default: Option, + pub values: Vec, +} + +#[derive(Debug, Deserialize)] +#[allow(dead_code)] +pub struct CapabilityValue { + pub id: String, + pub name: String, + pub description: Option, + pub action: Option, + pub tags: Option>, +} + +#[derive(Debug, Deserialize)] +#[allow(dead_code)] +pub struct TextCapability { + #[serde(rename = "type")] + pub capability_type: String, + pub default: String, +} \ No newline at end of file diff --git a/src/types/config.rs b/src/types/config.rs new file mode 100644 index 0000000..509311e --- /dev/null +++ b/src/types/config.rs @@ -0,0 +1,41 @@ +use std::path::Path; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct GlobalConfig { + pub dir: String, +} + +impl GlobalConfig {} + +pub enum FileType { + Yaml, + Json, + Toml, +} + +// will add file path later +pub struct ConfigMetadata { + pub file_type: FileType, +} + +impl ConfigMetadata { + pub fn from_path(file_path: String) -> anyhow::Result { + let extension = Path::new(&file_path) + .extension() + .and_then(|s| s.to_str()) + .ok_or_else(|| anyhow::anyhow!("No file extension found"))?; + + let current_file_type = match extension { + "yaml" | "yml" => FileType::Yaml, + "json" => FileType::Json, + "toml" => FileType::Toml, + _ => anyhow::bail!("Unsuppored file type"), + }; + + Ok(Self { + file_type: current_file_type, + }) + } +} diff --git a/src/types/generic.rs b/src/types/generic.rs new file mode 100644 index 0000000..5faaf6b --- /dev/null +++ b/src/types/generic.rs @@ -0,0 +1,46 @@ + +use serde::{Serialize,Deserialize}; + +#[derive(Serialize, Deserialize,Debug)] +#[serde(rename_all = "camelCase")] +pub struct SprintInitConfig { + /// gradle-kotline, gradle-groovy , gradle-mavn + pub project_type: String, + /// java, kotlin, grovvyy + pub language: String, + /// 5.0.5 + pub platform_version: String, + /// packageing : jar / war + pub packaging: String, + ///configurationFileFormat: properties/yaml + pub configuration_file_format: String, + /// 25,21,17 + pub java_version: i32, + /// com.example + pub group_id: String, + /// demo + pub artifact_id: String, + /// demo + pub name: String, + ///=Demo%20project%20for%20Spring%20Boot + pub description: String, + /// com.exmaple.demo = groupId + name + pub package_name: String, + ///lombok,devtools + pub dependencies: String, + /// boot version : 2.4.5 SNAPSHOT + pub boot_version: String, + /// version : 0.0.1-SNAPSHOT + pub version: String, +} + + +#[derive(Serialize, Deserialize,Debug)] +#[serde(rename_all = "camelCase")] +pub struct ErrorResponse { + pub timestamp: String, + pub status: i32, + pub error: String, + pub message: String, + pub path: String, +} \ No newline at end of file diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..eaeea97 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,3 @@ +pub mod api; +pub mod config; +pub mod generic; diff --git a/tests/cli.rs b/tests/cli.rs new file mode 100644 index 0000000..441cd80 --- /dev/null +++ b/tests/cli.rs @@ -0,0 +1,161 @@ +use spring_tui::cli::{Args, CliResult}; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cli_result_default() { + let result = CliResult::default(); + assert!(result.file.is_none()); + assert!(result.extract.is_none()); + } + + #[test] + fn test_args_derive_parser() { + // Test that Args can be instantiated + let args = Args { + file: Some("config.yaml".to_string()), + extract: false, + }; + + assert_eq!(args.file, Some("config.yaml".to_string())); + assert_eq!(args.extract, false); + } + + #[test] + fn test_parse_no_file_returns_none() { + // Simulate no arguments by testing the logic directly + let args = Args { + file: None, + extract: false, + }; + + // Test the logic inline since we can't easily mock Args::parse() + let result = if args.file.is_none() { + None + } else { + Some(CliResult { + file: args.file.clone(), + extract: args.file.as_ref().map(|_| args.extract), + }) + }; + + assert!(result.is_none()); + } + + #[test] + fn test_parse_with_file_only() { + let args = Args { + file: Some("test.yaml".to_string()), + extract: false, + }; + + let result = if args.file.is_none() { + None + } else { + Some(CliResult { + file: args.file.clone(), + extract: args.file.as_ref().map(|_| args.extract), + }) + }; + + assert!(result.is_some()); + let cli_result = result.unwrap(); + assert_eq!(cli_result.file, Some("test.yaml".to_string())); + assert_eq!(cli_result.extract, Some(false)); + } + + #[test] + fn test_parse_with_file_and_extract() { + let args = Args { + file: Some("config.json".to_string()), + extract: true, + }; + + let result = if args.file.is_none() { + None + } else { + Some(CliResult { + file: args.file.clone(), + extract: args.file.as_ref().map(|_| args.extract), + }) + }; + + assert!(result.is_some()); + let cli_result = result.unwrap(); + assert_eq!(cli_result.file, Some("config.json".to_string())); + assert_eq!(cli_result.extract, Some(true)); + } + + #[test] + fn test_extract_only_matters_with_file() { + // When file is None, extract should not be captured + let args_no_file = Args { + file: None, + extract: true, + }; + + let result = if args_no_file.file.is_none() { + None + } else { + Some(CliResult { + file: args_no_file.file.clone(), + extract: args_no_file.file.as_ref().map(|_| args_no_file.extract), + }) + }; + + assert!(result.is_none(), "Extract should not matter when file is not set"); + + // When file is Some, extract should be captured + let args_with_file = Args { + file: Some("config.toml".to_string()), + extract: true, + }; + + let result = if args_with_file.file.is_none() { + None + } else { + Some(CliResult { + file: args_with_file.file.clone(), + extract: args_with_file.file.as_ref().map(|_| args_with_file.extract), + }) + }; + + assert!(result.is_some()); + let cli_result = result.unwrap(); + assert_eq!(cli_result.extract, Some(true), "Extract should be captured when file is set"); + } + + #[test] + fn test_different_file_formats() { + let formats = vec![ + "config.yaml", + "config.json", + "config.toml", + "/absolute/path/to/config.yaml", + "./relative/path/config.json", + ]; + + for format in formats { + let args = Args { + file: Some(format.to_string()), + extract: false, + }; + + let result = if args.file.is_none() { + None + } else { + Some(CliResult { + file: args.file.clone(), + extract: args.file.as_ref().map(|_| args.extract), + }) + }; + + assert!(result.is_some()); + let cli_result = result.unwrap(); + assert_eq!(cli_result.file, Some(format.to_string())); + } + } +} + diff --git a/tests/integration_test.rs b/tests/integration_test.rs new file mode 100644 index 0000000..42ca9eb --- /dev/null +++ b/tests/integration_test.rs @@ -0,0 +1,36 @@ + +#[cfg(test)] +mod integration_tests { + use spring_tui::config::create_project_from_config; + use std::path::Path; + use std::fs; + + #[tokio::test] + async fn test_example_files_generation() { + // Map of config file to expected output file (zip) + // Based on the content of the example files (artifactId) + let examples = vec![ + ("example/rake-service-config.json", "snake.zip"), + ("example/rake-service-config.yaml", "rake.zip"), + ("example/security-config.toml", "hopper.zip"), + ]; + + for (config_path, output_file) in examples { + assert!(Path::new(config_path).exists(), "Example file {} not found in current directory", config_path); + + println!("Testing generation from {}", config_path); + + // Run the generation (extract = false -> creates zip) + let result = create_project_from_config(config_path, false).await; + + assert!(result.is_ok(), "Failed to generate project from {}: {:?}", config_path, result.err()); + + // Check if output file exists + let zip_path = Path::new(output_file); + assert!(zip_path.exists(), "Expected output file {} was not created", output_file); + + // Cleanup + let _ = fs::remove_file(zip_path); + } + } +} \ No newline at end of file