mirror of
https://github.com/loot/loot-condition-interpreter.git
synced 2026-07-27 14:16:09 -07:00
Add description_contains() condition function
It evaluates to true if the plugin description contains text that matches the given regex.
This commit is contained in:
+72
-6
@@ -106,11 +106,11 @@ 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> {
|
||||
fn parse_plugin(state: &State, file_path: &Path) -> Option<esplugin::Plugin> {
|
||||
use esplugin::GameId;
|
||||
|
||||
let game_id = match state.game_type {
|
||||
GameType::OpenMW => return Ok(false),
|
||||
GameType::OpenMW => return None,
|
||||
GameType::Morrowind => GameId::Morrowind,
|
||||
GameType::Oblivion => GameId::Oblivion,
|
||||
GameType::Skyrim => GameId::Skyrim,
|
||||
@@ -125,10 +125,17 @@ fn evaluate_is_master(state: &State, file_path: &Path) -> Result<bool, Error> {
|
||||
|
||||
let mut plugin = esplugin::Plugin::new(game_id, &path);
|
||||
|
||||
plugin
|
||||
.parse_file(ParseOptions::header_only())
|
||||
.map(|_| plugin.is_master_file())
|
||||
.or(Ok(false))
|
||||
if plugin.parse_file(ParseOptions::header_only()).is_ok() {
|
||||
Some(plugin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_is_master(state: &State, file_path: &Path) -> Result<bool, Error> {
|
||||
Ok(parse_plugin(state, file_path)
|
||||
.map(|plugin| plugin.is_master_file())
|
||||
.unwrap_or(false))
|
||||
}
|
||||
|
||||
fn evaluate_many_active(state: &State, regex: &Regex) -> Result<bool, Error> {
|
||||
@@ -281,6 +288,17 @@ fn evaluate_filename_version(
|
||||
evaluate_dir_entries(state, parent_path, evaluator)
|
||||
}
|
||||
|
||||
fn evaluate_description_contains(
|
||||
state: &State,
|
||||
file_path: &Path,
|
||||
regex: &Regex,
|
||||
) -> Result<bool, Error> {
|
||||
Ok(parse_plugin(state, file_path)
|
||||
.and_then(|plugin| plugin.description().unwrap_or(None))
|
||||
.map(|description| regex.is_match(&description))
|
||||
.unwrap_or(false))
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn eval(&self, state: &State) -> Result<bool, Error> {
|
||||
if self.is_slow() {
|
||||
@@ -308,6 +326,7 @@ impl Function {
|
||||
evaluate_version(state, p, v, *c, |_, p| get_product_version(p))
|
||||
}
|
||||
Function::FilenameVersion(p, r, v, c) => evaluate_filename_version(state, p, r, v, *c),
|
||||
Function::DescriptionContains(p, r) => evaluate_description_contains(state, p, r),
|
||||
};
|
||||
|
||||
if self.is_slow() {
|
||||
@@ -1580,4 +1599,51 @@ mod tests {
|
||||
|
||||
assert!(function.eval(&state).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_description_contains_eval_should_return_false_if_the_file_does_not_exist() {
|
||||
let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
|
||||
|
||||
let function = Function::DescriptionContains("missing.esp".into(), regex("€ƒ."));
|
||||
|
||||
assert!(!function.eval(&state).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_description_contains_eval_should_return_false_if_the_file_is_not_a_plugin() {
|
||||
let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
|
||||
|
||||
let function = Function::DescriptionContains("Blank.bsa".into(), regex("€ƒ."));
|
||||
|
||||
assert!(!function.eval(&state).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_description_contains_eval_should_return_false_if_the_plugin_has_no_description() {
|
||||
let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
|
||||
|
||||
let function = Function::DescriptionContains("Blank - Different.esm".into(), regex("€ƒ."));
|
||||
|
||||
assert!(!function.eval(&state).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_description_contains_eval_should_return_false_if_the_plugin_description_does_not_match(
|
||||
) {
|
||||
let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
|
||||
|
||||
let function = Function::DescriptionContains("Blank.esm".into(), regex("€ƒ."));
|
||||
|
||||
assert!(!function.eval(&state).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_description_contains_eval_should_return_true_if_the_plugin_description_contains_a_match(
|
||||
) {
|
||||
let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
|
||||
|
||||
let function = Function::DescriptionContains("Blank.esp".into(), regex("ƒ"));
|
||||
|
||||
assert!(function.eval(&state).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ pub enum Function {
|
||||
Version(PathBuf, String, ComparisonOperator),
|
||||
ProductVersion(PathBuf, String, ComparisonOperator),
|
||||
FilenameVersion(PathBuf, Regex, String, ComparisonOperator),
|
||||
DescriptionContains(PathBuf, Regex),
|
||||
}
|
||||
|
||||
impl fmt::Display for Function {
|
||||
@@ -82,6 +83,9 @@ impl fmt::Display for Function {
|
||||
c
|
||||
)
|
||||
}
|
||||
DescriptionContains(p, r) => {
|
||||
write!(f, "description_contains(\"{}\", \"{}\")", p.display(), r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +127,9 @@ impl PartialEq for Function {
|
||||
&& eq(r1.as_str(), r2.as_str())
|
||||
&& eq(&p1.to_string_lossy(), &p2.to_string_lossy())
|
||||
}
|
||||
(DescriptionContains(p1, r1), DescriptionContains(p2, r2)) => {
|
||||
eq(r1.as_str(), r2.as_str()) && eq(&p1.to_string_lossy(), &p2.to_string_lossy())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -187,6 +194,10 @@ impl Hash for Function {
|
||||
v.to_lowercase().hash(state);
|
||||
c.hash(state);
|
||||
}
|
||||
DescriptionContains(p, r) => {
|
||||
p.to_string_lossy().to_lowercase().hash(state);
|
||||
r.as_str().to_lowercase().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
discriminant(self).hash(state);
|
||||
@@ -332,6 +343,16 @@ mod tests {
|
||||
&format!("{}", function)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_fmt_for_description_contains_should_format_correctly() {
|
||||
let function = Function::DescriptionContains("Blank.esp".into(), regex("€ƒ."));
|
||||
|
||||
assert_eq!(
|
||||
"description_contains(\"Blank.esp\", \"€ƒ.\")",
|
||||
&format!("{}", function)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod eq {
|
||||
@@ -815,6 +836,40 @@ mod tests {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_eq_for_description_contains_should_check_pathbuf_and_regex() {
|
||||
assert_eq!(
|
||||
Function::DescriptionContains("Blank.esp".into(), regex("€ƒ.")),
|
||||
Function::DescriptionContains("Blank.esp".into(), regex("€ƒ."))
|
||||
);
|
||||
|
||||
assert_ne!(
|
||||
Function::DescriptionContains("Blank.esp".into(), regex("€ƒ.")),
|
||||
Function::DescriptionContains("Blank.esp".into(), regex(".*"))
|
||||
);
|
||||
assert_ne!(
|
||||
Function::DescriptionContains("Blank.esp".into(), regex("€ƒ.")),
|
||||
Function::DescriptionContains("other".into(), regex("€ƒ."))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_eq_for_description_contains_should_be_case_insensitive_on_pathbuf_and_regex() {
|
||||
assert_eq!(
|
||||
Function::DescriptionContains("Blank.esp".into(), regex("€ƒ.")),
|
||||
Function::DescriptionContains("blank.esp".into(), regex("€Ƒ."))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_eq_description_contains_should_not_be_equal_to_file_regex_with_same_pathbuf_and_regex(
|
||||
) {
|
||||
assert_ne!(
|
||||
Function::DescriptionContains("Blank.esp".into(), regex("€ƒ.")),
|
||||
Function::FileRegex("Blank.esp".into(), regex("€ƒ."))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod hash {
|
||||
@@ -1346,5 +1401,39 @@ mod tests {
|
||||
|
||||
assert_eq!(hash(function1), hash(function2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_hash_description_contains_should_hash_pathbuf_and_regex() {
|
||||
let function1 = Function::DescriptionContains("Blank.esp".into(), regex("€ƒ."));
|
||||
let function2 = Function::DescriptionContains("Blank.esp".into(), regex("€ƒ."));
|
||||
|
||||
assert_eq!(hash(function1), hash(function2));
|
||||
|
||||
let function1 = Function::DescriptionContains("Blank.esp".into(), regex("€ƒ."));
|
||||
let function2 = Function::DescriptionContains("other".into(), regex("€ƒ."));
|
||||
|
||||
assert_ne!(hash(function1), hash(function2));
|
||||
|
||||
let function1 = Function::DescriptionContains("Blank.esp".into(), regex("€ƒ."));
|
||||
let function2 = Function::DescriptionContains("Blank.esp".into(), regex(".*"));
|
||||
|
||||
assert_ne!(hash(function1), hash(function2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_hash_description_contains_should_be_case_insensitive() {
|
||||
let function1 = Function::DescriptionContains("blank.esp".into(), regex("€Ƒ."));
|
||||
let function2 = Function::DescriptionContains("Blank.esp".into(), regex("€ƒ."));
|
||||
|
||||
assert_eq!(hash(function1), hash(function2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_hash_file_regex_and_description_contains_should_not_have_equal_hashes() {
|
||||
let function1 = Function::FileRegex("Blank.esp".into(), regex("€ƒ."));
|
||||
let function2 = Function::DescriptionContains("Blank.esp".into(), regex("€ƒ."));
|
||||
|
||||
assert_ne!(hash(function1), hash(function2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+67
-6
@@ -46,11 +46,20 @@ fn is_in_game_path(path: &Path) -> bool {
|
||||
|
||||
true
|
||||
}
|
||||
fn parse_regex(input: &str) -> ParsingResult<Regex> {
|
||||
RegexBuilder::new(&format!("^{}$", input))
|
||||
|
||||
fn build_regex(input: &str) -> Result<(&'static str, Regex), regex::Error> {
|
||||
RegexBuilder::new(input)
|
||||
.case_insensitive(true)
|
||||
.build()
|
||||
.map(|r| ("", r))
|
||||
}
|
||||
|
||||
fn parse_regex(input: &str) -> ParsingResult<Regex> {
|
||||
build_regex(input).map_err(|e| Err::Failure(ParsingErrorKind::from(e).at(input)))
|
||||
}
|
||||
|
||||
fn parse_anchored_regex(input: &str) -> ParsingResult<Regex> {
|
||||
build_regex(&format!("^{}$", input))
|
||||
.map_err(|e| Err::Failure(ParsingErrorKind::from(e).at(input)))
|
||||
}
|
||||
|
||||
@@ -136,6 +145,22 @@ fn parse_filename_version_args(
|
||||
Ok((remaining_input, (path, regex, version, comparator)))
|
||||
}
|
||||
|
||||
fn parse_description_contains_args(input: &str) -> ParsingResult<(PathBuf, Regex)> {
|
||||
let mut parser = (
|
||||
map_err(parse_path),
|
||||
map_err(whitespace(tag(","))),
|
||||
delimited(
|
||||
map_err(tag("\"")),
|
||||
map_parser(is_not("\""), parse_regex),
|
||||
map_err(tag("\"")),
|
||||
),
|
||||
);
|
||||
|
||||
let (remaining_input, (path, _, regex)) = parser.parse(input)?;
|
||||
|
||||
Ok((remaining_input, (path, regex)))
|
||||
}
|
||||
|
||||
fn parse_crc(input: &str) -> ParsingResult<u32> {
|
||||
u32::from_str_radix(input, 16)
|
||||
.map(|c| ("", c))
|
||||
@@ -193,13 +218,13 @@ fn parse_regex_path(input: &str) -> ParsingResult<(PathBuf, Regex)> {
|
||||
return Err(not_in_game_directory(input, parent_path));
|
||||
}
|
||||
|
||||
let regex = parse_regex(regex_slice)?.1;
|
||||
let regex = parse_anchored_regex(regex_slice)?.1;
|
||||
|
||||
Ok((remaining_input, (parent_path, regex)))
|
||||
}
|
||||
|
||||
fn parse_regex_filename(input: &str) -> ParsingResult<Regex> {
|
||||
map_parser(is_not(INVALID_REGEX_PATH_CHARS), parse_regex).parse(input)
|
||||
map_parser(is_not(INVALID_REGEX_PATH_CHARS), parse_anchored_regex).parse(input)
|
||||
}
|
||||
|
||||
impl Function {
|
||||
@@ -319,6 +344,14 @@ impl Function {
|
||||
),
|
||||
|(path, crc)| Function::Checksum(path, crc),
|
||||
),
|
||||
map(
|
||||
delimited(
|
||||
map_err(tag("description_contains(")),
|
||||
parse_description_contains_args,
|
||||
map_err(tag(")")),
|
||||
),
|
||||
|(path, regex)| Function::DescriptionContains(path, regex),
|
||||
),
|
||||
))
|
||||
.parse(input)
|
||||
}
|
||||
@@ -338,8 +371,22 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_regex_should_produce_a_regex_that_does_not_partially_match() {
|
||||
let (_, regex) = parse_regex("cargo.").unwrap();
|
||||
fn parse_regex_should_produce_a_regex_that_does_partially_match() {
|
||||
let (_, regex) = parse_regex("argo.").unwrap();
|
||||
|
||||
assert!(regex.is_match("Cargo.toml"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_anchored_regex_should_produce_case_insensitive_regex() {
|
||||
let (_, regex) = parse_anchored_regex("cargo.*").unwrap();
|
||||
|
||||
assert!(regex.is_match("Cargo.toml"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_anchored_regex_should_produce_a_regex_that_does_not_partially_match() {
|
||||
let (_, regex) = parse_anchored_regex("cargo.").unwrap();
|
||||
|
||||
assert!(!regex.is_match("Cargo.toml"));
|
||||
}
|
||||
@@ -725,4 +772,18 @@ mod tests {
|
||||
Function::parse("filename_version(\"subdir/Cargo .+.toml\", \"1.2\", ==)").is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_parse_should_parse_a_description_contains_function() {
|
||||
let output = Function::parse("description_contains(\"Blank.esp\", \"€ƒ.\")").unwrap();
|
||||
|
||||
assert!(output.0.is_empty());
|
||||
match output.1 {
|
||||
Function::DescriptionContains(p, r) => {
|
||||
assert_eq!(PathBuf::from("Blank.esp"), p);
|
||||
assert_eq!(Regex::new("€ƒ.").unwrap().as_str(), r.as_str());
|
||||
}
|
||||
_ => panic!("Expected a description_contains function"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user