Add support for an is_master(file path) function

This commit is contained in:
Oliver Hamlet
2019-10-05 13:24:52 +01:00
parent 90cbeff71a
commit cb211d6ec3
4 changed files with 171 additions and 0 deletions
+1
View File
@@ -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"
+57
View File
@@ -99,6 +99,30 @@ fn evaluate_active_regex(state: &State, regex: &Regex) -> Result<bool, Error> {
Ok(state.active_plugins.iter().any(|p| regex.is_match(p)))
}
fn evaluate_is_master(state: &State, file_path: &Path) -> Result<bool, Error> {
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<bool, Error> {
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"));
+87
View File
@@ -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(".*"));
+26
View File
@@ -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();