diff --git a/src/main.rs b/src/main.rs index b5848be..b95cbd6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ struct Cli { #[derive(Subcommand)] enum Commands { - /// Initialises a new mcmpmgr project in the specified directory (or current dir if not specified) + /// Initialise a new mcmpmgr project in the specified directory (or current dir if not specified) Init { /// The root modpack project directory directory: Option, @@ -28,7 +28,7 @@ enum Commands { #[arg(long, default_value_t = modpack::ModLoader::Fabric)] modloader: modpack::ModLoader, }, - /// Creates and initialises a new mcmpmgr project in the current directory + /// Create and initialise a new mcmpmgr project in the current directory New { /// Name of the new modpack project name: String, @@ -39,6 +39,17 @@ enum Commands { #[arg(long, default_value_t = modpack::ModLoader::Fabric)] modloader: modpack::ModLoader, }, + /// Add a new mod to the modpack + Add { + /// Name of the mod to add to the project, optionally including a version + name: String, + /// Providers to download the mods from + #[arg(long)] + providers: Vec, + /// URL to download the mod from + #[arg(long)] + url: Option + } } fn main() -> Result<(), Box> { @@ -88,6 +99,19 @@ fn main() -> Result<(), Box> { let mc_modpack_meta: ModpackMeta = ModpackMeta::new(&name, &mc_version, modloader); mc_modpack_meta.init_project(&dir)?; } + Commands::Add { name, providers, url } => { + let mut modpack_meta = ModpackMeta::load_from_current_directory()?; + + let mut mod_meta = ModMeta::new(&name)?; + if let Some(url) = url { + mod_meta = mod_meta.url(&url); + } + for provider in providers.into_iter() { + mod_meta = mod_meta.provider(provider); + } + modpack_meta = modpack_meta.add_mod(mod_meta); + modpack_meta.save_current_dir_project()?; + }, } }; diff --git a/src/modpack.rs b/src/modpack.rs index 9ea433c..852a57a 100644 --- a/src/modpack.rs +++ b/src/modpack.rs @@ -2,7 +2,9 @@ use std::{borrow::BorrowMut, error::Error, path::PathBuf}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize, PartialEq)] +const MODPACK_FILENAME: &str = "modpack.toml"; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub enum ModProvider { /// Get mods from CurseForge CurseForge, @@ -12,6 +14,19 @@ pub enum ModProvider { Raw, } +impl std::str::FromStr for ModProvider { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "curseforge" => Ok(ModProvider::CurseForge), + "modrinth" => Ok(ModProvider::Modrinth), + "raw" => Ok(ModProvider::Raw), + _ => Err(format!("Invalid mod launcher: {}", s)), + } + } +} + #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct ModMeta { mod_name: String, @@ -29,11 +44,22 @@ struct ModMetaBuilder { } impl ModMeta { - pub fn new(mod_name: &str) -> Self { - Self { + pub fn new(mod_name: &str) -> Result> { + if mod_name.contains("@") { + let mod_name_and_version: Vec<&str> = mod_name.split("@").collect(); + if mod_name_and_version.len() != 2 { + return Err(format!("Invalid mod with version constraint: '{}'", &mod_name).into()); + } + return Ok(Self { + mod_name: mod_name_and_version[0].into(), + version: mod_name_and_version[1].into(), + ..Default::default() + }); + } + Ok(Self { mod_name: mod_name.into(), ..Default::default() - } + }) } pub fn provider(mut self, provider: ModProvider) -> Self { @@ -116,6 +142,23 @@ impl ModpackMeta { } } + pub fn load_from_directory(directory: &PathBuf) -> Result> { + let modpack_meta_file_path = directory.clone().join(PathBuf::from(MODPACK_FILENAME)); + if !modpack_meta_file_path.exists() { + return Err(format!( + "Directory '{}' does not seem to be a valid modpack project directory.", + directory.display() + ) + .into()); + }; + let modpack_contents = std::fs::read_to_string(modpack_meta_file_path)?; + Ok(toml::from_str(&modpack_contents)?) + } + + pub fn load_from_current_directory() -> Result> { + Self::load_from_directory(&std::env::current_dir()?) + } + pub fn provider(mut self, provider: ModProvider) -> Self { if !self.default_providers.contains(&provider) { self.default_providers.push(provider); @@ -125,13 +168,22 @@ impl ModpackMeta { pub fn add_mod(mut self, mod_meta: ModMeta) -> Self { if !self.mods.contains(&mod_meta) { + self.mods = self + .mods + .into_iter() + .filter(|m| m.mod_name != mod_meta.mod_name) + .collect(); + println!( + "Adding {}@{} to modpack '{}'...", + mod_meta.mod_name, mod_meta.version, self.pack_name + ); self.mods.push(mod_meta); } self } pub fn init_project(&self, directory: &PathBuf) -> Result<(), Box> { - let modpack_meta_file_path = directory.clone().join(PathBuf::from("mcmodpack.toml")); + let modpack_meta_file_path = directory.clone().join(PathBuf::from(MODPACK_FILENAME)); if modpack_meta_file_path.exists() { return Err(format!( "mcmodpack.toml already exists at {}", @@ -140,15 +192,25 @@ impl ModpackMeta { .into()); } - std::fs::write( - modpack_meta_file_path, - toml::to_string(self) - .expect("MC Modpack Meta should be serializable"), - )?; - + self.save_to_file(&modpack_meta_file_path)?; println!("MC modpack project initialized at {}", directory.display()); Ok(()) } + + pub fn save_to_file(&self, path: &PathBuf) -> Result<(), Box> { + std::fs::write( + path, + toml::to_string(self).expect("MC Modpack Meta should be serializable"), + )?; + println!("Saved modpack metadata to {}", path.display()); + Ok(()) + } + + pub fn save_current_dir_project(&self) -> Result<(), Box> { + let modpack_meta_file_path = std::env::current_dir()?.join(PathBuf::from(MODPACK_FILENAME)); + self.save_to_file(&modpack_meta_file_path)?; + Ok(()) + } } impl std::default::Default for ModpackMeta {