From 9ff80787ca36935797f30745f279e9996b3e0673 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 03:11:13 +0300 Subject: [PATCH 01/13] Start work on proper CLI --- Cargo.lock | 240 +++++++++++++--------------------- Cargo.toml | 10 +- src/app/generator.rs | 4 +- src/app/mod.rs | 42 +++--- src/app2/app.rs | 57 -------- src/app2/focus.rs | 34 ----- src/app2/mod.rs | 193 --------------------------- src/app2/screen.rs | 21 --- src/app2/widget.rs | 114 ---------------- src/async_support.rs | 13 -- src/cli/mod.rs | 163 +++++++++++++++++++++++ src/lib.rs | 4 +- src/main.rs | 29 +--- src/templates/engine/filer.rs | 61 ++++----- src/web.rs | 2 +- 15 files changed, 320 insertions(+), 667 deletions(-) delete mode 100644 src/app2/app.rs delete mode 100644 src/app2/focus.rs delete mode 100644 src/app2/mod.rs delete mode 100644 src/app2/screen.rs delete mode 100644 src/app2/widget.rs delete mode 100644 src/async_support.rs create mode 100644 src/cli/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 585ec8f..cb77369 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,12 +351,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "cc" version = "1.0.102" @@ -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" @@ -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 = "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 = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +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" @@ -1328,12 +1336,6 @@ dependencies = [ "windows-targets 0.52.5", ] -[[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" @@ -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", @@ -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", @@ -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", @@ -2728,6 +2658,20 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index d61d11d..bc4872e 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 = "4.5.9" [workspace.dependencies.web-sys] version = "0.3.64" @@ -42,7 +45,7 @@ crate-type = ["cdylib", "rlib"] miette = { workspace = true } reqwest = { workspace = true } futures = "0.3" -rfd = "0.14" +rfd = { workspace = true } zip = { version = "2.1.3", default-features = false, features = ["deflate"] } serde = { workspace = true } serde_json = { workspace = true } @@ -53,9 +56,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 } diff --git a/src/app/generator.rs b/src/app/generator.rs index 93de0f2..62c1571 100644 --- a/src/app/generator.rs +++ b/src/app/generator.rs @@ -14,7 +14,7 @@ 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 engine::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 @@ -228,7 +228,7 @@ pub async fn generate(app: &super::GeneratorApp) -> Result<()> { context.put(key, value); } - engine::filer::use_filer(|filer| { + filer_provider.use_filer(|filer| { let file_name = file_name_parts.join("-"); filer.set_file_name(file_name); diff --git a/src/app/mod.rs b/src/app/mod.rs index d6288d6..a91e086 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 { 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/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..a9980e2 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,163 @@ +// 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, select}; +use miette::{Context, IntoDiagnostic, Result}; +use strum::IntoEnumIterator; +use version_resolver::minecraft::MinecraftVersion; +use std::path::{Path, PathBuf}; + +use crate::{Dependencies, GeneratorApp, MappingSet, ProjectType, Subprojects}; +use crate::templates::engine::filer; + +#[derive(Parser)] +#[command(version)] +struct Args { + /// The project path (default: the current directory) + output: Option, +} + +pub async fn main() -> Result<()> { + let args = Args::parse(); + // TODO: Support zips + let dir = if let Some(directory) = args.output { + directory + } else { + std::env::current_dir() + .into_diagnostic() + .wrap_err("Couldn't get current directory")? + }; + let app = prompt(&dir)?; + let filer_provider = filer::DirectoryFilerProvider(&dir); + crate::generator::generate(&app, &filer_provider).await +} + +fn prompt(dir: &Path) -> Result { + intro("Architectury Template Generator").into_diagnostic()?; + + let mod_name: String = input("Mod name") + .default_input(dir.file_name().and_then(|s| s.to_str()).unwrap_or("")) + .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) +} + +// 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/lib.rs b/src/lib.rs index 1eb2ed6..df10573 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,7 @@ 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 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 index 2cf1336..ced496a 100644 --- a/src/templates/engine/filer.rs +++ b/src/templates/engine/filer.rs @@ -13,6 +13,13 @@ pub trait Filer { 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, @@ -82,31 +89,21 @@ where } #[cfg(not(target_family = "wasm"))] -pub use native::use_filer; +pub use native::DirectoryFilerProvider; #[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)?; - } + pub struct DirectoryFilerProvider<'a>(pub &'a path::Path); - // TODO: Return error when cancelled - Ok(()) + 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> { @@ -123,7 +120,7 @@ mod native { } #[cfg(not(target_family = "unix"))] - fn update_permissions(path: &path::Path, permissions: &super::FilePermissions) -> Result<()> { + fn update_permissions(_path: &path::Path, _permissions: &super::FilePermissions) -> Result<()> { // Not supported on Windows Ok(()) } @@ -153,6 +150,16 @@ mod native { } } +pub struct ZipFilerProvider; + +impl FilerProvider for ZipFilerProvider { + async fn use_filer(&self, block: F) -> Result<()> + where + F: FnOnce(&mut dyn Filer) -> Result<()> { + use_zip_filer(block).await + } +} + pub async fn use_zip_filer(block: F) -> Result<()> where F: FnOnce(&mut dyn Filer) -> Result<()>, @@ -175,6 +182,7 @@ where writer.finish().into_diagnostic()?; } + // TODO: Replace with platform-specific writer trait let saved = AsyncFileDialog::new() .set_title("Choose where to save the template") .add_filter("Zip file", &["zip"]) @@ -188,18 +196,3 @@ where 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/web.rs b/src/web.rs index b863684..15ac78f 100644 --- a/src/web.rs +++ b/src/web.rs @@ -80,7 +80,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, &crate::templates::engine::filer::ZipFilerProvider) .await .map_err(|err| JsValue::from(format!("{}", err))) } From c953cc81024dc919c685691762ff761f3de422a4 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:18:07 +0300 Subject: [PATCH 02/13] native: Remove dependency on RFD --- Cargo.toml | 2 +- src/templates/engine/filer.rs | 86 +++++++++++++++++++++-------------- src/web.rs | 4 +- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc4872e..9800cd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,6 @@ crate-type = ["cdylib", "rlib"] miette = { workspace = true } reqwest = { workspace = true } futures = "0.3" -rfd = { workspace = true } zip = { version = "2.1.3", default-features = false, features = ["deflate"] } serde = { workspace = true } serde_json = { workspace = true } @@ -65,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/src/templates/engine/filer.rs b/src/templates/engine/filer.rs index ced496a..17a026a 100644 --- a/src/templates/engine/filer.rs +++ b/src/templates/engine/filer.rs @@ -3,7 +3,6 @@ // 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; @@ -148,51 +147,70 @@ mod native { // Don't do anything as the directory filer prompts for the output name. } } + + impl super::ZipWriteTarget for T + where + T: AsRef, + { + async fn write(&self, _file_name: String, data: &[u8]) -> Result<()> { + tokio::fs::write(self, data).await.into_diagnostic() + } + } } -pub struct ZipFilerProvider; +pub struct ZipFilerProvider(pub T); -impl FilerProvider for ZipFilerProvider { +impl FilerProvider for ZipFilerProvider { async fn use_filer(&self, block: F) -> Result<()> where F: FnOnce(&mut dyn Filer) -> Result<()> { - use_zip_filer(block).await - } -} + let mut cursor = Cursor::new(Vec::new()); + let mut file_name: String = "template.zip".to_owned(); -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); + // Create and use the zip writer and filer. + // This is its own scope in order to drop the borrow to the cursor. { - let mut filer = ZipFiler::new(&mut writer); - block(&mut filer)?; - - if let Some(custom_name) = filer.file_name { - file_name = custom_name + ".zip"; + 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()?; } - 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<()>; +} - // TODO: Replace with platform-specific writer trait - 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; +#[cfg(target_family = "wasm")] +pub mod web { + use miette::{IntoDiagnostic, Result}; - if let Some(file) = saved { - file.write(cursor.get_ref()).await.into_diagnostic()?; - } + pub struct ZipSaveDialog; - Ok(()) + 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/web.rs b/src/web.rs index 15ac78f..94a4e44 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::templates::engine::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::templates::engine::filer::ZipFilerProvider) + crate::app::generator::generate(&app, &filer::ZipFilerProvider(filer::web::ZipSaveDialog)) .await .map_err(|err| JsValue::from(format!("{}", err))) } From 382c1263bbb30e9ddd4f367a3fba79488356e727 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:30:39 +0300 Subject: [PATCH 03/13] Move filer module to top level --- src/app/generator.rs | 2 +- src/cli/mod.rs | 4 +- .../engine/filer.rs => filer/mod.rs} | 101 ++---------------- src/filer/native.rs | 69 ++++++++++++ src/filer/web.rs | 24 +++++ src/lib.rs | 1 + src/templates/engine/mod.rs | 2 - src/templates/mod.rs | 6 +- src/web.rs | 2 +- 9 files changed, 107 insertions(+), 104 deletions(-) rename src/{templates/engine/filer.rs => filer/mod.rs} (54%) create mode 100644 src/filer/native.rs create mode 100644 src/filer/web.rs diff --git a/src/app/generator.rs b/src/app/generator.rs index 62c1571..bfa8841 100644 --- a/src/app/generator.rs +++ b/src/app/generator.rs @@ -14,7 +14,7 @@ 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, filer_provider: &impl engine::filer::FilerProvider) -> 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 diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a9980e2..6b291c2 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -10,7 +10,7 @@ use version_resolver::minecraft::MinecraftVersion; use std::path::{Path, PathBuf}; use crate::{Dependencies, GeneratorApp, MappingSet, ProjectType, Subprojects}; -use crate::templates::engine::filer; +use crate::filer::native::DirectoryFilerProvider; #[derive(Parser)] #[command(version)] @@ -30,7 +30,7 @@ pub async fn main() -> Result<()> { .wrap_err("Couldn't get current directory")? }; let app = prompt(&dir)?; - let filer_provider = filer::DirectoryFilerProvider(&dir); + let filer_provider = DirectoryFilerProvider(&dir); crate::generator::generate(&app, &filer_provider).await } diff --git a/src/templates/engine/filer.rs b/src/filer/mod.rs similarity index 54% rename from src/templates/engine/filer.rs rename to src/filer/mod.rs index 17a026a..3cfd3b7 100644 --- a/src/templates/engine/filer.rs +++ b/src/filer/mod.rs @@ -7,6 +7,12 @@ 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<()>; @@ -87,77 +93,6 @@ where } } -#[cfg(not(target_family = "wasm"))] -pub use native::DirectoryFilerProvider; - -#[cfg(not(target_family = "wasm"))] -mod native { - use miette::{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 = 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. - } - } - - impl super::ZipWriteTarget for T - where - T: AsRef, - { - async fn write(&self, _file_name: String, data: &[u8]) -> Result<()> { - tokio::fs::write(self, data).await.into_diagnostic() - } - } -} - pub struct ZipFilerProvider(pub T); impl FilerProvider for ZipFilerProvider { @@ -190,27 +125,3 @@ impl FilerProvider for ZipFilerProvider { pub trait ZipWriteTarget { async fn write(&self, file_name: String, data: &[u8]) -> Result<()>; } - -#[cfg(target_family = "wasm")] -pub mod web { - 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/filer/native.rs b/src/filer/native.rs new file mode 100644 index 0000000..05ec49e --- /dev/null +++ b/src/filer/native.rs @@ -0,0 +1,69 @@ +// 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::{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 = 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. + } +} + +impl super::ZipWriteTarget for T +where + T: AsRef, +{ + async fn write(&self, _file_name: String, data: &[u8]) -> Result<()> { + tokio::fs::write(self, 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 df10573..f17b466 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod app; #[cfg(not(target_family = "wasm"))] pub mod cli; +pub mod filer; pub mod mod_ids; pub mod tap; pub mod templates; 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 94a4e44..daa1157 100644 --- a/src/web.rs +++ b/src/web.rs @@ -7,7 +7,7 @@ use miette::{miette, Result}; use strum::IntoEnumIterator; use wasm_bindgen::prelude::*; -use crate::templates::engine::filer; +use crate::filer; pub trait ResultExt { fn to_miette(self) -> Result; From 316843720f114b824c5d74c5462ab09ae1fb8b89 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:34:18 +0300 Subject: [PATCH 04/13] Centralise clap dependency --- Cargo.lock | 142 ++++++++++++++++++------------------ Cargo.toml | 2 +- version_resolver/Cargo.toml | 2 +- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb77369..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,15 +347,15 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +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" @@ -861,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", @@ -890,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", @@ -943,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", @@ -1333,7 +1333,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1498,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", ] @@ -1636,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", @@ -1665,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", @@ -1703,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", @@ -1716,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", @@ -1726,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", ] @@ -1746,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", @@ -1757,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", @@ -1915,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", @@ -2010,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", @@ -2030,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", ] @@ -2045,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", @@ -2427,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]] @@ -2447,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]] @@ -2469,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" @@ -2481,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" @@ -2493,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" @@ -2511,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" @@ -2523,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" @@ -2535,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" @@ -2547,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" @@ -2675,9 +2675,9 @@ dependencies = [ [[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 9800cd4..004b8d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ strum = { version = "0.26", features = ["derive"] } tokio = { version = "1", features = ["full"] } rfd = "0.14" cliclack = "0.3.2" -clap = "4.5.9" +clap = { version = "4.5", features = ["derive"] } [workspace.dependencies.web-sys] version = "0.3.64" 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 } From 98485bfba103eb02fd0130e78bd5573089dd3062 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:11:10 +0300 Subject: [PATCH 05/13] native: Support zips and validate directories --- src/cli/mod.rs | 76 ++++++++++++++++++++++++++++++++++----------- src/filer/native.rs | 29 ++++++++++++----- 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 6b291c2..c59c4dd 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -4,42 +4,82 @@ use clap::Parser; use cliclack::{confirm, input, intro, multiselect, select}; -use miette::{Context, IntoDiagnostic, Result}; +use miette::{miette, Context, IntoDiagnostic, Result}; use strum::IntoEnumIterator; use version_resolver::minecraft::MinecraftVersion; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use crate::{Dependencies, GeneratorApp, MappingSet, ProjectType, Subprojects}; -use crate::filer::native::DirectoryFilerProvider; +use crate::filer::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(); - // TODO: Support zips - let dir = if let Some(directory) = args.output { - directory + 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 = std::env::current_dir() + .into_diagnostic() + .wrap_err("Couldn't get current directory")?; + (FsZipWriteTarget::InDirectory(dir), None) + }; + let app = prompt(default_name)?; + let filer_provider = ZipFilerProvider(file); + crate::generator::generate(&app, &filer_provider).await } else { - std::env::current_dir() - .into_diagnostic() - .wrap_err("Couldn't get current directory")? - }; - let app = prompt(&dir)?; - let filer_provider = DirectoryFilerProvider(&dir); - crate::generator::generate(&app, &filer_provider).await + let dir = if let Some(directory) = args.output { + directory + } else { + std::env::current_dir() + .into_diagnostic() + .wrap_err("Couldn't get current directory")? + }; + + 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()); + let app = prompt(default_name)?; + crate::generator::generate(&app, &DirectoryFilerProvider(&dir)).await + } } -fn prompt(dir: &Path) -> Result { +fn prompt(default_name: Option<&str>) -> Result { intro("Architectury Template Generator").into_diagnostic()?; - - let mod_name: String = input("Mod name") - .default_input(dir.file_name().and_then(|s| s.to_str()).unwrap_or("")) - .interact().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)) diff --git a/src/filer/native.rs b/src/filer/native.rs index 05ec49e..e53262f 100644 --- a/src/filer/native.rs +++ b/src/filer/native.rs @@ -2,7 +2,7 @@ // 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 miette::{miette, Context, IntoDiagnostic, Result}; use std::{fs, path}; pub struct DirectoryFilerProvider<'a>(pub &'a path::Path); @@ -59,11 +59,26 @@ impl<'a> super::Filer for DirectoryFiler<'a> { } } -impl super::ZipWriteTarget for T -where - T: AsRef, -{ - async fn write(&self, _file_name: String, data: &[u8]) -> Result<()> { - tokio::fs::write(self, data).await.into_diagnostic() +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() } } From 7610107b781dd107f3cd75d3d11c9c7cdaa0cacb Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:39:40 +0300 Subject: [PATCH 06/13] native: Display message after generating --- src/app/generator.rs | 57 ++++++++++++++++++++++++++++---------------- src/app/mod.rs | 8 +++++++ src/cli/mod.rs | 19 +++++++++++---- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/app/generator.rs b/src/app/generator.rs index bfa8841..19da425 100644 --- a/src/app/generator.rs +++ b/src/app/generator.rs @@ -16,24 +16,16 @@ use version_resolver::maven::{resolve_latest_version, resolve_matching_version, 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, filer_provider: &impl crate::fi 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, filer_provider: &impl crate::fi 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, filer_provider: &impl crate::fi 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 @@ -229,7 +211,7 @@ pub async fn generate(app: &super::GeneratorApp, filer_provider: &impl crate::fi } filer_provider.use_filer(|filer| { - let file_name = file_name_parts.join("-"); + 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, filer_provider: &impl crate::fi .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 a91e086..7a21eea 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -88,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/cli/mod.rs b/src/cli/mod.rs index c59c4dd..2e0aa06 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use clap::Parser; -use cliclack::{confirm, input, intro, multiselect, select}; +use cliclack::{confirm, input, intro, multiselect, outro, select}; use miette::{miette, Context, IntoDiagnostic, Result}; use strum::IntoEnumIterator; use version_resolver::minecraft::MinecraftVersion; @@ -25,7 +25,7 @@ struct Args { pub async fn main() -> Result<()> { let args = Args::parse(); - if args.zip { + let output_name = 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() @@ -41,7 +41,13 @@ pub async fn main() -> Result<()> { }; let app = prompt(default_name)?; let filer_provider = ZipFilerProvider(file); - crate::generator::generate(&app, &filer_provider).await + crate::generator::generate(&app, &filer_provider).await?; + + if let Some(output) = args.output { + output.to_string_lossy().into_owned() + } else { + crate::generator::compose_file_name(&app) + ".zip" + } } else { let dir = if let Some(directory) = args.output { directory @@ -68,8 +74,11 @@ pub async fn main() -> Result<()> { let default_name = dir.file_name().and_then(|s| s.to_str()); let app = prompt(default_name)?; - crate::generator::generate(&app, &DirectoryFilerProvider(&dir)).await - } + crate::generator::generate(&app, &DirectoryFilerProvider(&dir)).await?; + dir.to_string_lossy().into_owned() + }; + outro(format!("Generated into {}!", output_name)).into_diagnostic()?; + Ok(()) } fn prompt(default_name: Option<&str>) -> Result { From 65f6469f94b606a0597efb30d28b9bb8dcea89cb Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:03:45 +0300 Subject: [PATCH 07/13] Move single-file cli module to top level --- src/{cli/mod.rs => cli.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{cli/mod.rs => cli.rs} (100%) diff --git a/src/cli/mod.rs b/src/cli.rs similarity index 100% rename from src/cli/mod.rs rename to src/cli.rs From 3b500bdcc4db31b9928c57794d405bdbab2752f2 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:05:24 +0300 Subject: [PATCH 08/13] Change TUI -> CLI in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 3efb3f779d9bf8225b1fabfbe1a99b773249dd2e Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:16:22 +0300 Subject: [PATCH 09/13] Update GH workflows to also build for native target --- .github/workflows/build_native.yml | 11 +++++++++++ .github/workflows/{build.yml => build_web.yml} | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build_native.yml rename .github/workflows/{build.yml => build_web.yml} (98%) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml new file mode 100644 index 0000000..d9ddee7 --- /dev/null +++ b/.github/workflows/build_native.yml @@ -0,0 +1,11 @@ +name: Build native +on: [push, pull_request] +jobs: + build: + runs-on: [ubuntu-latest, windows-latest] + 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 From 8f84cf5f818bc5688613f85df1cc739574f1a7f0 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:20:20 +0300 Subject: [PATCH 10/13] github: Fix OS matrix in native build workflow --- .github/workflows/build_native.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index d9ddee7..6ec4448 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -2,7 +2,10 @@ name: Build native on: [push, pull_request] jobs: build: - runs-on: [ubuntu-latest, windows-latest] + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Build From 46e1a76acf6f941266ff7789a5789eca450808a7 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:24:06 +0300 Subject: [PATCH 11/13] github: Don't fail fast if one OS fails --- .github/workflows/build_native.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index 6ec4448..0a4382e 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -3,6 +3,7 @@ on: [push, pull_request] jobs: build: strategy: + fail-fast: false matrix: os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} From c1aa3f3fc7a9d0944ca0912e255897285bd2a8f7 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:33:28 +0300 Subject: [PATCH 12/13] native: Fix compilation on unix --- src/filer/native.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filer/native.rs b/src/filer/native.rs index e53262f..dc72d6e 100644 --- a/src/filer/native.rs +++ b/src/filer/native.rs @@ -25,7 +25,7 @@ fn update_permissions(path: &path::Path, permissions: &super::FilePermissions) - 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); + let new_permissions = fs::Permissions::from_mode(new_mode); fs::set_permissions(path, new_permissions).into_diagnostic() } From 50f508b6cebd312e7f26c5e90e4facd67e044570 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:12:06 +0300 Subject: [PATCH 13/13] cli: Clean up code, add progress spinner --- src/cli.rs | 61 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 2e0aa06..25d3c32 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3,14 +3,14 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use clap::Parser; -use cliclack::{confirm, input, intro, multiselect, outro, select}; +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::ZipFilerProvider; +use crate::filer::{FilerProvider, ZipFilerProvider}; use crate::filer::native::{DirectoryFilerProvider, FsZipWriteTarget}; #[derive(Parser)] @@ -25,7 +25,7 @@ struct Args { pub async fn main() -> Result<()> { let args = Args::parse(); - let output_name = if args.zip { + 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() @@ -34,27 +34,23 @@ pub async fn main() -> Result<()> { (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 = std::env::current_dir() - .into_diagnostic() - .wrap_err("Couldn't get current directory")?; + let dir = get_current_dir()?; (FsZipWriteTarget::InDirectory(dir), None) }; - let app = prompt(default_name)?; - let filer_provider = ZipFilerProvider(file); - crate::generator::generate(&app, &filer_provider).await?; - if let Some(output) = args.output { - output.to_string_lossy().into_owned() - } else { - crate::generator::compose_file_name(&app) + ".zip" - } + 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 { - std::env::current_dir() - .into_diagnostic() - .wrap_err("Couldn't get current directory")? + get_current_dir()? }; if dir.exists() { @@ -73,11 +69,26 @@ pub async fn main() -> Result<()> { } let default_name = dir.file_name().and_then(|s| s.to_str()); - let app = prompt(default_name)?; - crate::generator::generate(&app, &DirectoryFilerProvider(&dir)).await?; - dir.to_string_lossy().into_owned() - }; - outro(format!("Generated into {}!", output_name)).into_diagnostic()?; + 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(()) } @@ -173,6 +184,12 @@ fn prompt(default_name: Option<&str>) -> Result { 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;