Make normalized search syntax more explicit

Also fix a bug with NoCombining and WordBoundary searches.
This commit is contained in:
RumovZ 2020-12-29 11:06:53 +01:00
parent ecd04f8a59
commit 62753290d8

View file

@ -19,8 +19,8 @@ fn write_nodes(nodes: &[Node]) -> String {
fn write_node(node: &Node) -> String { fn write_node(node: &Node) -> String {
use Node::*; use Node::*;
match node { match node {
And => " ".to_string(), And => " AND ".to_string(),
Or => " or ".to_string(), Or => " OR ".to_string(),
Not(n) => format!("-{}", write_node(n)), Not(n) => format!("-{}", write_node(n)),
Group(ns) => format!("({})", write_nodes(ns)), Group(ns) => format!("({})", write_nodes(ns)),
Search(n) => write_search_node(n), Search(n) => write_search_node(n),
@ -30,83 +30,60 @@ fn write_node(node: &Node) -> String {
fn write_search_node(node: &SearchNode) -> String { fn write_search_node(node: &SearchNode) -> String {
use SearchNode::*; use SearchNode::*;
match node { match node {
UnqualifiedText(s) => escape(s), UnqualifiedText(s) => quote(&s.replace(":", "\\:")),
SingleField { field, text, is_re } => write_single_field(field, text, *is_re), SingleField { field, text, is_re } => write_single_field(field, text, *is_re),
AddedInDays(u) => format!("added:{}", u), AddedInDays(u) => format!("\"added:{}\"", u),
EditedInDays(u) => format!("edited:{}", u), EditedInDays(u) => format!("\"edited:{}\"", u),
CardTemplate(t) => write_template(t), CardTemplate(t) => write_template(t),
Deck(s) => format!("deck:{}", &escape_suffix(s)), Deck(s) => quote(&format!("deck:{}", s)),
DeckID(DeckIDType(i)) => format!("did:{}", i), DeckID(DeckIDType(i)) => format!("\"did:{}\"", i),
NoteTypeID(NoteTypeIDType(i)) => format!("mid:{}", i), NoteTypeID(NoteTypeIDType(i)) => format!("\"mid:{}\"", i),
NoteType(s) => format!("note:{}", &escape_suffix(s)), NoteType(s) => quote(&format!("note:{}", s)),
Rated { days, ease } => write_rated(days, ease), 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 } => { 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), State(k) => write_state(k),
Flag(u) => format!("flag:{}", u), Flag(u) => format!("\"flag:{}\"", u),
NoteIDs(s) => format!("nid:{}", s), NoteIDs(s) => format!("\"nid:{}\"", s),
CardIDs(s) => format!("cid:{}", s), CardIDs(s) => format!("\"cid:{}\"", s),
Property { operator, kind } => write_prop(operator, kind), Property { operator, kind } => write_prop(operator, kind),
WholeCollection => "".to_string(), WholeCollection => "".to_string(),
Regex(s) => format!("re:{}", &escape_suffix(s)), Regex(s) => quote(&format!("re:{}", s)),
NoCombining(s) => format!("re:{}", &escape_suffix(s)), NoCombining(s) => quote(&format!("nc:{}", s)),
WordBoundary(s) => format!("re:{}", &escape_suffix(s)), WordBoundary(s) => quote(&format!("w:{}", s)),
} }
} }
fn escape(txt: &str) -> String { /// Escape and wrap in double quotes.
let txt = txt.replace("\"", "\\\"").replace(":", "\\:"); fn quote(txt: &str) -> String {
if txt.chars().any(|c| " \u{3000}()".contains(c)) { format!("\"{}\"", txt.replace("\"", "\\\""))
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
}
} }
fn write_single_field(field: &str, text: &str, is_re: bool) -> String { fn write_single_field(field: &str, text: &str, is_re: bool) -> String {
let field = field.replace(":", "\\:");
let re = if is_re { "re:" } else { "" }; let re = if is_re { "re:" } else { "" };
let txt = format!("{}:{}{}", field, re, text).replace("\"", "\\\""); quote(&format!("{}:{}{}", field.replace(":", "\\:"), re, text))
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 write_template(template: &TemplateKind) -> String { fn write_template(template: &TemplateKind) -> String {
match template { match template {
TemplateKind::Ordinal(u) => format!("card:{}", u), TemplateKind::Ordinal(u) => format!("\"card:{}\"", u),
TemplateKind::Name(s) => format!("card:{}", s), TemplateKind::Name(s) => format!("\"card:{}\"", s),
} }
} }
fn write_rated(days: &u32, ease: &Option<u8>) -> String { fn write_rated(days: &u32, ease: &Option<u8>) -> String {
match ease { match ease {
Some(u) => format!("rated:{}:{}", days, u), Some(u) => format!("\"rated:{}:{}\"", days, u),
None => format!("rated:{}", days), None => format!("\"rated:{}\"\"", days),
} }
} }
fn write_state(kind: &StateKind) -> String { fn write_state(kind: &StateKind) -> String {
use StateKind::*; use StateKind::*;
format!( format!(
"is:{}", "\"is:{}\"",
match kind { match kind {
New => "new", New => "new",
Review => "review", Review => "review",
@ -123,10 +100,10 @@ fn write_state(kind: &StateKind) -> String {
fn write_prop(operator: &str, kind: &PropertyKind) -> String { fn write_prop(operator: &str, kind: &PropertyKind) -> String {
use PropertyKind::*; use PropertyKind::*;
match kind { match kind {
Due(i) => format!("prop:due{}{}", operator, i), Due(i) => format!("\"prop:due{}{}\"", operator, i),
Interval(u) => format!("prop:ivl{}{}", operator, u), Interval(u) => format!("\"prop:ivl{}{}\"", operator, u),
Reps(u) => format!("prop:reps{}{}", operator, u), Reps(u) => format!("\"prop:reps{}{}\"", operator, u),
Lapses(u) => format!("prop:lapses{}{}", operator, u), Lapses(u) => format!("\"prop:lapses{}{}\"", operator, u),
Ease(f) => format!("prop:ease{}{}", operator, f), Ease(f) => format!("\"prop:ease{}{}\"", operator, f),
} }
} }