Provide filter searches through backend
This commit is contained in:
parent
fe4da25e15
commit
9ef691c06f
@ -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;
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
@ -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>),
|
||||||
}
|
}
|
||||||
|
@ -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>>,
|
||||||
{
|
{
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user