aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_cli')
-rw-r--r--mingling_cli/src/proj_mgr/rule_solver.rs41
1 files changed, 39 insertions, 2 deletions
diff --git a/mingling_cli/src/proj_mgr/rule_solver.rs b/mingling_cli/src/proj_mgr/rule_solver.rs
index 7dfb7c7..c33cc1e 100644
--- a/mingling_cli/src/proj_mgr/rule_solver.rs
+++ b/mingling_cli/src/proj_mgr/rule_solver.rs
@@ -241,7 +241,15 @@ pub fn validate_mutexes(
/// - parentheses for grouping
pub fn eval_rule(rule: &str, answers: &HashMap<String, String>) -> bool {
let mut parser = RuleParser::new(rule, answers);
- parser.parse_or().unwrap_or(false)
+ let Some(value) = parser.parse_or() else {
+ return false;
+ };
+ // The whole expression must be consumed; trailing garbage invalidates it.
+ parser.skip_ws();
+ if parser.pos != parser.chars.len() {
+ return false;
+ }
+ value
}
/// A key is truthy when it is present with a non-empty, non-`"false"` value.
@@ -310,7 +318,7 @@ impl<'a> RuleParser<'a> {
self.parse_primary()
}
- /// `primary := '(' or ')' | ident ('==' ident)?`
+ /// `primary := '(' or ')' | ident (('==' | '!=') ident)?`
fn parse_primary(&mut self) -> Option<bool> {
if self.eat('(') {
let value = self.parse_or()?;
@@ -322,6 +330,10 @@ impl<'a> RuleParser<'a> {
let other = self.parse_ident()?;
return Some(self.answers.get(&ident).map(String::as_str) == Some(other.as_str()));
}
+ if self.eat('!') && self.eat('=') {
+ let other = self.parse_ident()?;
+ return Some(self.answers.get(&ident).map(String::as_str) != Some(other.as_str()));
+ }
Some(is_truthy(&ident, self.answers))
}
@@ -585,4 +597,29 @@ name = "tokio"
assert!(!eval_rule("parser == clap", &answers));
assert!(eval_rule("use_parser && parser == picker", &answers));
}
+
+ #[test]
+ fn eval_not_equal_comparison() {
+ let mut answers = HashMap::new();
+ answers.insert("parser".into(), "picker".into());
+ answers.insert("use_parser".into(), "true".into());
+
+ assert!(!eval_rule("parser != picker", &answers));
+ assert!(eval_rule("parser != clap", &answers));
+
+ // The template's NOT_PARSER_PICKER rule.
+ assert!(!eval_rule("!use_parser || parser != picker", &answers));
+ answers.remove("use_parser");
+ assert!(eval_rule("!use_parser || parser != picker", &answers));
+ }
+
+ #[test]
+ fn eval_rejects_trailing_garbage() {
+ // Unsupported tokens must invalidate the expression instead of being
+ // silently ignored.
+ let mut answers = HashMap::new();
+ answers.insert("parser".into(), "picker".into());
+ assert!(!eval_rule("parser >>> picker", &answers));
+ assert!(!eval_rule("parser ||", &answers));
+ }
}