From 7e58660aabd8019d6c35a49dabf1c7c767496877 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sat, 9 Jan 2021 14:04:57 +0100 Subject: [PATCH 1/7] Modify default behavior of rated searches to exclude manual --- rslib/src/search/parser.rs | 36 ++++++++++++++++++++++++++++------- rslib/src/search/sqlwriter.rs | 16 ++++++---------- rslib/src/search/writer.rs | 10 ++++++---- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 6274914eb..52d00b8d3 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,25 @@ pub enum TemplateKind<'a> { Name(Cow<'a, str>), } +#[derive(Debug, PartialEq, Clone)] +pub(super) enum EaseKind { + Rated(u8), + Reviewed, + All, +} + +impl std::fmt::Display for EaseKind { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use EaseKind::*; + + match self { + Rated(u) => write!(f, " and ease = {}", u), + Reviewed => write!(f, " and ease in (1, 2, 3, 4)"), + All => write!(f, ""), + } + } +} + /// Parse the input string into a list of nodes. pub(super) fn parse(input: &str) -> Result> { let input = input.trim(); @@ -359,14 +378,17 @@ fn parse_rated(val: &str) -> ParseResult> { let ease = match it.next() { Some(v) => { - let n: u8 = v.parse()?; - if n < 5 { - Some(n) - } else { - return Err(ParseError {}); + let c: char = v.parse().unwrap(); + match c { + '0' | '1' | '2' | '3' | '4' => { + let n = c.to_digit(10).unwrap() as u8; + EaseKind::Rated(n) + } + 'a' => EaseKind::All, + _ => return Err(ParseError {}), } } - None => None, + None => EaseKind::Reviewed, }; Ok(SearchNode::Rated { days, ease }) diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index caac139e9..3f447dea3 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::{Node, PropertyKind, SearchNode, StateKind, TemplateKind, EaseKind}; 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,16 @@ 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 + "c.id in (select cid from revlog where id>{}{})", + target_cutoff_ms, + ease, ) .unwrap(); - if let Some(ease) = ease { - write!(self.sql, " and ease={})", ease).unwrap(); - } else { - write!(self.sql, ")").unwrap(); - } Ok(()) } diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index 6507f3be9..61e5ee850 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, Node, PropertyKind, SearchNode, StateKind, TemplateKind, EaseKind}, }; 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), + Rated(n) => format!("\"rated:{}:{}\"", days, n), + Reviewed => format!("\"rated:{}\"", days), + All => format!("\"rated:{}:a\"", days), } } From cbfe14ef4fffd42bd2a6c3ecefc459abc456bdbe Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sat, 9 Jan 2021 15:32:50 +0100 Subject: [PATCH 2/7] Introduce "resched:n" instead of 0 and a flag for "rated" --- rslib/src/search/parser.rs | 27 +++++++++++++++++---------- rslib/src/search/writer.rs | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 52d00b8d3..6dd147140 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -122,7 +122,7 @@ pub enum TemplateKind<'a> { pub(super) enum EaseKind { Rated(u8), Reviewed, - All, + Manually, } impl std::fmt::Display for EaseKind { @@ -132,7 +132,7 @@ impl std::fmt::Display for EaseKind { match self { Rated(u) => write!(f, " and ease = {}", u), Reviewed => write!(f, " and ease in (1, 2, 3, 4)"), - All => write!(f, ""), + Manually => write!(f, " and ease = 0"), } } } @@ -305,6 +305,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)), @@ -378,14 +379,11 @@ fn parse_rated(val: &str) -> ParseResult> { let ease = match it.next() { Some(v) => { - let c: char = v.parse().unwrap(); - match c { - '0' | '1' | '2' | '3' | '4' => { - let n = c.to_digit(10).unwrap() as u8; - EaseKind::Rated(n) - } - 'a' => EaseKind::All, - _ => return Err(ParseError {}), + let u: u8 = v.parse().unwrap(); + if (1..5).contains(&u) { + EaseKind::Rated(u) + } else { + return Err(ParseError {}) } } None => EaseKind::Reviewed, @@ -394,6 +392,15 @@ fn parse_rated(val: &str) -> ParseResult> { Ok(SearchNode::Rated { days, ease }) } +/// eg resched:3 +fn parse_resched(val: &str) -> ParseResult> { + let mut it = val.splitn(1, ':'); + let days = it.next().unwrap().parse()?; + let ease = EaseKind::Manually; + + 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/writer.rs b/rslib/src/search/writer.rs index 61e5ee850..536fe39bd 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -159,7 +159,7 @@ fn write_rated(days: &u32, ease: &EaseKind) -> String { match ease { Rated(n) => format!("\"rated:{}:{}\"", days, n), Reviewed => format!("\"rated:{}\"", days), - All => format!("\"rated:{}:a\"", days), + Manually => format!("\"resched:{}\"", days), } } From 7a7152fe2762ebeca907fcfd5a22f5ffafb46b5b Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 11 Jan 2021 17:10:17 +0100 Subject: [PATCH 3/7] Remove EaseKind impl in favor of transforming to sql in function --- rslib/src/backend/mod.rs | 6 +++--- rslib/src/search/mod.rs | 2 +- rslib/src/search/parser.rs | 16 ++-------------- rslib/src/search/sqlwriter.rs | 12 +++++++++--- rslib/src/search/writer.rs | 2 +- 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index f67bb77b8..d253519ee 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -37,7 +37,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::{ @@ -292,11 +292,11 @@ impl From for Node<'_> { NamedFilter::AddedToday => Node::Search(SearchNode::AddedInDays(1)), NamedFilter::StudiedToday => Node::Search(SearchNode::Rated { days: 1, - ease: None, + ease: EaseKind::Reviewed, }), NamedFilter::AgainToday => Node::Search(SearchNode::Rated { days: 1, - ease: Some(1), + ease: EaseKind::Rated(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 6dd147140..c58e90b90 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -119,24 +119,12 @@ pub enum TemplateKind<'a> { } #[derive(Debug, PartialEq, Clone)] -pub(super) enum EaseKind { +pub enum EaseKind { Rated(u8), Reviewed, Manually, } -impl std::fmt::Display for EaseKind { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use EaseKind::*; - - match self { - Rated(u) => write!(f, " and ease = {}", u), - Reviewed => write!(f, " and ease in (1, 2, 3, 4)"), - Manually => write!(f, " and ease = 0"), - } - } -} - /// Parse the input string into a list of nodes. pub(super) fn parse(input: &str) -> Result> { let input = input.trim(); @@ -383,7 +371,7 @@ fn parse_rated(val: &str) -> ParseResult> { if (1..5).contains(&u) { EaseKind::Rated(u) } else { - return Err(ParseError {}) + return Err(ParseError {}); } } None => EaseKind::Reviewed, diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 3f447dea3..2b5facf70 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, EaseKind}; +use super::parser::{EaseKind, Node, PropertyKind, SearchNode, StateKind, TemplateKind}; use crate::{ card::{CardQueue, CardType}, collection::Collection, @@ -219,12 +219,18 @@ impl SqlWriter<'_> { 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>{}{})", + "c.id in (select cid from revlog where id>{}", target_cutoff_ms, - ease, ) .unwrap(); + match ease { + EaseKind::Rated(u) => write!(self.sql, " and ease = {})", u), + EaseKind::Reviewed => write!(self.sql, " and ease in (1, 2, 3, 4))"), + EaseKind::Manually => write!(self.sql, " and ease = 0)"), + } + .unwrap(); + Ok(()) } diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index 536fe39bd..f58143df0 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, EaseKind}, + search::parser::{parse, EaseKind, Node, PropertyKind, SearchNode, StateKind, TemplateKind}, }; use itertools::Itertools; use std::mem; From 5eb2c7b271716a4999d178d9563ed217cb1c5b5d Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 11 Jan 2021 17:18:40 +0100 Subject: [PATCH 4/7] Satisfy rslib unit tests --- rslib/src/search/sqlwriter.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 2b5facf70..3b63c2472 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -226,7 +226,7 @@ impl SqlWriter<'_> { match ease { EaseKind::Rated(u) => write!(self.sql, " and ease = {})", u), - EaseKind::Reviewed => write!(self.sql, " and ease in (1, 2, 3, 4))"), + EaseKind::Reviewed => write!(self.sql, " and ease between 1 and 4)"), EaseKind::Manually => write!(self.sql, " and ease = 0)"), } .unwrap(); @@ -719,14 +719,14 @@ 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 between 1 and 4))", (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 ) ); From 81d851610ef797a972d92e7e3ed2420844a2f633 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 11 Jan 2021 17:25:42 +0100 Subject: [PATCH 5/7] Coerce resched:days as well --- rslib/src/search/parser.rs | 7 +++++-- rslib/src/search/sqlwriter.rs | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index c58e90b90..fe0b6b3cc 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -358,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, ':'); @@ -383,7 +383,10 @@ fn parse_rated(val: &str) -> ParseResult> { /// eg resched:3 fn parse_resched(val: &str) -> ParseResult> { let mut it = val.splitn(1, ':'); - let days = it.next().unwrap().parse()?; + + let n: u32 = it.next().unwrap().parse()?; + let days = n.max(1).min(365); + let ease = EaseKind::Manually; Ok(SearchNode::Rated { days, ease }) diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 3b63c2472..2dafd9386 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -732,6 +732,15 @@ mod test { ); 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()); From c0ec21b139e1b6248abb7e2375d76f518a4baf93 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 13 Jan 2021 11:41:48 +0100 Subject: [PATCH 6/7] Rename EaseKind values --- rslib/src/backend/mod.rs | 4 ++-- rslib/src/search/parser.rs | 14 +++++++------- rslib/src/search/sqlwriter.rs | 6 +++--- rslib/src/search/writer.rs | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index d253519ee..abfebb75e 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -292,11 +292,11 @@ impl From for Node<'_> { NamedFilter::AddedToday => Node::Search(SearchNode::AddedInDays(1)), NamedFilter::StudiedToday => Node::Search(SearchNode::Rated { days: 1, - ease: EaseKind::Reviewed, + ease: EaseKind::AnyAnswerButton, }), NamedFilter::AgainToday => Node::Search(SearchNode::Rated { days: 1, - ease: EaseKind::Rated(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/parser.rs b/rslib/src/search/parser.rs index fe0b6b3cc..4197e19ed 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -120,9 +120,9 @@ pub enum TemplateKind<'a> { #[derive(Debug, PartialEq, Clone)] pub enum EaseKind { - Rated(u8), - Reviewed, - Manually, + AnswerButton(u8), + AnyAnswerButton, + ManualReschedule, } /// Parse the input string into a list of nodes. @@ -367,14 +367,14 @@ fn parse_rated(val: &str) -> ParseResult> { let ease = match it.next() { Some(v) => { - let u: u8 = v.parse().unwrap(); + let u: u8 = v.parse()?; if (1..5).contains(&u) { - EaseKind::Rated(u) + EaseKind::AnswerButton(u) } else { return Err(ParseError {}); } } - None => EaseKind::Reviewed, + None => EaseKind::AnyAnswerButton, }; Ok(SearchNode::Rated { days, ease }) @@ -387,7 +387,7 @@ fn parse_resched(val: &str) -> ParseResult> { let n: u32 = it.next().unwrap().parse()?; let days = n.max(1).min(365); - let ease = EaseKind::Manually; + let ease = EaseKind::ManualReschedule; Ok(SearchNode::Rated { days, ease }) } diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 2dafd9386..9114a9110 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -225,9 +225,9 @@ impl SqlWriter<'_> { .unwrap(); match ease { - EaseKind::Rated(u) => write!(self.sql, " and ease = {})", u), - EaseKind::Reviewed => write!(self.sql, " and ease between 1 and 4)"), - EaseKind::Manually => write!(self.sql, " and ease = 0)"), + EaseKind::AnswerButton(u) => write!(self.sql, " and ease = {})", u), + EaseKind::AnyAnswerButton => write!(self.sql, " and ease between 1 and 4)"), + EaseKind::ManualReschedule => write!(self.sql, " and ease = 0)"), } .unwrap(); diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index f58143df0..0b67224f7 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -157,9 +157,9 @@ fn write_template(template: &TemplateKind) -> String { fn write_rated(days: &u32, ease: &EaseKind) -> String { use EaseKind::*; match ease { - Rated(n) => format!("\"rated:{}:{}\"", days, n), - Reviewed => format!("\"rated:{}\"", days), - Manually => format!("\"resched:{}\"", days), + AnswerButton(n) => format!("\"rated:{}:{}\"", days, n), + AnyAnswerButton => format!("\"rated:{}\"", days), + ManualReschedule => format!("\"resched:{}\"", days), } } From 9f7170ac0f18286817c51bf09dcb6c326bee0c9a Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 13 Jan 2021 11:44:54 +0100 Subject: [PATCH 7/7] Change "between 1 and 4" to "> 0" --- rslib/src/search/sqlwriter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 9114a9110..dc39999d4 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -226,7 +226,7 @@ impl SqlWriter<'_> { match ease { EaseKind::AnswerButton(u) => write!(self.sql, " and ease = {})", u), - EaseKind::AnyAnswerButton => write!(self.sql, " and ease between 1 and 4)"), + EaseKind::AnyAnswerButton => write!(self.sql, " and ease > 0)"), EaseKind::ManualReschedule => write!(self.sql, " and ease = 0)"), } .unwrap(); @@ -719,7 +719,7 @@ mod test { assert_eq!( s(ctx, "rated:2").0, format!( - "(c.id in (select cid from revlog where id>{} and ease between 1 and 4))", + "(c.id in (select cid from revlog where id>{} and ease > 0))", (timing.next_day_at - (86_400 * 2)) * 1_000 ) );