diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 62d2406cb..ea8a90a26 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -36,7 +36,7 @@ use crate::{ sched::timespan::{answer_button_time, time_span}, search::{ concatenate_searches, negate_search, normalize_search, replace_search_term, write_nodes, - BoolSeparator, Node, SearchNode, SortMode, StateKind, TemplateKind, + BoolSeparator, EaseKind, Node, SearchNode, SortMode, StateKind, TemplateKind, }, stats::studied_today, sync::{ @@ -295,11 +295,11 @@ impl From for Node<'_> { NamedFilter::AddedToday => Node::Search(SearchNode::AddedInDays(1)), NamedFilter::StudiedToday => Node::Search(SearchNode::Rated { days: 1, - ease: None, + ease: EaseKind::AnyAnswerButton, }), NamedFilter::AgainToday => Node::Search(SearchNode::Rated { days: 1, - ease: Some(1), + ease: EaseKind::AnswerButton(1), }), NamedFilter::New => Node::Search(SearchNode::State(StateKind::New)), NamedFilter::Learn => Node::Search(SearchNode::State(StateKind::Learning)), diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index 5eef7e86e..ef107a94a 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -5,7 +5,7 @@ mod sqlwriter; mod writer; pub use cards::SortMode; -pub use parser::{Node, PropertyKind, SearchNode, StateKind, TemplateKind}; +pub use parser::{EaseKind, Node, PropertyKind, SearchNode, StateKind, TemplateKind}; pub use writer::{ concatenate_searches, negate_search, normalize_search, replace_search_term, write_nodes, BoolSeparator, diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 6274914eb..4197e19ed 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -69,7 +69,7 @@ pub enum SearchNode<'a> { NoteType(Cow<'a, str>), Rated { days: u32, - ease: Option, + ease: EaseKind, }, Tag(Cow<'a, str>), Duplicates { @@ -118,6 +118,13 @@ pub enum TemplateKind<'a> { Name(Cow<'a, str>), } +#[derive(Debug, PartialEq, Clone)] +pub enum EaseKind { + AnswerButton(u8), + AnyAnswerButton, + ManualReschedule, +} + /// Parse the input string into a list of nodes. pub(super) fn parse(input: &str) -> Result> { let input = input.trim(); @@ -286,6 +293,7 @@ fn search_node_for_text_with_argument<'a>( "is" => parse_state(val)?, "flag" => parse_flag(val)?, "rated" => parse_rated(val)?, + "resched" => parse_resched(val)?, "dupe" => parse_dupes(val)?, "prop" => parse_prop(val)?, "re" => SearchNode::Regex(unescape_quotes(val)), @@ -350,7 +358,7 @@ fn parse_flag(s: &str) -> ParseResult> { } /// eg rated:3 or rated:10:2 -/// second arg must be between 0-4 +/// second arg must be between 1-4 fn parse_rated(val: &str) -> ParseResult> { let mut it = val.splitn(2, ':'); @@ -359,19 +367,31 @@ fn parse_rated(val: &str) -> ParseResult> { let ease = match it.next() { Some(v) => { - let n: u8 = v.parse()?; - if n < 5 { - Some(n) + let u: u8 = v.parse()?; + if (1..5).contains(&u) { + EaseKind::AnswerButton(u) } else { return Err(ParseError {}); } } - None => None, + None => EaseKind::AnyAnswerButton, }; Ok(SearchNode::Rated { days, ease }) } +/// eg resched:3 +fn parse_resched(val: &str) -> ParseResult> { + let mut it = val.splitn(1, ':'); + + let n: u32 = it.next().unwrap().parse()?; + let days = n.max(1).min(365); + + let ease = EaseKind::ManualReschedule; + + Ok(SearchNode::Rated { days, ease }) +} + /// eg dupes:1231,hello fn parse_dupes(val: &str) -> ParseResult { let mut it = val.splitn(2, ','); diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index caac139e9..dc39999d4 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use super::parser::{Node, PropertyKind, SearchNode, StateKind, TemplateKind}; +use super::parser::{EaseKind, Node, PropertyKind, SearchNode, StateKind, TemplateKind}; use crate::{ card::{CardQueue, CardType}, collection::Collection, @@ -144,7 +144,7 @@ impl SqlWriter<'_> { write!(self.sql, "c.did = {}", did).unwrap(); } SearchNode::NoteType(notetype) => self.write_note_type(&norm(notetype))?, - SearchNode::Rated { days, ease } => self.write_rated(*days, *ease)?, + SearchNode::Rated { days, ease } => self.write_rated(*days, ease)?, SearchNode::Tag(tag) => self.write_tag(&norm(tag))?, SearchNode::State(state) => self.write_state(state)?, @@ -214,20 +214,22 @@ impl SqlWriter<'_> { Ok(()) } - fn write_rated(&mut self, days: u32, ease: Option) -> Result<()> { + fn write_rated(&mut self, days: u32, ease: &EaseKind) -> Result<()> { let today_cutoff = self.col.timing_today()?.next_day_at; let target_cutoff_ms = (today_cutoff - 86_400 * i64::from(days)) * 1_000; write!( self.sql, "c.id in (select cid from revlog where id>{}", - target_cutoff_ms + target_cutoff_ms, ) .unwrap(); - if let Some(ease) = ease { - write!(self.sql, " and ease={})", ease).unwrap(); - } else { - write!(self.sql, ")").unwrap(); + + match ease { + EaseKind::AnswerButton(u) => write!(self.sql, " and ease = {})", u), + EaseKind::AnyAnswerButton => write!(self.sql, " and ease > 0)"), + EaseKind::ManualReschedule => write!(self.sql, " and ease = 0)"), } + .unwrap(); Ok(()) } @@ -717,19 +719,28 @@ mod test { assert_eq!( s(ctx, "rated:2").0, format!( - "(c.id in (select cid from revlog where id>{}))", + "(c.id in (select cid from revlog where id>{} and ease > 0))", (timing.next_day_at - (86_400 * 2)) * 1_000 ) ); assert_eq!( s(ctx, "rated:400:1").0, format!( - "(c.id in (select cid from revlog where id>{} and ease=1))", + "(c.id in (select cid from revlog where id>{} and ease = 1))", (timing.next_day_at - (86_400 * 365)) * 1_000 ) ); assert_eq!(s(ctx, "rated:0").0, s(ctx, "rated:1").0); + // resched + assert_eq!( + s(ctx, "resched:400").0, + format!( + "(c.id in (select cid from revlog where id>{} and ease = 0))", + (timing.next_day_at - (86_400 * 365)) * 1_000 + ) + ); + // props assert_eq!(s(ctx, "prop:lapses=3").0, "(lapses = 3)".to_string()); assert_eq!(s(ctx, "prop:ease>=2.5").0, "(factor >= 2500)".to_string()); diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index 6507f3be9..0b67224f7 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -5,7 +5,7 @@ use crate::{ decks::DeckID as DeckIDType, err::Result, notetype::NoteTypeID as NoteTypeIDType, - search::parser::{parse, Node, PropertyKind, SearchNode, StateKind, TemplateKind}, + search::parser::{parse, EaseKind, Node, PropertyKind, SearchNode, StateKind, TemplateKind}, }; use itertools::Itertools; use std::mem; @@ -154,10 +154,12 @@ fn write_template(template: &TemplateKind) -> String { } } -fn write_rated(days: &u32, ease: &Option) -> String { +fn write_rated(days: &u32, ease: &EaseKind) -> String { + use EaseKind::*; match ease { - Some(u) => format!("\"rated:{}:{}\"", days, u), - None => format!("\"rated:{}\"", days), + AnswerButton(n) => format!("\"rated:{}:{}\"", days, n), + AnyAnswerButton => format!("\"rated:{}\"", days), + ManualReschedule => format!("\"resched:{}\"", days), } }