mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Make normalized search syntax more explicit
Also fix a bug with NoCombining and WordBoundary searches.
This commit is contained in:
parent
ecd04f8a59
commit
62753290d8
1 changed files with 31 additions and 54 deletions
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue