Provide filter searches through backend

This commit is contained in:
RumovZ 2021-01-09 10:50:08 +01:00
parent fe4da25e15
commit 9ef691c06f
6 changed files with 94 additions and 10 deletions

View File

@ -84,6 +84,7 @@ service BackendService {
// searching // searching
rpc FilterToSearch (FilterToSearchIn) returns (String);
rpc NormalizeSearch (String) returns (String); rpc NormalizeSearch (String) returns (String);
rpc SearchCards (SearchCardsIn) returns (SearchCardsOut); rpc SearchCards (SearchCardsIn) returns (SearchCardsOut);
rpc SearchNotes (SearchNotesIn) returns (SearchNotesOut); rpc SearchNotes (SearchNotesIn) returns (SearchNotesOut);
@ -756,6 +757,35 @@ message BuiltinSearchOrder {
bool reverse = 2; bool reverse = 2;
} }
message FilterToSearchIn {
enum NamedFilter {
WHOLE_COLLECTION = 0;
CURRENT_DECK = 1;
ADDED_TODAY = 2;
STUDIED_TODAY = 3;
AGAIN_TODAY = 4;
NEW = 5;
LEARN = 6;
REVIEW = 7;
DUE = 8;
SUSPENDED = 9;
BURIED = 10;
RED_FLAG = 11;
ORANGE_FLAG = 12;
GREEN_FLAG = 13;
BLUE_FLAG = 14;
NO_FLAG = 15;
ANY_FLAG = 16;
}
oneof filter {
NamedFilter name = 1;
string tag = 2;
string deck = 3;
string note = 4;
uint32 template = 5;
}
}
message ConcatenateSearchesIn { message ConcatenateSearchesIn {
enum Separator { enum Separator {
AND = 0; AND = 0;

View File

@ -36,8 +36,8 @@ use crate::{
sched::new::NewCardSortOrder, sched::new::NewCardSortOrder,
sched::timespan::{answer_button_time, time_span}, sched::timespan::{answer_button_time, time_span},
search::{ search::{
concatenate_searches, negate_search, normalize_search, replace_search_term, BoolSeparator, concatenate_searches, negate_search, normalize_search, replace_search_term, write_nodes,
SortMode, BoolSeparator, Node, SearchNode, SortMode, StateKind, TemplateKind,
}, },
stats::studied_today, stats::studied_today,
sync::{ sync::{
@ -45,7 +45,7 @@ use crate::{
SyncActionRequired, SyncAuth, SyncMeta, SyncOutput, SyncStage, SyncActionRequired, SyncAuth, SyncMeta, SyncOutput, SyncStage,
}, },
template::RenderedNode, template::RenderedNode,
text::{extract_av_tags, strip_av_tags, AVTag}, text::{escape_anki_wildcards, extract_av_tags, strip_av_tags, AVTag},
timestamp::TimestampSecs, timestamp::TimestampSecs,
types::Usn, types::Usn,
}; };
@ -277,6 +277,46 @@ impl From<pb::DeckConfigId> for DeckConfID {
} }
} }
impl From<pb::FilterToSearchIn> for Node<'_> {
fn from(msg: pb::FilterToSearchIn) -> Self {
use pb::filter_to_search_in::Filter as F;
use pb::filter_to_search_in::NamedFilter as NF;
use Node as N;
use SearchNode as SN;
match msg.filter.unwrap_or(F::Name(NF::WholeCollection as i32)) {
F::Name(name) => match NF::from_i32(name).unwrap_or(NF::WholeCollection) {
NF::WholeCollection => N::Search(SN::WholeCollection),
NF::CurrentDeck => N::Search(SN::Deck("current".into())),
NF::AddedToday => N::Search(SN::AddedInDays(1)),
NF::StudiedToday => N::Search(SN::Rated {
days: 1,
ease: None,
}),
NF::AgainToday => N::Search(SN::Rated {
days: 1,
ease: Some(1),
}),
NF::New => N::Search(SN::State(StateKind::New)),
NF::Learn => N::Search(SN::State(StateKind::Learning)),
NF::Review => N::Search(SN::State(StateKind::Review)),
NF::Due => N::Search(SN::State(StateKind::Due)),
NF::Suspended => N::Search(SN::State(StateKind::Suspended)),
NF::Buried => N::Search(SN::State(StateKind::Buried)),
NF::RedFlag => N::Search(SN::Flag(1)),
NF::OrangeFlag => N::Search(SN::Flag(2)),
NF::GreenFlag => N::Search(SN::Flag(3)),
NF::BlueFlag => N::Search(SN::Flag(4)),
NF::NoFlag => N::Search(SN::Flag(0)),
NF::AnyFlag => N::Not(Box::new(N::Search(SN::Flag(0)))),
},
F::Tag(s) => N::Search(SN::Tag(escape_anki_wildcards(&s).into_owned().into())),
F::Deck(s) => N::Search(SN::Deck(escape_anki_wildcards(&s).into_owned().into())),
F::Note(s) => N::Search(SN::NoteType(escape_anki_wildcards(&s).into_owned().into())),
F::Template(u) => N::Search(SN::CardTemplate(TemplateKind::Ordinal(u as u16))),
}
}
}
impl From<BoolSeparatorProto> for BoolSeparator { impl From<BoolSeparatorProto> for BoolSeparator {
fn from(sep: BoolSeparatorProto) -> Self { fn from(sep: BoolSeparatorProto) -> Self {
match sep { match sep {
@ -408,6 +448,10 @@ impl BackendService for Backend {
// searching // searching
//----------------------------------------------- //-----------------------------------------------
fn filter_to_search(&self, input: pb::FilterToSearchIn) -> Result<pb::String> {
Ok(write_nodes(&[input.into()]).into())
}
fn normalize_search(&self, input: pb::String) -> Result<pb::String> { fn normalize_search(&self, input: pb::String) -> Result<pb::String> {
Ok(normalize_search(&input.val)?.into()) Ok(normalize_search(&input.val)?.into())
} }

View File

@ -5,6 +5,8 @@ mod sqlwriter;
mod writer; mod writer;
pub use cards::SortMode; pub use cards::SortMode;
pub use parser::{Node, PropertyKind, SearchNode, StateKind, TemplateKind};
pub use writer::{ pub use writer::{
concatenate_searches, negate_search, normalize_search, replace_search_term, BoolSeparator, concatenate_searches, negate_search, normalize_search, replace_search_term, write_nodes,
BoolSeparator,
}; };

View File

@ -42,7 +42,7 @@ impl<I> From<nom::Err<(I, ErrorKind)>> for ParseError {
type ParseResult<T> = std::result::Result<T, ParseError>; type ParseResult<T> = std::result::Result<T, ParseError>;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub(super) enum Node<'a> { pub enum Node<'a> {
And, And,
Or, Or,
Not(Box<Node<'a>>), Not(Box<Node<'a>>),
@ -51,7 +51,7 @@ pub(super) enum Node<'a> {
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub(super) enum SearchNode<'a> { pub enum SearchNode<'a> {
// text without a colon // text without a colon
UnqualifiedText(Cow<'a, str>), UnqualifiedText(Cow<'a, str>),
// foo:bar, where foo doesn't match a term below // foo:bar, where foo doesn't match a term below
@ -91,7 +91,7 @@ pub(super) enum SearchNode<'a> {
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub(super) enum PropertyKind { pub enum PropertyKind {
Due(i32), Due(i32),
Interval(u32), Interval(u32),
Reps(u32), Reps(u32),
@ -101,7 +101,7 @@ pub(super) enum PropertyKind {
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub(super) enum StateKind { pub enum StateKind {
New, New,
Review, Review,
Learning, Learning,
@ -113,7 +113,7 @@ pub(super) enum StateKind {
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub(super) enum TemplateKind<'a> { pub enum TemplateKind<'a> {
Ordinal(u16), Ordinal(u16),
Name(Cow<'a, str>), Name(Cow<'a, str>),
} }

View File

@ -87,7 +87,7 @@ pub fn replace_search_term(search: &str, replacement: &str) -> Result<String> {
Ok(write_nodes(&nodes)) Ok(write_nodes(&nodes))
} }
fn write_nodes<'a, I>(nodes: I) -> String pub fn write_nodes<'a, I>(nodes: I) -> String
where where
I: IntoIterator<Item = &'a Node<'a>>, I: IntoIterator<Item = &'a Node<'a>>,
{ {

View File

@ -331,6 +331,14 @@ pub(crate) fn escape_sql(txt: &str) -> Cow<str> {
RE.replace_all(&txt, r"\$0") RE.replace_all(&txt, r"\$0")
} }
/// Escape Anki wildcards and the backslash for escaping them: \*_
pub(crate) fn escape_anki_wildcards(txt: &str) -> Cow<str> {
lazy_static! {
static ref RE: Regex = Regex::new(r"[\\*_]").unwrap();
}
RE.replace_all(&txt, r"\$0")
}
/// Compare text with a possible glob, folding case. /// Compare text with a possible glob, folding case.
pub(crate) fn matches_glob(text: &str, search: &str) -> bool { pub(crate) fn matches_glob(text: &str, search: &str) -> bool {
if is_glob(search) { if is_glob(search) {