Drop ANDs and optional quotes when normalising
This commit is contained in:
parent
7b2b4528b3
commit
2554e80ce8
@ -68,7 +68,7 @@ pub 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 => " AND ".to_string(),
|
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)),
|
||||||
@ -79,33 +79,37 @@ 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) => quote(&s.replace(":", "\\:")),
|
UnqualifiedText(s) => maybe_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) => quote(&format!("deck:{}", s)),
|
Deck(s) => maybe_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) => quote(&format!("note:{}", s)),
|
Notetype(s) => maybe_quote(&format!("note:{}", s)),
|
||||||
Rated { days, ease } => write_rated(days, ease),
|
Rated { days, ease } => write_rated(days, ease),
|
||||||
Tag(s) => quote(&format!("tag:{}", s)),
|
Tag(s) => maybe_quote(&format!("tag:{}", s)),
|
||||||
Duplicates { notetype_id, text } => write_dupe(notetype_id, text),
|
Duplicates { notetype_id, text } => write_dupe(notetype_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_property(operator, kind),
|
Property { operator, kind } => write_property(operator, kind),
|
||||||
WholeCollection => "\"deck:*\"".to_string(),
|
WholeCollection => "deck:*".to_string(),
|
||||||
Regex(s) => quote(&format!("re:{}", s)),
|
Regex(s) => maybe_quote(&format!("re:{}", s)),
|
||||||
NoCombining(s) => quote(&format!("nc:{}", s)),
|
NoCombining(s) => maybe_quote(&format!("nc:{}", s)),
|
||||||
WordBoundary(s) => quote(&format!("w:{}", s)),
|
WordBoundary(s) => maybe_quote(&format!("w:{}", s)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Escape and wrap in double quotes.
|
/// Escape double quotes and wrap in double quotes if necessary.
|
||||||
fn quote(txt: &str) -> String {
|
fn maybe_quote(txt: &str) -> String {
|
||||||
format!("\"{}\"", txt.replace("\"", "\\\""))
|
if txt.chars().any(|c| " \u{3000}()".contains(c)) {
|
||||||
|
format!("\"{}\"", txt.replace("\"", "\\\""))
|
||||||
|
} else {
|
||||||
|
txt.replace("\"", "\\\"")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_single_field(field: &str, text: &str, is_re: bool) -> String {
|
fn write_single_field(field: &str, text: &str, is_re: bool) -> String {
|
||||||
@ -115,35 +119,35 @@ fn write_single_field(field: &str, text: &str, is_re: bool) -> String {
|
|||||||
} else {
|
} else {
|
||||||
text.to_string()
|
text.to_string()
|
||||||
};
|
};
|
||||||
quote(&format!("{}:{}{}", field.replace(":", "\\:"), re, &text))
|
maybe_quote(&format!("{}:{}{}", field.replace(":", "\\:"), re, &text))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_template(template: &TemplateKind) -> String {
|
fn write_template(template: &TemplateKind) -> String {
|
||||||
match template {
|
match template {
|
||||||
TemplateKind::Ordinal(u) => format!("\"card:{}\"", u + 1),
|
TemplateKind::Ordinal(u) => format!("card:{}", u + 1),
|
||||||
TemplateKind::Name(s) => format!("\"card:{}\"", s),
|
TemplateKind::Name(s) => maybe_quote(&format!("card:{}", s)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_rated(days: &u32, ease: &RatingKind) -> String {
|
fn write_rated(days: &u32, ease: &RatingKind) -> String {
|
||||||
use RatingKind::*;
|
use RatingKind::*;
|
||||||
match ease {
|
match ease {
|
||||||
AnswerButton(n) => format!("\"rated:{}:{}\"", days, n),
|
AnswerButton(n) => format!("rated:{}:{}", days, n),
|
||||||
AnyAnswerButton => format!("\"rated:{}\"", days),
|
AnyAnswerButton => format!("rated:{}", days),
|
||||||
ManualReschedule => format!("\"resched:{}\"", days),
|
ManualReschedule => format!("resched:{}", days),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Escape double quotes and backslashes: \"
|
/// Escape double quotes and backslashes: \"
|
||||||
fn write_dupe(notetype_id: &NotetypeId, text: &str) -> String {
|
fn write_dupe(notetype_id: &NotetypeId, text: &str) -> String {
|
||||||
let esc = text.replace(r"\", r"\\").replace('"', r#"\""#);
|
let esc = text.replace(r"\", r"\\").replace('"', r#"\""#);
|
||||||
format!("\"dupe:{},{}\"", notetype_id, esc)
|
maybe_quote(&format!("dupe:{},{}", notetype_id, esc))
|
||||||
}
|
}
|
||||||
|
|
||||||
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",
|
||||||
@ -160,16 +164,16 @@ fn write_state(kind: &StateKind) -> String {
|
|||||||
fn write_property(operator: &str, kind: &PropertyKind) -> String {
|
fn write_property(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),
|
||||||
Position(u) => format!("\"prop:pos{}{}\"", operator, u),
|
Position(u) => format!("prop:pos{}{}", operator, u),
|
||||||
Rated(u, ease) => match ease {
|
Rated(u, ease) => match ease {
|
||||||
RatingKind::AnswerButton(val) => format!("\"prop:rated{}{}:{}\"", operator, u, val),
|
RatingKind::AnswerButton(val) => format!("prop:rated{}{}:{}", operator, u, val),
|
||||||
RatingKind::AnyAnswerButton => format!("\"prop:rated{}{}\"", operator, u),
|
RatingKind::AnyAnswerButton => format!("prop:rated{}{}", operator, u),
|
||||||
RatingKind::ManualReschedule => format!("\"prop:resched{}{}\"", operator, u),
|
RatingKind::ManualReschedule => format!("prop:resched{}{}", operator, u),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,17 +196,15 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalizing() {
|
fn normalizing() {
|
||||||
assert_eq!(r#""(" AND "-""#, normalize_search(r"\( \-").unwrap());
|
|
||||||
assert_eq!(r#""deck::""#, normalize_search(r"deck:\:").unwrap());
|
|
||||||
assert_eq!(r#""\*" OR "\:""#, normalize_search(r"\* or \:").unwrap());
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
r#""field:foo""#,
|
r#"foo "b a r""#,
|
||||||
normalize_search(r#"field:"foo""#).unwrap()
|
normalize_search(r#""foo" "b a r""#).unwrap()
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
r#""prop:ease>1""#,
|
|
||||||
normalize_search("prop:ease>1.0").unwrap()
|
|
||||||
);
|
);
|
||||||
|
assert_eq!(r#""(" -"#, normalize_search(r"\( \-").unwrap());
|
||||||
|
assert_eq!("deck::", normalize_search(r"deck:\:").unwrap());
|
||||||
|
assert_eq!(r"\* OR \:", normalize_search(r"\* or \:").unwrap());
|
||||||
|
assert_eq!("field:foo", normalize_search(r#"field:"foo""#).unwrap());
|
||||||
|
assert_eq!("prop:ease>1", normalize_search("prop:ease>1.0").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -213,7 +215,7 @@ mod test {
|
|||||||
vec![Node::Search(SearchNode::UnqualifiedText("foo".to_string()))],
|
vec![Node::Search(SearchNode::UnqualifiedText("foo".to_string()))],
|
||||||
Node::Search(SearchNode::UnqualifiedText("bar".to_string()))
|
Node::Search(SearchNode::UnqualifiedText("bar".to_string()))
|
||||||
),
|
),
|
||||||
r#""foo" AND "bar""#,
|
"foo bar",
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
concatenate_searches(
|
concatenate_searches(
|
||||||
@ -221,7 +223,7 @@ mod test {
|
|||||||
vec![Node::Search(SearchNode::UnqualifiedText("foo".to_string()))],
|
vec![Node::Search(SearchNode::UnqualifiedText("foo".to_string()))],
|
||||||
Node::Search(SearchNode::UnqualifiedText("bar".to_string()))
|
Node::Search(SearchNode::UnqualifiedText("bar".to_string()))
|
||||||
),
|
),
|
||||||
r#""foo" OR "bar""#,
|
"foo OR bar",
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
concatenate_searches(
|
concatenate_searches(
|
||||||
@ -229,7 +231,7 @@ mod test {
|
|||||||
vec![Node::Search(SearchNode::WholeCollection)],
|
vec![Node::Search(SearchNode::WholeCollection)],
|
||||||
Node::Search(SearchNode::UnqualifiedText("bar".to_string()))
|
Node::Search(SearchNode::UnqualifiedText("bar".to_string()))
|
||||||
),
|
),
|
||||||
r#""deck:*" OR "bar""#,
|
"deck:* OR bar",
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
concatenate_searches(
|
concatenate_searches(
|
||||||
@ -237,7 +239,7 @@ mod test {
|
|||||||
vec![],
|
vec![],
|
||||||
Node::Search(SearchNode::UnqualifiedText("bar".to_string()))
|
Node::Search(SearchNode::UnqualifiedText("bar".to_string()))
|
||||||
),
|
),
|
||||||
r#""bar""#,
|
"bar",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,29 +247,29 @@ mod test {
|
|||||||
fn replacing() -> Result<()> {
|
fn replacing() -> Result<()> {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
replace_search_node(parse("deck:baz bar")?, parse("deck:foo")?.pop().unwrap()),
|
replace_search_node(parse("deck:baz bar")?, parse("deck:foo")?.pop().unwrap()),
|
||||||
r#""deck:foo" AND "bar""#,
|
"deck:foo bar",
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
replace_search_node(
|
replace_search_node(
|
||||||
parse("tag:foo Or tag:bar")?,
|
parse("tag:foo Or tag:bar")?,
|
||||||
parse("tag:baz")?.pop().unwrap()
|
parse("tag:baz")?.pop().unwrap()
|
||||||
),
|
),
|
||||||
r#""tag:baz" OR "tag:baz""#,
|
"tag:baz OR tag:baz",
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
replace_search_node(
|
replace_search_node(
|
||||||
parse("foo or (-foo tag:baz)")?,
|
parse("foo or (-foo tag:baz)")?,
|
||||||
parse("bar")?.pop().unwrap()
|
parse("bar")?.pop().unwrap()
|
||||||
),
|
),
|
||||||
r#""bar" OR (-"bar" AND "tag:baz")"#,
|
"bar OR (-bar tag:baz)",
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
replace_search_node(parse("is:due")?, parse("-is:new")?.pop().unwrap()),
|
replace_search_node(parse("is:due")?, parse("-is:new")?.pop().unwrap()),
|
||||||
r#""is:due""#
|
"is:due"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
replace_search_node(parse("added:1")?, parse("is:due")?.pop().unwrap()),
|
replace_search_node(parse("added:1")?, parse("is:due")?.pop().unwrap()),
|
||||||
r#""added:1""#
|
"added:1"
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
Reference in New Issue
Block a user