diff --git a/Cargo.lock b/Cargo.lock index ba59dca..317cccd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mod_installer" -version = "6.0.3" +version = "8.1.0" dependencies = [ "clap", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 5ae0d02..493e6e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mod_installer" -version = "6.0.3" +version = "8.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index 36dc836..e036d3a 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ Options: Timeout time per mod in seconds, default is 1 hour [env: TIMEOUT=] [default: 3600] -u, --weidu-log-mode Weidu log setting "--autolog" is default [env: WEIDU_LOG_MODE=] [default: --autolog] + -x, --strict-matching + Strict Version and Component/SubComponent matching, default is false [env: STRICT_MATCHING=] -h, --help Print help -V, --version diff --git a/src/args.rs b/src/args.rs index a47d271..d438184 100644 --- a/src/args.rs +++ b/src/args.rs @@ -60,6 +60,10 @@ pub struct Args { /// Weidu log setting "--autolog" is default #[clap(env, long, short='u', default_value = "autolog", value_parser = parse_weidu_log_mode, required = false)] pub weidu_log_mode: String, + + /// Strict Version and Component/SubComponent matching, default is false + #[clap(env, long, short = 'x', action=ArgAction::SetFalse, default_value = "false")] + pub strict_matching: bool, } fn parse_weidu_log_mode(arg: &str) -> Result { diff --git a/src/component.rs b/src/component.rs index 85fdc79..76dc67a 100644 --- a/src/component.rs +++ b/src/component.rs @@ -2,7 +2,7 @@ use std::error::Error; // This should mirror the weidu component // https://github.com/WeiDUorg/weidu/blob/devel/src/tp.ml#L98 -#[derive(Debug, PartialEq, PartialOrd, Clone)] +#[derive(Debug, PartialOrd, Clone)] pub(crate) struct Component { pub(crate) tp_file: String, pub(crate) name: String, @@ -13,6 +13,24 @@ pub(crate) struct Component { pub(crate) version: String, } +impl PartialEq for Component { + fn eq(&self, other: &Self) -> bool { + self.tp_file == other.tp_file + && self.name == other.name + && self.lang == other.lang + && self.component == other.component + } +} + +impl Component { + pub(crate) fn strict_matching(&self, other: &Self) -> bool { + self.eq(other) + && self.component_name == other.component_name + && self.sub_component == other.sub_component + && self.version == other.version + } +} + impl TryFrom for Component { type Error = Box; @@ -139,4 +157,32 @@ mod tests { }; assert_eq!(mod_component, expected) } + + #[test] + fn test_strict_match() { + let non_strict_match_1 = Component { + tp_file: "TOBEX.TP2".to_string(), + name: "tobex".to_string(), + lang: "0".to_string(), + component: "100".to_string(), + component_name: "TobEx - Core".to_string(), + sub_component: "".to_string(), + version: "v28".to_string(), + }; + + let non_strict_match_2 = Component { + tp_file: "TOBEX.TP2".to_string(), + name: "tobex".to_string(), + lang: "0".to_string(), + component: "100".to_string(), + component_name: "TobEx - Core Chicken".to_string(), + sub_component: "".to_string(), + version: "v28".to_string(), + }; + assert_eq!(non_strict_match_1, non_strict_match_2); + assert_eq!( + non_strict_match_1.strict_matching(&non_strict_match_2), + false + ) + } } diff --git a/src/main.rs b/src/main.rs index 25d4986..86ac299 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ -use std::{collections::HashMap, error::Error, path::PathBuf, process::ExitCode}; +use std::{collections::HashMap, process::ExitCode}; use args::Args; use clap::Parser; use env_logger::Env; -use log_file::LogFile; +use utils::find_mods; use crate::{ utils::{copy_mod_folder, mod_folder_present_in_game_directory, search_mod_folders}, @@ -18,33 +18,6 @@ mod utils; mod weidu; mod weidu_parser; -fn find_mods( - log_file: PathBuf, - skip_installed: bool, - game_directory: PathBuf, -) -> Result> { - let mut mods = LogFile::try_from(log_file)?; - let number_of_mods_found = mods.len(); - let mods_to_be_installed = if skip_installed { - let existing_weidu_log_file_path = game_directory.join("weidu").with_extension("log"); - if let Ok(installed_mods) = LogFile::try_from(existing_weidu_log_file_path) { - for installed_mod in &installed_mods { - mods.retain(|mod_to_install| installed_mod != mod_to_install); - } - } - mods - } else { - mods - }; - - log::info!( - "Number of mods found: {}, Number of mods to be installed: {}", - number_of_mods_found, - mods_to_be_installed.len() - ); - Ok(mods_to_be_installed) -} - fn main() -> ExitCode { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); log::info!( @@ -61,6 +34,7 @@ fn main() -> ExitCode { args.log_file, args.skip_installed, args.game_directory.clone(), + args.strict_matching, ) { Ok(mods) => mods, Err(err) => { @@ -117,50 +91,3 @@ fn main() -> ExitCode { } ExitCode::SUCCESS } - -#[cfg(test)] -mod tests { - use super::*; - use component::Component; - use pretty_assertions::assert_eq; - use std::path::PathBuf; - - #[test] - fn test_find_mods() { - let log_file = PathBuf::from("./fixtures/test.log"); - let skip_installed = false; - let game_directory = PathBuf::from("./fixtures"); - let result = find_mods(log_file.clone(), skip_installed, game_directory); - let expected = LogFile::try_from(log_file); - assert_eq!(expected.ok(), result.ok()) - } - - #[test] - fn test_find_mods_skip_installed() { - let log_file = PathBuf::from("./fixtures/test.log"); - let skip_installed = true; - let game_directory = PathBuf::from("./fixtures"); - let result = find_mods(log_file, skip_installed, game_directory).unwrap(); - let expected = LogFile(vec![ - Component { - tp_file: "TEST.TP2".to_string(), - name: "test_mod_name_1".to_string(), - lang: "0".to_string(), - component: "1".to_string(), - component_name: "test mod two".to_string(), - sub_component: "".to_string(), - version: "".to_string(), - }, - Component { - tp_file: "END.TP2".to_string(), - name: "test_mod_name_3".to_string(), - lang: "0".to_string(), - component: "0".to_string(), - component_name: "test mod with version".to_string(), - sub_component: "".to_string(), - version: "1.02".to_string(), - }, - ]); - assert_eq!(expected, result) - } -} diff --git a/src/utils.rs b/src/utils.rs index 320c7c5..fcd0cc2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,12 +1,13 @@ use core::time; use fs_extra::dir::{copy, CopyOptions}; use std::{ + error::Error, path::{Path, PathBuf}, thread, }; use walkdir::WalkDir; -use crate::component::Component; +use crate::{component::Component, log_file::LogFile}; pub fn mod_folder_present_in_game_directory(game_directory: &Path, mod_name: &str) -> bool { game_directory.join(mod_name).is_dir() @@ -63,6 +64,38 @@ fn find_mod_folder(mod_component: &Component, mod_dir: &Path, depth: usize) -> O }) } +pub(crate) fn find_mods( + log_file: PathBuf, + skip_installed: bool, + game_directory: PathBuf, + strict_matching: bool, +) -> Result> { + let mut mods = LogFile::try_from(log_file)?; + let number_of_mods_found = mods.len(); + let mods_to_be_installed = if skip_installed { + let existing_weidu_log_file_path = game_directory.join("weidu").with_extension("log"); + if let Ok(installed_mods) = LogFile::try_from(existing_weidu_log_file_path) { + for installed_mod in &installed_mods { + if strict_matching { + mods.retain(|mod_to_install| installed_mod.strict_matching(mod_to_install)); + } else { + mods.retain(|mod_to_install| installed_mod != mod_to_install); + } + } + } + mods + } else { + mods + }; + + log::info!( + "Number of mods found: {}, Number of mods to be installed: {}", + number_of_mods_found, + mods_to_be_installed.len() + ); + Ok(mods_to_be_installed) +} + pub fn sleep(millis: u64) { let duration = time::Duration::from_millis(millis); thread::sleep(duration); @@ -90,4 +123,43 @@ mod tests { Path::new(&format!("fixtures/mods/mod_a/{}", mod_component.name)).to_path_buf(); assert_eq!(mod_folder, Some(expected)) } + + #[test] + fn test_find_mods() { + let log_file = PathBuf::from("./fixtures/test.log"); + let skip_installed = false; + let game_directory = PathBuf::from("./fixtures"); + let result = find_mods(log_file.clone(), skip_installed, game_directory, false); + let expected = LogFile::try_from(log_file); + assert_eq!(expected.ok(), result.ok()) + } + + #[test] + fn test_find_mods_skip_installed() { + let log_file = PathBuf::from("./fixtures/test.log"); + let skip_installed = true; + let game_directory = PathBuf::from("./fixtures"); + let result = find_mods(log_file, skip_installed, game_directory, false).unwrap(); + let expected = LogFile(vec![ + Component { + tp_file: "TEST.TP2".to_string(), + name: "test_mod_name_1".to_string(), + lang: "0".to_string(), + component: "1".to_string(), + component_name: "test mod two".to_string(), + sub_component: "".to_string(), + version: "".to_string(), + }, + Component { + tp_file: "END.TP2".to_string(), + name: "test_mod_name_3".to_string(), + lang: "0".to_string(), + component: "0".to_string(), + component_name: "test mod with version".to_string(), + sub_component: "".to_string(), + version: "1.02".to_string(), + }, + ]); + assert_eq!(expected, result) + } }