From cb211d6ec39de3d6e3e920d5be6bd9f93dab42f5 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sat, 5 Oct 2019 11:37:36 +0100 Subject: [PATCH] Add support for an is_master(file path) function --- Cargo.toml | 1 + src/function/eval.rs | 57 ++++++++++++++++++++++++++++ src/function/mod.rs | 87 +++++++++++++++++++++++++++++++++++++++++++ src/function/parse.rs | 26 +++++++++++++ 4 files changed, 171 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 6b01f1c..b9794ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] crc = "1.0.0" +esplugin = "3.0.0" nom = "5.0.0" pelite = "0.7.0" regex = "1.0.5" diff --git a/src/function/eval.rs b/src/function/eval.rs index 12f0629..47c3b05 100644 --- a/src/function/eval.rs +++ b/src/function/eval.rs @@ -99,6 +99,30 @@ fn evaluate_active_regex(state: &State, regex: &Regex) -> Result { Ok(state.active_plugins.iter().any(|p| regex.is_match(p))) } +fn evaluate_is_master(state: &State, file_path: &Path) -> Result { + use crate::GameType; + use esplugin::GameId; + + let game_id = match state.game_type { + GameType::Morrowind => GameId::Morrowind, + GameType::Oblivion => GameId::Oblivion, + GameType::Skyrim => GameId::Skyrim, + GameType::SkyrimSE | GameType::SkyrimVR => GameId::SkyrimSE, + GameType::Fallout3 => GameId::Fallout3, + GameType::FalloutNV => GameId::FalloutNV, + GameType::Fallout4 | GameType::Fallout4VR => GameId::Fallout4, + }; + + let path = resolve_path(state, file_path); + + let mut plugin = esplugin::Plugin::new(game_id, &path); + + plugin + .parse_file(true) + .map(|_| plugin.is_master_file()) + .or(Ok(false)) +} + fn evaluate_many_active(state: &State, regex: &Regex) -> Result { let mut found_one = false; for active_plugin in &state.active_plugins { @@ -231,6 +255,7 @@ impl Function { Function::FileRegex(p, r) => evaluate_file_regex(state, p, r), Function::ActivePath(p) => evaluate_active_path(state, p), Function::ActiveRegex(r) => evaluate_active_regex(state, r), + Function::IsMaster(p) => evaluate_is_master(state, p), Function::Many(p, r) => evaluate_many(state, p, r), Function::ManyActive(r) => evaluate_many_active(state, r), Function::Checksum(path, crc) => evaluate_checksum(state, path, *crc), @@ -464,6 +489,38 @@ mod tests { assert!(!function.eval(&state).unwrap()); } + #[test] + fn function_is_master_eval_should_be_true_if_the_path_is_a_master_plugin() { + let function = Function::IsMaster(PathBuf::from("Blank.esm")); + let state = state("tests/testing-plugins/Oblivion/Data"); + + assert!(function.eval(&state).unwrap()); + } + + #[test] + fn function_is_master_eval_should_be_false_if_the_path_does_not_exist() { + let function = Function::IsMaster(PathBuf::from("missing.esp")); + let state = state("tests/testing-plugins/Oblivion/Data"); + + assert!(!function.eval(&state).unwrap()); + } + + #[test] + fn function_is_master_eval_should_be_false_if_the_path_is_not_a_plugin() { + let function = Function::IsMaster(PathBuf::from("Cargo.toml")); + let state = state("."); + + assert!(!function.eval(&state).unwrap()); + } + + #[test] + fn function_is_master_eval_should_be_false_if_the_path_is_a_non_master_plugin() { + let function = Function::IsMaster(PathBuf::from("Blank.esp")); + let state = state("tests/testing-plugins/Oblivion/Data"); + + assert!(!function.eval(&state).unwrap()); + } + #[test] fn function_many_eval_should_be_false_if_no_directory_entries_match() { let function = Function::Many(PathBuf::from("."), regex("missing")); diff --git a/src/function/mod.rs b/src/function/mod.rs index d3a08f6..9e1e90e 100644 --- a/src/function/mod.rs +++ b/src/function/mod.rs @@ -39,6 +39,7 @@ pub enum Function { FileRegex(PathBuf, Regex), ActivePath(PathBuf), ActiveRegex(Regex), + IsMaster(PathBuf), Many(PathBuf, Regex), ManyActive(Regex), Checksum(PathBuf, u32), @@ -54,6 +55,7 @@ impl fmt::Display for Function { FileRegex(p, r) => write!(f, "file(\"{}/{}\")", p.display(), r), ActivePath(p) => write!(f, "active(\"{}\")", p.display()), ActiveRegex(r) => write!(f, "active(\"{}\")", r), + IsMaster(p) => write!(f, "is_master(\"{}\")", p.display()), Many(p, r) => write!(f, "many(\"{}/{}\")", p.display(), r), ManyActive(r) => write!(f, "many_active(\"{}\")", r), Checksum(p, c) => write!(f, "checksum(\"{}\", {:02X?})", p.display(), c), @@ -75,6 +77,7 @@ impl PartialEq for Function { } (ActivePath(p1), ActivePath(p2)) => eq(&p1.to_string_lossy(), &p2.to_string_lossy()), (ActiveRegex(r1), ActiveRegex(r2)) => eq(r1.as_str(), r2.as_str()), + (IsMaster(p1), IsMaster(p2)) => eq(&p1.to_string_lossy(), &p2.to_string_lossy()), (Many(p1, r1), Many(p2, r2)) => { eq(r1.as_str(), r2.as_str()) && eq(&p1.to_string_lossy(), &p2.to_string_lossy()) } @@ -112,6 +115,9 @@ impl Hash for Function { ActiveRegex(r) => { r.as_str().to_lowercase().hash(state); } + IsMaster(p) => { + p.to_string_lossy().to_lowercase().hash(state); + } Many(p, r) => { p.to_string_lossy().to_lowercase().hash(state); r.as_str().to_lowercase().hash(state); @@ -178,6 +184,13 @@ mod tests { assert_eq!("active(\"Blank.*\")", &format!("{}", function)); } + #[test] + fn function_fmt_for_is_master_should_format_correctly() { + let function = Function::IsMaster("Blank.esm".into()); + + assert_eq!("is_master(\"Blank.esm\")", &format!("{}", function)); + } + #[test] fn function_fmt_for_many_should_format_correctly() { let function = Function::Many("subdir".into(), regex("Blank.*")); @@ -330,6 +343,43 @@ mod tests { ); } + #[test] + fn function_eq_for_is_master_should_check_pathbuf() { + assert_eq!( + Function::IsMaster("Blank.esm".into()), + Function::IsMaster("Blank.esm".into()) + ); + + assert_ne!( + Function::IsMaster("Blank.esp".into()), + Function::IsMaster("Blank.esm".into()) + ); + } + + #[test] + fn function_eq_for_is_master_should_be_case_insensitive_on_pathbuf() { + assert_eq!( + Function::IsMaster("Blank.esm".into()), + Function::IsMaster("blank.esm".into()) + ); + } + + #[test] + fn function_eq_for_is_master_should_not_be_equal_to_file_path_with_same_pathbuf() { + assert_ne!( + Function::IsMaster("Blank.esm".into()), + Function::FilePath("Blank.esm".into()) + ); + } + + #[test] + fn function_eq_for_active_path_should_not_be_equal_to_active_path_with_same_pathbuf() { + assert_ne!( + Function::IsMaster("Blank.esm".into()), + Function::ActivePath("Blank.esm".into()) + ); + } + #[test] fn function_eq_for_many_should_check_pathbuf_and_regex() { assert_eq!( @@ -588,6 +638,43 @@ mod tests { assert_eq!(hash(function1), hash(function2)); } + #[test] + fn function_hash_is_master_should_hash_pathbuf() { + let function1 = Function::IsMaster("Blank.esm".into()); + let function2 = Function::IsMaster("Blank.esm".into()); + + assert_eq!(hash(function1), hash(function2)); + + let function1 = Function::IsMaster("Blank.esm".into()); + let function2 = Function::IsMaster("Blank.esp".into()); + + assert_ne!(hash(function1), hash(function2)); + } + + #[test] + fn function_hash_is_master_should_be_case_insensitive() { + let function1 = Function::IsMaster("Blank.esm".into()); + let function2 = Function::IsMaster("blank.esm".into()); + + assert_eq!(hash(function1), hash(function2)); + } + + #[test] + fn function_hash_file_path_and_is_master_should_not_have_equal_hashes() { + let function1 = Function::FilePath("Blank.esm".into()); + let function2 = Function::IsMaster("Blank.esm".into()); + + assert_ne!(hash(function1), hash(function2)); + } + + #[test] + fn function_hash_active_path_and_is_master_should_not_have_equal_hashes() { + let function1 = Function::FilePath("Blank.esm".into()); + let function2 = Function::IsMaster("Blank.esm".into()); + + assert_ne!(hash(function1), hash(function2)); + } + #[test] fn function_hash_many_should_hash_pathbuf_and_regex() { let function1 = Function::Many("subdir".into(), regex(".*")); diff --git a/src/function/parse.rs b/src/function/parse.rs index fe28a4a..4ba254d 100644 --- a/src/function/parse.rs +++ b/src/function/parse.rs @@ -186,6 +186,14 @@ impl Function { ), Function::ActiveRegex, ), + map( + delimited( + map_err(tag("is_master(\"")), + parse_non_regex_path, + map_err(tag("\")")), + ), + Function::IsMaster, + ), map( delimited( map_err(tag("many(\"")), @@ -335,6 +343,24 @@ mod tests { } } + #[test] + fn function_parse_should_parse_an_is_master_function() { + let output = Function::parse("is_master(\"Blank.esm\")".into()).unwrap(); + + assert!(output.0.is_empty()); + match output.1 { + Function::IsMaster(f) => assert_eq!(Path::new("Blank.esm"), f), + _ => panic!("Expected an is master function"), + } + } + + #[test] + fn function_parse_should_error_if_the_is_master_path_is_outside_the_game_directory() { + // Trying to check if a path that isn't a plugin in the data folder is + // active is pointless, but it's not worth having a more specific check. + assert!(Function::parse("is_master(\"../../Blank.esm\")".into()).is_err()); + } + #[test] fn function_parse_should_parse_a_many_function_with_no_parent_path() { let output = Function::parse("many(\"Cargo.*\")".into()).unwrap();