use std::fmt::{self, Display, Formatter};
#[derive(Debug, Clone)]
pub struct AssertionSentence {
pub subject: String,
pub verb: String,
pub object: String,
pub qualifiers: Vec<String>,
pub negated: bool,
}
impl AssertionSentence {
pub fn new(verb: impl Into<String>, object: impl Into<String>) -> Self {
return Self { subject: "".to_string(), verb: verb.into(), object: object.into(), qualifiers: Vec::new(), negated: false };
}
pub fn with_negation(mut self, negated: bool) -> Self {
self.negated = negated;
return self;
}
pub fn with_qualifier(mut self, qualifier: impl Into<String>) -> Self {
self.qualifiers.push(qualifier.into());
return self;
}
pub fn format(&self) -> String {
let mut result = if self.negated { format!("not {} {}", self.verb, self.object) } else { format!("{} {}", self.verb, self.object) };
if !self.qualifiers.is_empty() {
result.push(' ');
result.push_str(&self.qualifiers.join(" "));
}
return result;
}
pub fn format_grammatical(&self) -> String {
let mut result = if self.negated {
format!("{} not {}", self.verb, self.object)
} else {
format!("{} {}", self.verb, self.object)
};
if !self.qualifiers.is_empty() {
result.push(' ');
result.push_str(&self.qualifiers.join(" "));
}
return result;
}
pub fn format_with_conjugation(&self, subject: &str) -> String {
let is_plural = Self::is_plural_subject(subject);
let conjugated_verb = self.conjugate_verb(is_plural);
let mut result = if self.negated {
format!("{} not {}", conjugated_verb, self.object)
} else {
format!("{} {}", conjugated_verb, self.object)
};
if !self.qualifiers.is_empty() {
result.push(' ');
result.push_str(&self.qualifiers.join(" "));
}
return result;
}
fn is_plural_subject(subject: &str) -> bool {
let base_name = Self::extract_base_name(subject);
let plural_endings = ["s", "es", "ies"];
let is_plural_ending = plural_endings.iter().any(|ending| base_name.ends_with(ending));
let common_plurals = [
"items",
"elements",
"values",
"arrays",
"lists",
"maps",
"sets",
"objects",
"attributes",
"properties",
"entries",
"keys",
"numbers",
"strings",
"data",
"results",
"options",
"errors",
"children",
];
let is_common_plural = common_plurals.contains(&base_name.to_lowercase().as_str());
return is_plural_ending || is_common_plural;
}
fn extract_base_name(expr: &str) -> String {
let without_ref = expr.trim_start_matches('&');
if let Some(dot_pos) = without_ref.find('.') {
return without_ref[0..dot_pos].to_string();
}
if let Some(bracket_pos) = without_ref.find('[') {
return without_ref[0..bracket_pos].to_string();
}
return without_ref.to_string();
}
fn conjugate_verb(&self, is_plural: bool) -> String {
match self.verb.as_str() {
"be" => {
if is_plural {
"are".to_string()
} else {
"is".to_string()
}
}
"have" => {
if is_plural {
"have".to_string()
} else {
"has".to_string()
}
}
"contain" => {
if is_plural {
"contain".to_string()
} else {
"contains".to_string()
}
}
"start with" => {
if is_plural {
"start with".to_string()
} else {
"starts with".to_string()
}
}
"end with" => {
if is_plural {
"end with".to_string()
} else {
"ends with".to_string()
}
}
verb => {
if is_plural {
verb.to_string()
} else {
if verb.ends_with('s') || verb.ends_with('x') || verb.ends_with('z') || verb.ends_with("sh") || verb.ends_with("ch") {
format!("{}es", verb)
} else if verb.ends_with('y')
&& !verb.ends_with("ay")
&& !verb.ends_with("ey")
&& !verb.ends_with("oy")
&& !verb.ends_with("uy")
{
format!("{}ies", &verb[0..verb.len() - 1])
} else {
format!("{}s", verb)
}
}
}
}
}
}
impl Display for AssertionSentence {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
return write!(f, "{}", self.format());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assertion_sentence_new() {
let sentence = AssertionSentence::new("be", "positive");
assert_eq!(sentence.subject, "");
assert_eq!(sentence.verb, "be");
assert_eq!(sentence.object, "positive");
assert_eq!(sentence.qualifiers.len(), 0);
assert_eq!(sentence.negated, false);
}
#[test]
fn test_with_negation() {
let sentence = AssertionSentence::new("be", "positive").with_negation(true);
assert_eq!(sentence.negated, true);
let toggled_sentence = sentence.with_negation(false);
assert_eq!(toggled_sentence.negated, false);
}
#[test]
fn test_with_qualifier() {
let sentence = AssertionSentence::new("be", "in range").with_qualifier("when rounded");
assert_eq!(sentence.qualifiers, vec!["when rounded"]);
let updated_sentence = sentence.with_qualifier("with tolerance");
assert_eq!(updated_sentence.qualifiers, vec!["when rounded", "with tolerance"]);
}
#[test]
fn test_format_basic() {
let sentence = AssertionSentence::new("be", "positive");
assert_eq!(sentence.format(), "be positive");
}
#[test]
fn test_format_with_negation() {
let sentence = AssertionSentence::new("be", "positive").with_negation(true);
assert_eq!(sentence.format(), "not be positive");
}
#[test]
fn test_format_with_qualifiers() {
let sentence = AssertionSentence::new("be", "in range").with_qualifier("when rounded").with_qualifier("with tolerance");
assert_eq!(sentence.format(), "be in range when rounded with tolerance");
}
#[test]
fn test_format_with_negation_and_qualifiers() {
let sentence = AssertionSentence::new("be", "in range").with_negation(true).with_qualifier("when rounded");
assert_eq!(sentence.format(), "not be in range when rounded");
}
#[test]
fn test_format_grammatical() {
let sentence = AssertionSentence::new("be", "positive");
assert_eq!(sentence.format_grammatical(), "be positive");
let negated = sentence.clone().with_negation(true);
assert_eq!(negated.format_grammatical(), "be not positive");
}
#[test]
fn test_format_grammatical_with_qualifiers() {
let sentence = AssertionSentence::new("be", "in range").with_negation(true).with_qualifier("when rounded");
assert_eq!(sentence.format_grammatical(), "be not in range when rounded");
}
#[test]
fn test_is_plural_subject() {
assert_eq!(AssertionSentence::is_plural_subject("value"), false);
assert_eq!(AssertionSentence::is_plural_subject("number"), false);
assert_eq!(AssertionSentence::is_plural_subject("count"), false);
assert_eq!(AssertionSentence::is_plural_subject("item"), false);
assert_eq!(AssertionSentence::is_plural_subject("values"), true);
assert_eq!(AssertionSentence::is_plural_subject("numbers"), true);
assert_eq!(AssertionSentence::is_plural_subject("items"), true);
assert_eq!(AssertionSentence::is_plural_subject("lists"), true);
assert_eq!(AssertionSentence::is_plural_subject("data"), true);
assert_eq!(AssertionSentence::is_plural_subject("children"), true);
}
#[test]
fn test_extract_base_name() {
assert_eq!(AssertionSentence::extract_base_name("&value"), "value");
assert_eq!(AssertionSentence::extract_base_name("values.len()"), "values");
assert_eq!(AssertionSentence::extract_base_name("items[0]"), "items");
assert_eq!(AssertionSentence::extract_base_name("&items[0]"), "items");
assert_eq!(AssertionSentence::extract_base_name("&values.len()"), "values");
}
#[test]
fn test_conjugate_verb() {
let sentence = AssertionSentence::new("", "");
let special_verbs = [
("be", "is", "are"),
("have", "has", "have"),
("contain", "contains", "contain"),
("start with", "starts with", "start with"),
("end with", "ends with", "end with"),
];
for (base, singular, plural) in special_verbs.iter() {
let mut test_sentence = sentence.clone();
test_sentence.verb = base.to_string();
assert_eq!(test_sentence.conjugate_verb(false), *singular);
assert_eq!(test_sentence.conjugate_verb(true), *plural);
}
let regular_verbs = [("match", "matches"), ("exceed", "exceeds"), ("include", "includes")];
for (base, singular) in regular_verbs.iter() {
let mut test_sentence = sentence.clone();
test_sentence.verb = base.to_string();
assert_eq!(test_sentence.conjugate_verb(false), *singular);
assert_eq!(test_sentence.conjugate_verb(true), *base);
}
let special_spelling = [
("pass", "passes"),
("fix", "fixes"),
("buzz", "buzzes"),
("wash", "washes"),
("match", "matches"),
("try", "tries"),
("fly", "flies"),
("comply", "complies"),
("play", "plays"),
("enjoy", "enjoys"),
];
for (base, singular) in special_spelling.iter() {
let mut test_sentence = sentence.clone();
test_sentence.verb = base.to_string();
assert_eq!(test_sentence.conjugate_verb(false), *singular);
assert_eq!(test_sentence.conjugate_verb(true), *base);
}
}
#[test]
fn test_format_with_conjugation() {
let sentence = AssertionSentence::new("be", "positive");
assert_eq!(sentence.format_with_conjugation("value"), "is positive");
assert_eq!(sentence.format_with_conjugation("values"), "are positive");
let negated = sentence.clone().with_negation(true);
assert_eq!(negated.format_with_conjugation("value"), "is not positive");
assert_eq!(negated.format_with_conjugation("values"), "are not positive");
let qualified = sentence.clone().with_qualifier("always");
assert_eq!(qualified.format_with_conjugation("value"), "is positive always");
let contain_sentence = AssertionSentence::new("contain", "element");
assert_eq!(contain_sentence.format_with_conjugation("list"), "contains element");
assert_eq!(contain_sentence.format_with_conjugation("lists"), "contain element");
}
#[test]
fn test_display_trait() {
let sentence = AssertionSentence::new("be", "positive");
assert_eq!(format!("{}", sentence), "be positive");
let negated = sentence.clone().with_negation(true);
assert_eq!(format!("{}", negated), "not be positive");
}
}