#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Flags {
pub case_insensitive: bool,
pub multiline: bool,
pub dot_matches_newline: bool,
}
impl Flags {
pub fn new() -> Self {
Self::default()
}
pub fn any_set(&self) -> bool {
self.case_insensitive || self.multiline || self.dot_matches_newline
}
pub fn parse_from_pattern(pattern: &str) -> Option<(Self, &str)> {
if !pattern.starts_with("(?") {
return None;
}
let close_idx = pattern.find(')')?;
let flags_str = &pattern[2..close_idx];
if flags_str.is_empty() {
return None;
}
let first_char = flags_str.chars().next()?;
match first_char {
'=' | '!' | '<' | ':' | '#' | '>' | 'P' => {
return None;
}
_ => {}
}
let mut flags = Flags::new();
let mut has_flags = false;
for ch in flags_str.chars() {
match ch {
'i' => {
flags.case_insensitive = true;
has_flags = true;
}
'm' => {
flags.multiline = true;
has_flags = true;
}
's' => {
flags.dot_matches_newline = true;
has_flags = true;
}
'x' | 'U' | '-' => {
has_flags = true;
}
_ => {
return None;
}
}
}
if !has_flags {
return None;
}
let remaining = &pattern[close_idx + 1..];
Some((flags, remaining))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_single_flag() {
let (flags, rest) = Flags::parse_from_pattern("(?i)hello").unwrap();
assert!(flags.case_insensitive);
assert!(!flags.multiline);
assert!(!flags.dot_matches_newline);
assert_eq!(rest, "hello");
}
#[test]
fn test_parse_multiple_flags() {
let (flags, rest) = Flags::parse_from_pattern("(?ims)pattern").unwrap();
assert!(flags.case_insensitive);
assert!(flags.multiline);
assert!(flags.dot_matches_newline);
assert_eq!(rest, "pattern");
}
#[test]
fn test_parse_dotall_flag() {
let (flags, rest) = Flags::parse_from_pattern("(?s)a.*b").unwrap();
assert!(!flags.case_insensitive);
assert!(!flags.multiline);
assert!(flags.dot_matches_newline);
assert_eq!(rest, "a.*b");
}
#[test]
fn test_no_flags() {
assert!(Flags::parse_from_pattern("hello").is_none());
assert!(Flags::parse_from_pattern("(?:hello)").is_none());
assert!(Flags::parse_from_pattern("(?=lookahead)").is_none());
}
}