mirror of
https://github.com/ankitects/anki.git
synced 2025-09-22 16:02:23 -04:00
Implement concatenate_searches
Fix minor stuff in writer.rs.
This commit is contained in:
parent
713db05f27
commit
79d0b5496b
3 changed files with 35 additions and 5 deletions
|
@ -88,6 +88,7 @@ service BackendService {
|
||||||
rpc SearchCards (SearchCardsIn) returns (SearchCardsOut);
|
rpc SearchCards (SearchCardsIn) returns (SearchCardsOut);
|
||||||
rpc SearchNotes (SearchNotesIn) returns (SearchNotesOut);
|
rpc SearchNotes (SearchNotesIn) returns (SearchNotesOut);
|
||||||
rpc NegateSearch (String) returns (String);
|
rpc NegateSearch (String) returns (String);
|
||||||
|
rpc ConcatenateSearches (ConcatenateSearchesIn) returns (String);
|
||||||
rpc FindAndReplace (FindAndReplaceIn) returns (UInt32);
|
rpc FindAndReplace (FindAndReplaceIn) returns (UInt32);
|
||||||
|
|
||||||
// scheduling
|
// scheduling
|
||||||
|
|
|
@ -435,6 +435,10 @@ impl BackendService for Backend {
|
||||||
fn negate_search(&self, input: pb::String) -> Result<pb::String> {
|
fn negate_search(&self, input: pb::String) -> Result<pb::String> {
|
||||||
Ok(negate_search(&input.val)?.into())
|
Ok(negate_search(&input.val)?.into())
|
||||||
}
|
}
|
||||||
|
fn concatenate_searches(&self, input: pb::ConcatenateSearchesIn) -> Result<pb::String> {
|
||||||
|
Ok(concatenate_searches(input.sep, &input.searches)?.into())
|
||||||
|
}
|
||||||
|
|
||||||
fn find_and_replace(&self, input: pb::FindAndReplaceIn) -> BackendResult<pb::UInt32> {
|
fn find_and_replace(&self, input: pb::FindAndReplaceIn) -> BackendResult<pb::UInt32> {
|
||||||
let mut search = if input.regex {
|
let mut search = if input.regex {
|
||||||
input.search
|
input.search
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
backend_proto::concatenate_searches_in::Separator,
|
||||||
decks::DeckID as DeckIDType,
|
decks::DeckID as DeckIDType,
|
||||||
err::Result,
|
err::Result,
|
||||||
notetype::NoteTypeID as NoteTypeIDType,
|
notetype::NoteTypeID as NoteTypeIDType,
|
||||||
search::parser::{parse, Node, PropertyKind, SearchNode, StateKind, TemplateKind},
|
search::parser::{parse, Node, PropertyKind, SearchNode, StateKind, TemplateKind},
|
||||||
};
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
/// Take an Anki-style search string and convert it into an equivalent
|
/// Take an Anki-style search string and convert it into an equivalent
|
||||||
/// search string with normalized syntax.
|
/// search string with normalized syntax.
|
||||||
|
@ -31,6 +33,26 @@ pub fn negate_search(input: &str) -> Result<String> {
|
||||||
write_node(&Not(Box::new(Group(nodes))))
|
write_node(&Not(Box::new(Group(nodes))))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take arbitrary Anki-style search strings and return their concatenation where they
|
||||||
|
/// are separated by the provided boolean operator.
|
||||||
|
/// Empty searches (whole collection) are left out.
|
||||||
|
pub fn concatenate_searches(sep: i32, searches: &[String]) -> Result<String> {
|
||||||
|
let bool_node = vec![if let Some(Separator::Or) = Separator::from_i32(sep) {
|
||||||
|
Node::Or
|
||||||
|
} else {
|
||||||
|
Node::And
|
||||||
|
}];
|
||||||
|
Ok(write_nodes(
|
||||||
|
searches
|
||||||
|
.iter()
|
||||||
|
.map(|s: &String| -> Result<Vec<Node>> { parse(s) })
|
||||||
|
.collect::<Result<Vec<Vec<Node>>>>()?
|
||||||
|
.iter()
|
||||||
|
.filter(|v| v[0] != Node::Search(SearchNode::WholeCollection))
|
||||||
|
.intersperse(&&bool_node)
|
||||||
|
.flat_map(|v| v.iter()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -67,9 +89,7 @@ fn write_search_node(node: &SearchNode) -> String {
|
||||||
NoteType(s) => quote(&format!("note:{}", s)),
|
NoteType(s) => 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) => quote(&format!("tag:{}", s)),
|
||||||
Duplicates { note_type_id, text } => {
|
Duplicates { note_type_id, text } => quote(&format!("dupes:{},{}", note_type_id, 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),
|
||||||
|
@ -89,7 +109,12 @@ fn quote(txt: &str) -> String {
|
||||||
|
|
||||||
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 re = if is_re { "re:" } else { "" };
|
let re = if is_re { "re:" } else { "" };
|
||||||
quote(&format!("{}:{}{}", field.replace(":", "\\:"), re, text))
|
let text = if !is_re && text.starts_with("re:") {
|
||||||
|
text.replacen(":", "\\:", 1)
|
||||||
|
} else {
|
||||||
|
text.to_string()
|
||||||
|
};
|
||||||
|
quote(&format!("{}:{}{}", field.replace(":", "\\:"), re, &text))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_template(template: &TemplateKind) -> String {
|
fn write_template(template: &TemplateKind) -> String {
|
||||||
|
@ -102,7 +127,7 @@ fn write_template(template: &TemplateKind) -> String {
|
||||||
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue