From 62753290d83376111006c14805f85a294fa305b9 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 29 Dec 2020 11:06:53 +0100 Subject: [PATCH] Make normalized search syntax more explicit Also fix a bug with NoCombining and WordBoundary searches. --- rslib/src/search/writer.rs | 85 ++++++++++++++------------------------ 1 file changed, 31 insertions(+), 54 deletions(-) diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index 98e91addc..dab60aee0 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -19,8 +19,8 @@ fn write_nodes(nodes: &[Node]) -> String { fn write_node(node: &Node) -> String { use Node::*; match node { - And => " ".to_string(), - Or => " or ".to_string(), + And => " AND ".to_string(), + Or => " OR ".to_string(), Not(n) => format!("-{}", write_node(n)), Group(ns) => format!("({})", write_nodes(ns)), Search(n) => write_search_node(n), @@ -30,83 +30,60 @@ fn write_node(node: &Node) -> String { fn write_search_node(node: &SearchNode) -> String { use SearchNode::*; match node { - UnqualifiedText(s) => escape(s), + UnqualifiedText(s) => quote(&s.replace(":", "\\:")), SingleField { field, text, is_re } => write_single_field(field, text, *is_re), - AddedInDays(u) => format!("added:{}", u), - EditedInDays(u) => format!("edited:{}", u), + AddedInDays(u) => format!("\"added:{}\"", u), + EditedInDays(u) => format!("\"edited:{}\"", u), CardTemplate(t) => write_template(t), - Deck(s) => format!("deck:{}", &escape_suffix(s)), - DeckID(DeckIDType(i)) => format!("did:{}", i), - NoteTypeID(NoteTypeIDType(i)) => format!("mid:{}", i), - NoteType(s) => format!("note:{}", &escape_suffix(s)), + Deck(s) => quote(&format!("deck:{}", s)), + DeckID(DeckIDType(i)) => format!("\"did:{}\"", i), + NoteTypeID(NoteTypeIDType(i)) => format!("\"mid:{}\"", i), + NoteType(s) => quote(&format!("note:{}", s)), Rated { days, ease } => write_rated(days, ease), - Tag(s) => format!("tag:{}", &escape_suffix(s)), + Tag(s) => quote(&format!("tag:{}", s)), Duplicates { note_type_id, text } => { - format!("dupes:{},{}", note_type_id, escape_suffix(text)) + quote(&format!("dupes:{},{}", note_type_id, text)) } State(k) => write_state(k), - Flag(u) => format!("flag:{}", u), - NoteIDs(s) => format!("nid:{}", s), - CardIDs(s) => format!("cid:{}", s), + Flag(u) => format!("\"flag:{}\"", u), + NoteIDs(s) => format!("\"nid:{}\"", s), + CardIDs(s) => format!("\"cid:{}\"", s), Property { operator, kind } => write_prop(operator, kind), WholeCollection => "".to_string(), - Regex(s) => format!("re:{}", &escape_suffix(s)), - NoCombining(s) => format!("re:{}", &escape_suffix(s)), - WordBoundary(s) => format!("re:{}", &escape_suffix(s)), + Regex(s) => quote(&format!("re:{}", s)), + NoCombining(s) => quote(&format!("nc:{}", s)), + WordBoundary(s) => quote(&format!("w:{}", s)), } } -fn escape(txt: &str) -> String { - let txt = txt.replace("\"", "\\\"").replace(":", "\\:"); - if txt.chars().any(|c| " \u{3000}()".contains(c)) { - format!(r#""{}""#, txt) - } else if txt.len() > 1 && txt.starts_with('-') { - format!("\\{}", txt) - } else { - txt - } -} - -fn escape_suffix(txt: &str) -> String { - let txt = txt.replace("\"", "\\\""); - if txt.chars().any(|c| " \u{3000}()".contains(c)) { - format!(r#""{}""#, txt) - } else { - txt - } +/// Escape and wrap in double quotes. +fn quote(txt: &str) -> String { + format!("\"{}\"", txt.replace("\"", "\\\"")) } fn write_single_field(field: &str, text: &str, is_re: bool) -> String { - let field = field.replace(":", "\\:"); let re = if is_re { "re:" } else { "" }; - let txt = format!("{}:{}{}", field, re, text).replace("\"", "\\\""); - if txt.chars().any(|c| " \u{3000}()".contains(c)) { - format!(r#""{}""#, txt) - } else if txt.len() > 1 && txt.starts_with('-') { - format!("\\{}", txt) - } else { - txt - } + quote(&format!("{}:{}{}", field.replace(":", "\\:"), re, text)) } fn write_template(template: &TemplateKind) -> String { match template { - TemplateKind::Ordinal(u) => format!("card:{}", u), - TemplateKind::Name(s) => format!("card:{}", s), + TemplateKind::Ordinal(u) => format!("\"card:{}\"", u), + TemplateKind::Name(s) => format!("\"card:{}\"", s), } } fn write_rated(days: &u32, ease: &Option) -> String { match ease { - Some(u) => format!("rated:{}:{}", days, u), - None => format!("rated:{}", days), + Some(u) => format!("\"rated:{}:{}\"", days, u), + None => format!("\"rated:{}\"\"", days), } } fn write_state(kind: &StateKind) -> String { use StateKind::*; format!( - "is:{}", + "\"is:{}\"", match kind { New => "new", Review => "review", @@ -123,10 +100,10 @@ fn write_state(kind: &StateKind) -> String { fn write_prop(operator: &str, kind: &PropertyKind) -> String { use PropertyKind::*; match kind { - Due(i) => format!("prop:due{}{}", operator, i), - Interval(u) => format!("prop:ivl{}{}", operator, u), - Reps(u) => format!("prop:reps{}{}", operator, u), - Lapses(u) => format!("prop:lapses{}{}", operator, u), - Ease(f) => format!("prop:ease{}{}", operator, f), + Due(i) => format!("\"prop:due{}{}\"", operator, i), + Interval(u) => format!("\"prop:ivl{}{}\"", operator, u), + Reps(u) => format!("\"prop:reps{}{}\"", operator, u), + Lapses(u) => format!("\"prop:lapses{}{}\"", operator, u), + Ease(f) => format!("\"prop:ease{}{}\"", operator, f), } }