diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 4fc860232..f59378be4 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -5,7 +5,7 @@ use crate::err::{AnkiError, Result}; use crate::types::ObjID; use nom::branch::alt; use nom::bytes::complete::{escaped, is_not, tag, take_while1}; -use nom::character::complete::{char, one_of}; +use nom::character::complete::{anychar, char, one_of}; use nom::character::is_digit; use nom::combinator::{all_consuming, map, map_res}; use nom::sequence::{delimited, preceded, tuple}; @@ -77,6 +77,7 @@ pub(super) enum SearchNode<'a> { kind: PropertyKind, }, WholeCollection, + Regex(Cow<'a, str>), } #[derive(Debug, PartialEq)] @@ -235,9 +236,8 @@ fn quoted_term_str(s: &str) -> IResult<&str, &str> { } /// Quoted text, terminated by a non-escaped double quote -/// Can escape %, _, " and \ fn quoted_term_inner(s: &str) -> IResult<&str, &str> { - escaped(is_not(r#""\"#), '\\', one_of(r#""\%_"#))(s) + escaped(is_not(r#""\"#), '\\', anychar)(s) } /// eg deck:"foo bar" - quotes must come after the : @@ -270,7 +270,7 @@ fn search_node_for_text_with_argument<'a>( "rated" => parse_rated(val.as_ref())?, "dupes" => parse_dupes(val.as_ref())?, "prop" => parse_prop(val.as_ref())?, - + "re" => SearchNode::Regex(val), // anything else is a field search _ => SearchNode::SingleField { field: key, @@ -432,6 +432,12 @@ mod test { ] ); + // any character should be escapable in quotes + assert_eq!( + parse(r#""re:\btest""#)?, + vec![Search(Regex(r"\btest".into()))] + ); + assert_eq!(parse("added:3")?, vec![Search(AddedInDays(3))]); assert_eq!( parse("card:front")?, diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index da3a3e6b8..58519631e 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -80,6 +80,7 @@ impl SqlWriter<'_, '_> { } SearchNode::Property { operator, kind } => self.write_prop(operator, kind)?, SearchNode::WholeCollection => write!(self.sql, "true").unwrap(), + SearchNode::Regex(re) => self.write_regex(re.as_ref()), }; Ok(()) } @@ -334,6 +335,11 @@ impl SqlWriter<'_, '_> { write!(self.sql, "c.id > {}", cutoff).unwrap(); Ok(()) } + + fn write_regex(&mut self, word: &str) { + self.sql.push_str("n.flds regexp ?"); + self.args.push(format!(r"(?i){}", word)); + } } // Write a list of IDs as '(x,y,...)' into the provided string. @@ -537,6 +543,12 @@ mod test { "(n.mid in (1581236385345,1581236385346,1581236385347,1581236385344))" ); + // regex + assert_eq!( + s(ctx, r"re:\bone"), + ("(n.flds regexp ?)".into(), vec![r"(?i)\bone".into()]) + ); + Ok(()) }) .unwrap();