diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml new file mode 100644 index 0000000..0a4382e --- /dev/null +++ b/.github/workflows/build_native.yml @@ -0,0 +1,15 @@ +name: Build native +on: [push, pull_request] +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Build + run: cargo build --verbose + - name: Test + run: cargo test --verbose diff --git a/.github/workflows/build.yml b/.github/workflows/build_web.yml similarity index 98% rename from .github/workflows/build.yml rename to .github/workflows/build_web.yml index 31f3401..ee537e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build_web.yml @@ -1,4 +1,4 @@ -name: Build +name: Build web on: [push, pull_request] permissions: contents: write diff --git a/Cargo.lock b/Cargo.lock index 585ec8f..3ec1e9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" dependencies = [ "async-task", "concurrent-queue", @@ -224,9 +224,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" dependencies = [ "async-io", "async-lock", @@ -248,9 +248,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", @@ -347,21 +347,15 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" - -[[package]] -name = "cassowary" -version = "0.3.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" [[package]] name = "cc" -version = "1.0.102" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779e6b7d17797c0b42023d417228c02889300190e700cb074c3438d9c541d332" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" [[package]] name = "cfg-if" @@ -377,9 +371,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.8" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", "clap_derive", @@ -387,9 +381,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstream", "anstyle", @@ -403,7 +397,7 @@ version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn", @@ -415,6 +409,20 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +[[package]] +name = "cliclack" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "178885eafa17af5b359629e9a4d278940ae790c0bff75843985dc65574c3ac42" +dependencies = [ + "console", + "indicatif", + "once_cell", + "strsim", + "textwrap", + "zeroize", +] + [[package]] name = "colorchoice" version = "1.0.1" @@ -430,6 +438,19 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -470,31 +491,6 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags 2.6.0", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "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.6" @@ -544,10 +540,10 @@ dependencies = [ ] [[package]] -name = "either" -version = "1.13.0" +name = "encode_unicode" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" @@ -828,12 +824,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -871,9 +861,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -900,9 +890,9 @@ checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", @@ -953,9 +943,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" dependencies = [ "bytes", "futures-channel", @@ -992,10 +982,26 @@ dependencies = [ ] [[package]] -name = "indoc" -version = "2.0.5" +name = "indicatif" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] [[package]] name = "ipnet" @@ -1015,15 +1021,6 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.11" @@ -1039,6 +1036,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.155" @@ -1150,7 +1153,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "log", "wasi", "windows-sys 0.48.0", ] @@ -1195,6 +1197,12 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "objc" version = "0.2.7" @@ -1325,15 +1333,9 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "percent-encoding" version = "2.3.1" @@ -1410,6 +1412,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1482,23 +1490,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "ratatui" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e2e4cd95294a85c3b4446e63ef054eea43e0205b1fd60120c16b74ff7ff96ad" -dependencies = [ - "bitflags 2.6.0", - "cassowary", - "crossterm", - "indoc", - "itertools", - "paste", - "strum 0.25.0", - "unicode-segmentation", - "unicode-width", -] - [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1507,9 +1498,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -1645,9 +1636,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" dependencies = [ "once_cell", "rustls-pki-types", @@ -1674,9 +1665,9 @@ checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" dependencies = [ "ring", "rustls-pki-types", @@ -1712,9 +1703,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -1725,9 +1716,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -1735,9 +1726,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -1755,9 +1746,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -1766,9 +1757,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.118" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -1809,27 +1800,6 @@ dependencies = [ "digest", ] -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1894,35 +1864,13 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros 0.25.3", -] - [[package]] name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros 0.26.4", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn", + "strum_macros", ] [[package]] @@ -1931,7 +1879,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", @@ -1967,9 +1915,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.68" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", @@ -2020,19 +1968,18 @@ name = "templateer" version = "0.1.0" dependencies = [ "bytes", - "crossterm", + "clap", + "cliclack", "futures", "js-sys", "miette", - "ratatui", "reqwest", "rfd", "serde", "serde-wasm-bindgen", "serde_json", - "strum 0.26.3", + "strum", "tokio", - "tui-textarea", "version_resolver", "wasm-bindgen", "wasm-bindgen-futures", @@ -2063,18 +2010,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -2083,9 +2030,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2098,9 +2045,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" dependencies = [ "backtrace", "bytes", @@ -2241,17 +2188,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tui-textarea" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ae7c920c9e7d428f8d0d1a7e371833deb932b358f588afceac6c58d34cef5f" -dependencies = [ - "crossterm", - "ratatui", - "unicode-width", -] - [[package]] name = "typenum" version = "1.17.0" @@ -2296,12 +2232,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - [[package]] name = "unicode-width" version = "0.1.13" @@ -2361,7 +2291,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "strum 0.26.3", + "strum", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -2497,7 +2427,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2517,18 +2447,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "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]] @@ -2539,9 +2469,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2551,9 +2481,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2563,15 +2493,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2581,9 +2511,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2593,9 +2523,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2605,9 +2535,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2617,9 +2547,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -2728,12 +2658,26 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zip" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" +checksum = "e29ab4097989787b2029a5981c41b7bfb427b5a601e23f455daacb4d0360a9e9" dependencies = [ "arbitrary", "crc32fast", diff --git a/Cargo.toml b/Cargo.toml index d61d11d..004b8d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,9 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" strum = { version = "0.26", features = ["derive"] } tokio = { version = "1", features = ["full"] } +rfd = "0.14" +cliclack = "0.3.2" +clap = { version = "4.5", features = ["derive"] } [workspace.dependencies.web-sys] version = "0.3.64" @@ -42,7 +45,6 @@ crate-type = ["cdylib", "rlib"] miette = { workspace = true } reqwest = { workspace = true } futures = "0.3" -rfd = "0.14" zip = { version = "2.1.3", default-features = false, features = ["deflate"] } serde = { workspace = true } serde_json = { workspace = true } @@ -53,9 +55,8 @@ bytes = "1.6" [target.'cfg(not(target_family = "wasm"))'.dependencies] miette = { workspace = true, features = ["fancy"] } tokio = { workspace = true } -ratatui = "0.23.0" -crossterm = "0.27.0" -tui-textarea = { version = "0.2.2", default-features = false, features = ["ratatui-crossterm"] } +cliclack = { workspace = true } +clap = { workspace = true } [target.'cfg(target_family = "wasm")'.dependencies] wasm-bindgen = { workspace = true } @@ -63,3 +64,4 @@ wasm-bindgen-futures = { workspace = true } js-sys = { workspace = true } web-sys = { workspace = true } serde-wasm-bindgen = "0.6" +rfd = { workspace = true } diff --git a/README.md b/README.md index e339e30..3130b45 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Template Generator -A generator for Architectury mod templates with a web UI and a TUI. +A generator for Architectury mod templates with a web UI and an interactive command line UI. ## Requirements diff --git a/src/app/generator.rs b/src/app/generator.rs index 93de0f2..19da425 100644 --- a/src/app/generator.rs +++ b/src/app/generator.rs @@ -14,26 +14,18 @@ use std::pin::Pin; use std::sync::Arc; use version_resolver::maven::{resolve_latest_version, resolve_matching_version, MavenLibrary}; -pub async fn generate(app: &super::GeneratorApp) -> Result<()> { +pub async fn generate(app: &super::GeneratorApp, filer_provider: &impl crate::filer::FilerProvider) -> Result<()> { let mut context = engine::Context::new(); - // This vec contains all dash-separated parts of the template file name. - // Example: my_mod-1.21-fabric-neoforge-template.zip - let mut file_name_parts: Vec = Vec::new(); // Mod properties context.put("PACKAGE_NAME", &app.package_name); context.put("PACKAGE_DIR", &app.package_name.replace(".", "/")); - let mut mod_id: String = app.mod_id.clone(); - if mod_id.is_empty() { - mod_id = crate::mod_ids::to_mod_id(&app.mod_name); - } - file_name_parts.push(mod_id.clone()); + let mod_id: String = app.get_effective_mod_id(); context.put("MOD_ID", mod_id); let escaped_name = escape_json_and_toml(&app.mod_name); context.put("MOD_NAME", escaped_name); // Game version-specific let game_version = app.game_version; - file_name_parts.push(game_version.version().to_owned()); context.put("MINECRAFT_VERSION", game_version.version()); context.put( "GRADLE_JAVA_VERSION", @@ -166,11 +158,6 @@ pub async fn generate(app: &super::GeneratorApp) -> Result<()> { platforms.push("quilt"); } - // Add all platforms to template file name. - for platform in &platforms { - file_name_parts.push((*platform).to_owned()); - } - let platforms = platforms.join(","); context.put("ARCHITECTURY_PLATFORMS", platforms); @@ -198,7 +185,6 @@ pub async fn generate(app: &super::GeneratorApp) -> Result<()> { files.push(Box::pin(neoforge_only::neoforge_mods_toml_files(client.clone()))); } context.maybe_put("NEOFORGE_YARN_PATCH_VERSION", versions.neoforge_yarn_patch); - file_name_parts.push("neoforge-only".to_owned()); } ProjectType::Forge => { files.push(Box::pin(forge_only::all_files(client.clone()))); @@ -208,13 +194,9 @@ pub async fn generate(app: &super::GeneratorApp) -> Result<()> { std::future::ready(Ok(version)), ))); } - file_name_parts.push("forge-only".to_owned()); } } - // Add final template suffix to file name - file_name_parts.push("template".to_owned()); - // Resolve versions let (files, variables) = join!(join_all(files), join_all(variables)); let files: Vec = files @@ -228,8 +210,8 @@ pub async fn generate(app: &super::GeneratorApp) -> Result<()> { context.put(key, value); } - engine::filer::use_filer(|filer| { - let file_name = file_name_parts.join("-"); + filer_provider.use_filer(|filer| { + let file_name = compose_file_name(app); filer.set_file_name(file_name); for file_data in files { @@ -258,6 +240,41 @@ pub async fn generate(app: &super::GeneratorApp) -> Result<()> { .await } +pub fn compose_file_name(app: &super::GeneratorApp) -> String { + let mut file_name = app.get_effective_mod_id(); + file_name += "-"; + file_name += app.game_version.version(); + + match app.project_type { + ProjectType::Multiplatform => { + if app.subprojects.fabric && app.subprojects.quilt && app.subprojects.fabric_likes { + file_name += "-fabric-like"; + } else { + if app.subprojects.fabric { + file_name += "-fabric"; + } + + if app.subprojects.quilt { + file_name += "-quilt"; + } + } + + if app.subprojects.neoforge { + file_name += "-neoforge"; + } + + if app.subprojects.forge { + file_name += "-forge"; + } + }, + ProjectType::NeoForge => file_name += "-neoforge-only", + ProjectType::Forge => file_name += "-forge-only", + } + + file_name += "-template"; + file_name +} + fn add_key(key: &'static str, future: F) -> impl Future> where F: Future>, diff --git a/src/app/mod.rs b/src/app/mod.rs index d6288d6..7a21eea 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3,13 +3,14 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use serde::{Deserialize, Serialize}; +use strum::EnumIter; pub mod generator; pub mod versions; pub const SUBHEADING_STYLE: &'static str = "subheading"; -#[derive(Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ProjectType { #[default] Multiplatform, @@ -17,7 +18,7 @@ pub enum ProjectType { Forge, } -#[derive(Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, EnumIter)] pub enum MappingSet { #[default] Mojang, @@ -25,7 +26,14 @@ pub enum MappingSet { } impl MappingSet { - fn description(&self) -> &'static str { + pub fn name(&self) -> &'static str { + match self { + Self::Mojang => "Official Mojang mappings", + Self::Yarn => "Yarn", + } + } + + pub fn description(&self) -> &'static str { match self { Self::Mojang => "The official obfuscation maps published by Mojang.", Self::Yarn => "A libre mapping set maintained by FabricMC.", @@ -35,16 +43,16 @@ impl MappingSet { #[derive(Default, Serialize, Deserialize)] pub struct Subprojects { - fabric: bool, - forge: bool, - neoforge: bool, - quilt: bool, - fabric_likes: bool, + pub fabric: bool, + pub forge: bool, + pub neoforge: bool, + pub quilt: bool, + pub fabric_likes: bool, } #[derive(Serialize, Deserialize)] pub struct Dependencies { - architectury_api: bool, + pub architectury_api: bool, } impl Default for Dependencies { @@ -57,14 +65,14 @@ impl Default for Dependencies { #[derive(Serialize, Deserialize)] pub struct GeneratorApp { - mod_name: String, - mod_id: String, - package_name: String, - game_version: version_resolver::minecraft::MinecraftVersion, - project_type: ProjectType, - subprojects: Subprojects, - mapping_set: MappingSet, - dependencies: Dependencies, + pub mod_name: String, + pub mod_id: String, + pub package_name: String, + pub game_version: version_resolver::minecraft::MinecraftVersion, + pub project_type: ProjectType, + pub subprojects: Subprojects, + pub mapping_set: MappingSet, + pub dependencies: Dependencies, } impl GeneratorApp { @@ -80,4 +88,12 @@ impl GeneratorApp { dependencies: Default::default(), } } + + pub fn get_effective_mod_id(&self) -> String { + if self.mod_id.is_empty() { + crate::mod_ids::to_mod_id(&self.mod_name) + } else { + self.mod_id.clone() + } + } } diff --git a/src/app2/app.rs b/src/app2/app.rs deleted file mode 100644 index 43c628b..0000000 --- a/src/app2/app.rs +++ /dev/null @@ -1,57 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use super::screen::{Message, Screen}; -use miette::{IntoDiagnostic, Result}; -use ratatui::prelude::CrosstermBackend; -use ratatui::Terminal; -use std::cell::RefCell; -use std::rc::Rc; - -pub struct App { - terminal: Terminal>, - screen_stack: Vec>>, - should_exit: bool, -} - -impl App { - pub fn new(terminal: Terminal>) -> Self { - App { - terminal, - screen_stack: Vec::new(), - should_exit: false, - } - } - - pub fn push_screen(&mut self, screen: Rc>) { - self.screen_stack.push(screen); - } - - pub fn should_exit(&self) -> bool { - self.should_exit - } - - pub fn tick(&mut self) -> Result<()> { - if let Some(screen) = self.screen_stack.last_mut() { - let mut screen = screen.borrow_mut(); - self.terminal.draw(|f| screen.view(f)).into_diagnostic()?; - - let event = crossterm::event::read().into_diagnostic()?; - if let Some(message) = screen.input(event) { - drop(screen); - match message { - Message::OpenScreen(next) => self.screen_stack.push(next), - Message::CloseScreen => { - self.screen_stack.pop(); - if self.screen_stack.is_empty() { - self.should_exit = true; - } - } - } - } - } - - Ok(()) - } -} diff --git a/src/app2/focus.rs b/src/app2/focus.rs deleted file mode 100644 index a9275db..0000000 --- a/src/app2/focus.rs +++ /dev/null @@ -1,34 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -pub struct Focus { - count: usize, - selected: usize, -} - -impl Focus { - pub fn new(count: usize) -> Self { - Focus { count, selected: 0 } - } - - pub fn cycle(&mut self) { - self.selected = (self.selected + 1) % self.count; - } - - pub fn selected(&self) -> usize { - self.selected - } - - pub fn is_selected(&self, index: usize) -> bool { - self.selected == index - } - - pub fn choose_at(&self, index: usize, a: T, b: T) -> T { - if self.is_selected(index) { - a - } else { - b - } - } -} diff --git a/src/app2/mod.rs b/src/app2/mod.rs deleted file mode 100644 index 2308a8d..0000000 --- a/src/app2/mod.rs +++ /dev/null @@ -1,193 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -pub mod app; -pub mod focus; -pub mod screen; -pub mod widget; - -use crate::versions::ALL_MINECRAFT_VERSIONS; -use app::*; -use crossterm::event::Event; -use focus::*; -use ratatui::prelude::*; -use ratatui::widgets::{Block, Borders, Paragraph}; -use ratatui::{Frame, Terminal}; -use screen::*; -use std::cell::RefCell; -use std::io::Stderr; -use std::rc::Rc; -use tui_textarea::{Input, Key, TextArea}; -use widget::*; - -const HIGHLIGHTED_BLOCK_STYLE: Style = Style::new().fg(Color::LightBlue); - -struct MainScreen<'a> { - focus: Focus, - mod_name_area: TextArea<'a>, - mod_id_area: TextArea<'a>, - package_name_area: TextArea<'a>, - minecraft_version_dropdown: Dropdown, -} - -impl<'a> MainScreen<'a> { - fn new() -> Self { - let minecraft_version_dropdown = Dropdown::new( - ALL_MINECRAFT_VERSIONS - .iter() - .map(|version| (version.version, Style::default())) - .collect(), - ); - - MainScreen { - focus: Focus::new(4), - mod_name_area: TextArea::default(), - mod_id_area: TextArea::default(), - package_name_area: TextArea::default(), - minecraft_version_dropdown, - } - } - - fn focus_targets(&mut self) -> Vec> { - vec![ - Some(&mut self.mod_name_area), - Some(&mut self.mod_id_area), - Some(&mut self.package_name_area), - Some(&mut self.minecraft_version_dropdown), - ] - } -} - -impl<'a> Screen for MainScreen<'a> { - fn view(&self, f: &mut Frame>) { - let layout = Layout::default() - .direction(Direction::Horizontal) - .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) - .split(f.size()); - let left_layout = Layout::default() - .direction(Direction::Vertical) - .constraints(vec![ - Constraint::Percentage(20), - Constraint::Percentage(20), - Constraint::Percentage(20), - Constraint::Percentage(20), - Constraint::Percentage(20), - ]) - .split(layout[0]); - - let mod_name_block = - Block::new() - .title("Mod name") - .borders(Borders::ALL) - .style( - self.focus - .choose_at(0, HIGHLIGHTED_BLOCK_STYLE, Style::default()), - ); - let mod_name_layout = Layout::default() - .direction(Direction::Vertical) - .constraints(vec![Constraint::Min(1), Constraint::Length(1)]) - .split(mod_name_block.inner(left_layout[0])); - f.render_widget(mod_name_block, left_layout[0]); - f.render_widget( - Paragraph::new("The human-readable name of your mod."), - mod_name_layout[0], - ); - f.render_widget(self.mod_name_area.widget(), mod_name_layout[1]); - - let mod_id_block = Block::new() - .title("Mod ID (optional)") - .borders(Borders::ALL) - .style( - self.focus - .choose_at(1, HIGHLIGHTED_BLOCK_STYLE, Style::default()), - ); - let mod_id_layout = Layout::default() - .direction(Direction::Vertical) - .constraints(vec![Constraint::Min(1), Constraint::Length(1)]) - .split(mod_id_block.inner(left_layout[1])); - f.render_widget(mod_id_block, left_layout[1]); - f.render_widget( - Paragraph::new("A unique ID for your mod."), - mod_id_layout[0], - ); - f.render_widget(self.mod_id_area.widget(), mod_id_layout[1]); - - let package_name_block = Block::new() - .title("Package name") - .borders(Borders::ALL) - .style( - self.focus - .choose_at(2, HIGHLIGHTED_BLOCK_STYLE, Style::default()), - ); - let package_name_layout = Layout::default() - .direction(Direction::Vertical) - .constraints(vec![Constraint::Min(1), Constraint::Length(1)]) - .split(package_name_block.inner(left_layout[2])); - f.render_widget(package_name_block, left_layout[2]); - f.render_widget( - Paragraph::new("A unique package name for your mod."), - package_name_layout[0], - ); - f.render_widget(self.package_name_area.widget(), package_name_layout[1]); - - let minecraft_version_block = Block::new() - .title("Minecraft version") - .borders(Borders::ALL) - .style( - self.focus - .choose_at(3, HIGHLIGHTED_BLOCK_STYLE, Style::default()), - ); - f.render_widget( - self.minecraft_version_dropdown.widget(), - minecraft_version_block.inner(left_layout[3]), - ); - f.render_widget(minecraft_version_block, left_layout[3]); - - let mappings_block = Block::new().title("Mappings").borders(Borders::ALL); - let mappings_layout = Layout::default() - .direction(Direction::Vertical) - .constraints(vec![ - Constraint::Length(1), - Constraint::Min(1), - Constraint::Length(1), - ]) - .split(mappings_block.inner(left_layout[4])); - f.render_widget(mappings_block, left_layout[4]); - f.render_widget( - Paragraph::new("The set of names used for Minecraft code."), - mappings_layout[0], - ); - f.render_widget( - Paragraph::new("The official obfuscation maps published by Mojang."), - mappings_layout[2], - ); - } - - fn input(&mut self, event: Event) -> Option { - // Note: the text area crate's Input used here to prevent rapid repeated keystrokes - let input: Input = event.clone().into(); - match input { - Input { key: Key::Tab, .. } => { - self.focus.cycle(); - None - } - Input { key: Key::Esc, .. } => Some(Message::CloseScreen), - _ => { - let selected_focus_target = self.focus.selected(); - - if let Some(widget) = &mut self.focus_targets()[selected_focus_target] { - widget.input(event) - } else { - None - } - } - } - } -} - -pub fn create_app(terminal: Terminal>) -> App { - let mut app = App::new(terminal); - app.push_screen(Rc::new(RefCell::new(MainScreen::new()))); - app -} diff --git a/src/app2/screen.rs b/src/app2/screen.rs deleted file mode 100644 index 3c04b62..0000000 --- a/src/app2/screen.rs +++ /dev/null @@ -1,21 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use std::cell::RefCell; -use std::io::Stderr; -use std::rc::Rc; - -use crossterm::event::Event; -use ratatui::prelude::CrosstermBackend; -use ratatui::Frame; - -pub enum Message { - OpenScreen(Rc>), - CloseScreen, -} - -pub trait Screen { - fn view(&self, f: &mut Frame>); - fn input(&mut self, event: Event) -> Option; -} diff --git a/src/app2/widget.rs b/src/app2/widget.rs deleted file mode 100644 index 2746344..0000000 --- a/src/app2/widget.rs +++ /dev/null @@ -1,114 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use std::cell::RefCell; -use std::io::Stderr; -use std::rc::Rc; - -use crossterm::event::Event; -use ratatui::backend::CrosstermBackend; -use ratatui::prelude::Style; -use ratatui::widgets::{List, ListItem, ListState, Paragraph, Widget as RWidget}; -use ratatui::Frame; -use tui_textarea::{Input, Key, TextArea}; - -use crate::app2::screen::Screen; - -use super::screen::Message; - -pub trait Widget { - fn input(&mut self, event: Event) -> Option; -} - -impl<'a> Widget for TextArea<'a> { - fn input(&mut self, event: Event) -> Option { - TextArea::input(self, event); - None - } -} - -pub struct Dropdown { - items: Vec<(&'static str, Style)>, - state: Rc>, -} - -impl Dropdown { - pub fn new(items: Vec<(&'static str, Style)>) -> Self { - Self { - items, - state: Rc::new(RefCell::new(ListState::default().with_selected(Some(0)))), - } - } - - pub fn widget(&self) -> impl RWidget { - let state = self.state.borrow(); - if let Some(selected) = state.selected() { - let item_index = selected.min(self.items.len() - 1); - let (text, style) = &self.items[item_index]; - Paragraph::new(*text).style(style.clone()) - } else { - Paragraph::new("") - } - } -} - -impl Widget for Dropdown { - fn input(&mut self, event: Event) -> Option { - let input: Input = event.into(); - match input { - Input { - key: Key::Enter, .. - } => { - let list_items: Vec<_> = self - .items - .iter() - .map(|(text, style)| ListItem::new(*text).style(style.clone())) - .collect(); - let screen = DropdownScreen { - list: List::new(list_items), - state: self.state.clone(), - }; - Some(Message::OpenScreen(Rc::new(RefCell::new(screen)))) - } - _ => None, - } - } -} - -struct DropdownScreen<'a> { - list: List<'a>, - state: Rc>, -} - -impl<'a> Screen for DropdownScreen<'a> { - fn view(&self, f: &mut Frame>) { - let state = self.state.borrow(); - f.render_stateful_widget(self.list.clone(), f.size(), &mut state.clone()); - } - - fn input(&mut self, event: Event) -> Option { - let mut state = self.state.borrow_mut(); - - let input: Input = event.into(); - match input { - Input { key: Key::Up, .. } => { - if let Some(selected) = state.selected() { - state.select(Some(selected.saturating_sub(1))) - } - None - } - Input { key: Key::Down, .. } => { - if let Some(selected) = state.selected() { - let new_index = selected.saturating_add(1).min(self.list.len() - 1); - state.select(Some(new_index)) - } - None - } - Input { - key: Key::Enter, .. - } => Some(Message::CloseScreen), - _ => None, - } - } -} diff --git a/src/async_support.rs b/src/async_support.rs deleted file mode 100644 index fea1d15..0000000 --- a/src/async_support.rs +++ /dev/null @@ -1,13 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use std::future::Future; - -pub fn block_on(future: F) -> F::Output { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(future) -} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..25d3c32 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,229 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use clap::Parser; +use cliclack::{confirm, input, intro, multiselect, outro, select, spinner}; +use miette::{miette, Context, IntoDiagnostic, Result}; +use strum::IntoEnumIterator; +use version_resolver::minecraft::MinecraftVersion; +use std::path::PathBuf; + +use crate::{Dependencies, GeneratorApp, MappingSet, ProjectType, Subprojects}; +use crate::filer::{FilerProvider, ZipFilerProvider}; +use crate::filer::native::{DirectoryFilerProvider, FsZipWriteTarget}; + +#[derive(Parser)] +#[command(version)] +struct Args { + /// The project path (default: the current directory) + output: Option, + /// Output a zip instead of a directory + #[arg(short, long)] + zip: bool, +} + +pub async fn main() -> Result<()> { + let args = Args::parse(); + if args.zip { + let (file, default_name) = if let Some(output) = &args.output { + // If the file was provided, try to derive the mod name from it. + let name = output.file_name() + .and_then(|s| s.to_str()) + .map(|s| s.strip_suffix(".zip").unwrap_or(s)); + (FsZipWriteTarget::ZipFile(output.clone()), name) + } else { + // If the file wasn't provided, get the current dir and use the default file name inside. + let dir = get_current_dir()?; + (FsZipWriteTarget::InDirectory(dir), None) + }; + + run(ZipFilerProvider(file), default_name, |app| { + if let Some(output) = &args.output { + output.to_string_lossy().into_owned() + } else { + crate::generator::compose_file_name(app) + ".zip" + } + }) + .await? + } else { + let dir = if let Some(directory) = args.output { + directory + } else { + get_current_dir()? + }; + + if dir.exists() { + if !dir.is_dir() { + return Err(miette!("File {} is not a directory", dir.to_string_lossy())); + } + + // Check that the directory is empty. + let mut iter = tokio::fs::read_dir(&dir) + .await + .into_diagnostic() + .wrap_err("Could not check if the output directory is empty")?; + if iter.next_entry().await.into_diagnostic()?.is_some() { + return Err(miette!("Output directory {} is not empty", dir.to_string_lossy())); + } + } + + let default_name = dir.file_name().and_then(|s| s.to_str()); + run(DirectoryFilerProvider(&dir), default_name, |_| { + dir.to_string_lossy() + }) + .await? + } + Ok(()) +} + +async fn run(filer_provider: F, default_mod_name: Option<&str>, output_name_provider: N) -> Result<()> +where + F: FilerProvider, + N: FnOnce(&GeneratorApp) -> D, + D: std::fmt::Display, +{ + let app = prompt(default_mod_name)?; + let spinner = spinner(); + spinner.start("Generating..."); + crate::generator::generate(&app, &filer_provider).await?; + spinner.stop("Done!"); + outro(format!("Generated into {}!", output_name_provider(&app))).into_diagnostic()?; + Ok(()) +} + +fn prompt(default_name: Option<&str>) -> Result { + intro("Architectury Template Generator").into_diagnostic()?; + + let mut mod_name = input("Mod name"); + if let Some(name) = default_name { + mod_name = mod_name.default_input(name); + } + let mod_name: String = mod_name.interact().into_diagnostic()?; + + let mod_id: String = input("Mod ID") + .default_input(&crate::mod_ids::to_mod_id(&mod_name)) + .validate_interactively(ModIdValidate) + .interact().into_diagnostic()?; + + let package_name: String = input("Package name") + .interact().into_diagnostic()?; + + let mut versions: Vec<_> = MinecraftVersion::iter() + .map(|version| { + (version, version.version(), "") + }) + .collect(); + versions.reverse(); // newest first + let game_version = select("Minecraft version") + .items(&versions) + .interact().into_diagnostic()?; + + let mapping_sets: Vec<_> = MappingSet::iter() + .map(|set| { + (set, set.name(), set.description()) + }) + .collect(); + let mapping_set = select("Mappings") + .items(&mapping_sets) + .interact().into_diagnostic()?; + + let mut project_types = vec![ + (ProjectType::Multiplatform, "Multiplatform", ""), + ]; + if game_version.forge_major_version().is_some() { + project_types.push((ProjectType::Forge, "Forge", "")); + } + if game_version.neoforge_major().is_some() { + project_types.push((ProjectType::NeoForge, "NeoForge", "")); + } + let project_type: ProjectType = select("Project type") + .items(&project_types) + .interact().into_diagnostic()?; + + let mut subprojects = Subprojects::default(); + let mut dependencies = Dependencies::default(); + + if project_type == ProjectType::Multiplatform { + let mut subproject_options: Vec<_> = vec![ + (Subproject::Fabric, "Fabric", ""), + (Subproject::Forge, "Forge", ""), + (Subproject::NeoForge, "NeoForge", ""), + (Subproject::Quilt, "Quilt", ""), + ]; + subproject_options.retain(|(s, _, _)| s.is_available_on(&game_version)); + let chosen_subprojects = multiselect("Mod loaders") + .items(&subproject_options) + .interact().into_diagnostic()?; + + for subproject in chosen_subprojects { + subproject.apply_to(&mut subprojects); + } + + if subprojects.fabric && subprojects.quilt { + subprojects.fabric_likes = confirm("Fabric-like subproject (shared code between Fabric and Quilt)?") + .initial_value(subprojects.fabric_likes) + .interact().into_diagnostic()?; + } + + dependencies.architectury_api = confirm("Architectury API?") + .initial_value(dependencies.architectury_api) + .interact().into_diagnostic()?; + } + + let generator = GeneratorApp { + mod_name, + mod_id, + package_name, + game_version, + project_type, + subprojects, + mapping_set, + dependencies + }; + Ok(generator) +} + +fn get_current_dir() -> Result { + std::env::current_dir() + .into_diagnostic() + .wrap_err("Couldn't get current directory") +} + +// TODO: Can this be a function ref? +struct ModIdValidate; + +impl cliclack::Validate for ModIdValidate { + type Err = miette::Error; + + fn validate(&self, input: &String) -> Result<()> { + crate::mod_ids::validate_mod_id(input) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Subproject { + Fabric, + Forge, + NeoForge, + Quilt, +} + +impl Subproject { + pub fn is_available_on(&self, game_version: &MinecraftVersion) -> bool { + match self { + Self::Forge => game_version.forge_major_version().is_some(), + Self::NeoForge => game_version.neoforge_major().is_some(), + _ => true, + } + } + + pub fn apply_to(&self, settings: &mut crate::Subprojects) { + match self { + Self::Fabric => settings.fabric = true, + Self::Forge => settings.forge = true, + Self::NeoForge => settings.neoforge = true, + Self::Quilt => settings.quilt = true, + } + } +} diff --git a/src/filer/mod.rs b/src/filer/mod.rs new file mode 100644 index 0000000..3cfd3b7 --- /dev/null +++ b/src/filer/mod.rs @@ -0,0 +1,127 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use miette::{IntoDiagnostic, Result}; +use std::collections::HashSet; +use std::io::{Cursor, Seek, Write}; +use zip::write::SimpleFileOptions; + +// Platform impls +#[cfg(not(target_family = "wasm"))] +pub mod native; +#[cfg(target_family = "wasm")] +pub mod web; + +pub trait Filer { + fn set_file_name(&mut self, file_name: String); + fn save(&mut self, path: &str, content: &[u8], permissions: &FilePermissions) -> Result<()>; +} + +#[allow(async_fn_in_trait)] +pub trait FilerProvider { + async fn use_filer(&self, block: F) -> Result<()> + where + F: FnOnce(&mut dyn Filer) -> Result<()>; +} + +pub enum FilePermissions { + None, + Execute, +} + +impl FilePermissions { + pub fn unix(&self) -> u32 { + match self { + Self::None => 0o644, + Self::Execute => 0o755, + } + } +} + +pub struct ZipFiler<'a, W> +where + W: Write + Seek, +{ + writer: &'a mut zip::ZipWriter, + directories: HashSet, + pub file_name: Option, +} + +impl<'a, W> ZipFiler<'a, W> +where + W: Write + Seek, +{ + pub fn new(writer: &'a mut zip::ZipWriter) -> Self { + Self { + writer, + directories: HashSet::new(), + file_name: None, + } + } +} + +impl<'a, W> Filer for ZipFiler<'a, W> +where + W: Write + Seek, +{ + fn save(&mut self, path: &str, content: &[u8], permissions: &FilePermissions) -> Result<()> { + let parts: Vec<_> = path.split("/").collect(); + + for i in 0..(parts.len() - 1) { + let directory = format!("{}/", parts[0..=i].join("/")); + + if self.directories.insert(directory.clone()) { + self.writer + .add_directory(directory, SimpleFileOptions::default()) + .into_diagnostic()?; + } + } + + self.writer + .start_file( + path, + SimpleFileOptions::default().unix_permissions(permissions.unix()), + ) + .into_diagnostic()?; + self.writer.write_all(content).into_diagnostic()?; + Ok(()) + } + + fn set_file_name(&mut self, file_name: String) { + self.file_name = Some(file_name) + } +} + +pub struct ZipFilerProvider(pub T); + +impl FilerProvider for ZipFilerProvider { + async fn use_filer(&self, block: F) -> Result<()> + where + F: FnOnce(&mut dyn Filer) -> Result<()> { + let mut cursor = Cursor::new(Vec::new()); + let mut file_name: String = "template.zip".to_owned(); + + // Create and use the zip writer and filer. + // This is its own scope in order to drop the borrow to the cursor. + { + let mut writer = zip::ZipWriter::new(&mut cursor); + { + let mut filer = ZipFiler::new(&mut writer); + block(&mut filer)?; + + if let Some(custom_name) = filer.file_name { + file_name = custom_name + ".zip"; + } + } + writer.finish().into_diagnostic()?; + } + + self.0.write(file_name, cursor.get_ref()).await + } +} + +#[allow(async_fn_in_trait)] +pub trait ZipWriteTarget { + async fn write(&self, file_name: String, data: &[u8]) -> Result<()>; +} diff --git a/src/filer/native.rs b/src/filer/native.rs new file mode 100644 index 0000000..dc72d6e --- /dev/null +++ b/src/filer/native.rs @@ -0,0 +1,84 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use miette::{miette, Context, IntoDiagnostic, Result}; +use std::{fs, path}; + +pub struct DirectoryFilerProvider<'a>(pub &'a path::Path); + +impl<'a> super::FilerProvider for DirectoryFilerProvider<'a> { + async fn use_filer(&self, block: F) -> Result<()> + where + F: FnOnce(&mut dyn super::Filer) -> Result<()>, + { + block(&mut DirectoryFiler { path: self.0 }) + } +} + +struct DirectoryFiler<'a> { + path: &'a path::Path, +} + +#[cfg(target_family = "unix")] +fn update_permissions(path: &path::Path, permissions: &super::FilePermissions) -> Result<()> { + use std::os::unix::fs::PermissionsExt; + let file_permissions = fs::metadata(path).into_diagnostic()?.permissions(); + let new_mode = file_permissions.mode() | permissions.unix(); + let new_permissions = fs::Permissions::from_mode(new_mode); + fs::set_permissions(path, new_permissions).into_diagnostic() +} + +#[cfg(not(target_family = "unix"))] +fn update_permissions(_path: &path::Path, _permissions: &super::FilePermissions) -> Result<()> { + // Not supported on Windows + Ok(()) +} + +impl<'a> super::Filer for DirectoryFiler<'a> { + fn save( + &mut self, + path: &str, + content: &[u8], + permissions: &super::FilePermissions, + ) -> Result<()> { + let mut full_path = path::PathBuf::from(self.path); + full_path.push(path); + + if let Some(parent) = full_path.parent() { + fs::create_dir_all(parent).into_diagnostic()?; + } + + fs::write(&full_path, content).into_diagnostic()?; + update_permissions(&full_path, permissions)?; + Ok(()) + } + + fn set_file_name(&mut self, _file_name: String) { + // Don't do anything as the directory filer prompts for the output name. + } +} + +pub enum FsZipWriteTarget { + ZipFile(path::PathBuf), + InDirectory(path::PathBuf), +} + +impl super::ZipWriteTarget for FsZipWriteTarget { + async fn write(&self, file_name: String, data: &[u8]) -> Result<()> { + let path = match self { + Self::ZipFile(path) => path, + Self::InDirectory(path) => &path.join(file_name), + }; + + let exists = tokio::fs::try_exists(path) + .await + .into_diagnostic() + .wrap_err_with(|| format!("Could not check if file {} exists", path.to_string_lossy()))?; + if exists { + return Err(miette!("Output file {} already exists!", path.to_string_lossy())); + } + + tokio::fs::write(path, data).await.into_diagnostic() + } +} diff --git a/src/filer/web.rs b/src/filer/web.rs new file mode 100644 index 0000000..34d46f6 --- /dev/null +++ b/src/filer/web.rs @@ -0,0 +1,24 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use miette::{IntoDiagnostic, Result}; + +pub struct ZipSaveDialog; + +impl super::ZipWriteTarget for ZipSaveDialog { + async fn write(&self, file_name: String, data: &[u8]) -> Result<()> { + let saved = rfd::AsyncFileDialog::new() + .set_title("Choose where to save the template") + .add_filter("Zip file", &["zip"]) + .set_file_name(file_name) + .save_file() + .await; + + if let Some(file) = saved { + file.write(data).await.into_diagnostic()?; + } + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 1eb2ed6..f17b466 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,8 @@ pub mod app; #[cfg(not(target_family = "wasm"))] -pub mod app2; -#[cfg(not(target_family = "wasm"))] -pub mod async_support; +pub mod cli; +pub mod filer; pub mod mod_ids; pub mod tap; pub mod templates; diff --git a/src/main.rs b/src/main.rs index c8e6ed1..981c7ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,32 +3,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. #[cfg(not(target_family = "wasm"))] -fn main() -> Result<()> { - use miette::{IntoDiagnostic, Result}; - use ratatui::prelude::*; - use templateer::app2::create_app; - - // Set up Crossterm - crossterm::terminal::enable_raw_mode().into_diagnostic()?; - crossterm::execute!(std::io::stderr(), crossterm::terminal::EnterAlternateScreen) - .into_diagnostic()?; - - let terminal = Terminal::new(CrosstermBackend::new(std::io::stderr())).into_diagnostic()?; - let mut app = create_app(terminal); - - loop { - app.tick()?; - if app.should_exit() { - break; - } - } - - // Clean up - crossterm::execute!(std::io::stderr(), crossterm::terminal::LeaveAlternateScreen) - .into_diagnostic()?; - crossterm::terminal::disable_raw_mode().into_diagnostic()?; - - Ok(()) +#[tokio::main] +async fn main() -> miette::Result<()> { + templateer::cli::main().await } #[cfg(target_family = "wasm")] diff --git a/src/templates/engine/filer.rs b/src/templates/engine/filer.rs deleted file mode 100644 index 2cf1336..0000000 --- a/src/templates/engine/filer.rs +++ /dev/null @@ -1,205 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use miette::{IntoDiagnostic, Result}; -use rfd::AsyncFileDialog; -use std::collections::HashSet; -use std::io::{Cursor, Seek, Write}; -use zip::write::SimpleFileOptions; - -pub trait Filer { - fn set_file_name(&mut self, file_name: String); - fn save(&mut self, path: &str, content: &[u8], permissions: &FilePermissions) -> Result<()>; -} - -pub enum FilePermissions { - None, - Execute, -} - -impl FilePermissions { - pub fn unix(&self) -> u32 { - match self { - Self::None => 0o644, - Self::Execute => 0o755, - } - } -} - -pub struct ZipFiler<'a, W> -where - W: Write + Seek, -{ - writer: &'a mut zip::ZipWriter, - directories: HashSet, - pub file_name: Option, -} - -impl<'a, W> ZipFiler<'a, W> -where - W: Write + Seek, -{ - pub fn new(writer: &'a mut zip::ZipWriter) -> Self { - Self { - writer, - directories: HashSet::new(), - file_name: None, - } - } -} - -impl<'a, W> Filer for ZipFiler<'a, W> -where - W: Write + Seek, -{ - fn save(&mut self, path: &str, content: &[u8], permissions: &FilePermissions) -> Result<()> { - let parts: Vec<_> = path.split("/").collect(); - - for i in 0..(parts.len() - 1) { - let directory = format!("{}/", parts[0..=i].join("/")); - - if self.directories.insert(directory.clone()) { - self.writer - .add_directory(directory, SimpleFileOptions::default()) - .into_diagnostic()?; - } - } - - self.writer - .start_file( - path, - SimpleFileOptions::default().unix_permissions(permissions.unix()), - ) - .into_diagnostic()?; - self.writer.write_all(content).into_diagnostic()?; - Ok(()) - } - - fn set_file_name(&mut self, file_name: String) { - self.file_name = Some(file_name) - } -} - -#[cfg(not(target_family = "wasm"))] -pub use native::use_filer; - -#[cfg(not(target_family = "wasm"))] -mod native { - use miette::{IntoDiagnostic, Result}; - use rfd::FileDialog; - use std::{fs, path}; - - pub async fn use_filer(block: F) -> Result<()> - where - F: FnOnce(&mut dyn super::Filer) -> Result<()>, - { - let saved = FileDialog::new() - .set_title("Choose where to save the template") - .pick_folder(); - - if let Some(directory) = saved { - let mut filer = DirectoryFiler { - path: directory.as_ref(), - }; - block(&mut filer)?; - } - - // TODO: Return error when cancelled - Ok(()) - } - - struct DirectoryFiler<'a> { - path: &'a path::Path, - } - - #[cfg(target_family = "unix")] - fn update_permissions(path: &path::Path, permissions: &super::FilePermissions) -> Result<()> { - use std::os::unix::fs::PermissionsExt; - let file_permissions = fs::metadata(path).into_diagnostic()?.permissions(); - let new_mode = file_permissions.mode() | permissions.unix(); - let new_permissions = Permissions::from_mode(new_mode); - fs::set_permissions(path, new_permissions).into_diagnostic() - } - - #[cfg(not(target_family = "unix"))] - fn update_permissions(path: &path::Path, permissions: &super::FilePermissions) -> Result<()> { - // Not supported on Windows - Ok(()) - } - - impl<'a> super::Filer for DirectoryFiler<'a> { - fn save( - &mut self, - path: &str, - content: &[u8], - permissions: &super::FilePermissions, - ) -> Result<()> { - let mut full_path = path::PathBuf::from(self.path); - full_path.push(path); - - if let Some(parent) = full_path.parent() { - fs::create_dir_all(parent).into_diagnostic()?; - } - - fs::write(&full_path, content).into_diagnostic()?; - update_permissions(&full_path, permissions)?; - Ok(()) - } - - fn set_file_name(&mut self, _file_name: String) { - // Don't do anything as the directory filer prompts for the output name. - } - } -} - -pub async fn use_zip_filer(block: F) -> Result<()> -where - F: FnOnce(&mut dyn Filer) -> Result<()>, -{ - let mut cursor = Cursor::new(Vec::new()); - let mut file_name: String = "template.zip".to_owned(); - - // Create and use the zip writer and filer. - // This is its own scope in order to drop the borrow to the cursor. - { - let mut writer = zip::ZipWriter::new(&mut cursor); - { - let mut filer = ZipFiler::new(&mut writer); - block(&mut filer)?; - - if let Some(custom_name) = filer.file_name { - file_name = custom_name + ".zip"; - } - } - writer.finish().into_diagnostic()?; - } - - let saved = AsyncFileDialog::new() - .set_title("Choose where to save the template") - .add_filter("Zip file", &["zip"]) - .set_file_name(file_name) - .save_file() - .await; - - if let Some(file) = saved { - file.write(cursor.get_ref()).await.into_diagnostic()?; - } - - Ok(()) -} - -#[cfg(target_family = "wasm")] -pub use web::use_filer; - -#[cfg(target_family = "wasm")] -mod web { - use miette::Result; - - pub async fn use_filer(block: F) -> Result<()> - where - F: FnOnce(&mut dyn super::Filer) -> Result<()>, - { - super::use_zip_filer(block).await - } -} diff --git a/src/templates/engine/mod.rs b/src/templates/engine/mod.rs index b95beeb..982d775 100644 --- a/src/templates/engine/mod.rs +++ b/src/templates/engine/mod.rs @@ -2,8 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -pub mod filer; - use std::collections::{HashMap, HashSet}; pub enum TemplatePart { diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 17515f7..9358dad 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use bytes::Bytes; -use engine::filer::FilePermissions; +use crate::filer::FilePermissions; pub mod engine; pub mod fabric; @@ -130,7 +130,7 @@ macro_rules! file_data_raw { Ok(crate::templates::FileData { path, content: crate::templates::FileContent::$file_content_type($const_name.into()), - permissions: crate::templates::engine::filer::FilePermissions::$permissions, + permissions: crate::filer::FilePermissions::$permissions, }) } @@ -143,7 +143,7 @@ macro_rules! file_data_raw { let url = format!("templates/{}/{}", $dir.replace("-", "_"), $file_name); let bytes = crate::templates::$download_function(client, &url).await?; let content = crate::templates::FileContent::$file_content_type(bytes); - let permissions = crate::templates::engine::filer::FilePermissions::$permissions; + let permissions = crate::filer::FilePermissions::$permissions; Ok(crate::templates::FileData { path, content, permissions }) } }; diff --git a/src/web.rs b/src/web.rs index b863684..daa1157 100644 --- a/src/web.rs +++ b/src/web.rs @@ -7,6 +7,8 @@ use miette::{miette, Result}; use strum::IntoEnumIterator; use wasm_bindgen::prelude::*; +use crate::filer; + pub trait ResultExt { fn to_miette(self) -> Result; } @@ -80,7 +82,7 @@ pub async fn generate(state: JsValue) { async fn generate_inner(state: JsValue) -> Result<(), JsValue> { let app: crate::app::GeneratorApp = serde_wasm_bindgen::from_value(state)?; - crate::app::generator::generate(&app) + crate::app::generator::generate(&app, &filer::ZipFilerProvider(filer::web::ZipSaveDialog)) .await .map_err(|err| JsValue::from(format!("{}", err))) } diff --git a/version_resolver/Cargo.toml b/version_resolver/Cargo.toml index 2e301f5..7c856b6 100644 --- a/version_resolver/Cargo.toml +++ b/version_resolver/Cargo.toml @@ -13,7 +13,7 @@ xml_dom = { workspace = true } flexver-rs = "0.1.2" [target.'cfg(not(target_family = "wasm"))'.dependencies] -clap = { version = "4.4", features = ["derive"] } +clap = { workspace = true } miette = { workspace = true, features = ["fancy"] } tokio = { workspace = true } xml_dom = { workspace = true }