Merge pull request #926 from hgiesel/ratedextension

Introduce "prop:rated" and "prop:resched"
This commit is contained in:
Damien Elmes 2021-01-18 09:09:53 +10:00 committed by GitHub
commit 4c30f5506a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 114 additions and 39 deletions

View File

@ -17,7 +17,7 @@ search-invalid-argument = `{ $term }` was given an invalid argument '`{ $argumen
search-invalid-flag = `flag:` must be followed by a valid flag number: `1` (red), `2` (orange), `3` (green), `4` (blue) or `0` (no flag).
search-invalid-followed-by-positive-days = `{ $term }` must be followed by a positive number of days.
search-invalid-rated-days = `rated:` must be followed by a positive number of days.
search-invalid-rated-ease = `rated:{ $val }:` must be followed by `1` (again), `2` (hard), `3` (good) or `4` (easy).
search-invalid-rated-ease = `{ $val }:` must be followed by `1` (again), `2` (hard), `3` (good) or `4` (easy).
search-invalid-prop-operator = `prop:{ $val }` must be followed by one of the comparison operators: `=`, `!=`, `<`, `>`, `<=` or `>=`.
search-invalid-prop-float = `prop:{ $val }` must be followed by a decimal number.
search-invalid-prop-integer = `prop:{ $val }` must be followed by a whole number.

View File

@ -87,6 +87,7 @@ pub enum PropertyKind {
Lapses(u32),
Ease(f32),
Position(u32),
Rated(i32, EaseKind),
}
#[derive(Debug, PartialEq, Clone)]
@ -350,9 +351,9 @@ fn parse_flag(s: &str) -> ParseResult<SearchNode> {
/// eg resched:3
fn parse_resched(s: &str) -> ParseResult<SearchNode> {
if let Ok(d) = s.parse::<u32>() {
if let Ok(days) = s.parse::<u32>() {
Ok(SearchNode::Rated {
days: d.max(1).min(365),
days,
ease: EaseKind::ManualReschedule,
})
} else {
@ -369,6 +370,8 @@ fn parse_prop(s: &str) -> ParseResult<SearchNode> {
tag("lapses"),
tag("ease"),
tag("pos"),
tag("rated"),
tag("resched"),
))(s)
.map_err(|_| parse_failure(s, FailKind::InvalidPropProperty(s.into())))?;
@ -400,6 +403,54 @@ fn parse_prop(s: &str) -> ParseResult<SearchNode> {
FailKind::InvalidPropInteger(format!("{}{}", prop, operator)),
));
}
} else if prop == "rated" {
let mut it = num.splitn(2, ':');
let days: i32 = if let Ok(i) = it.next().unwrap().parse::<i32>() {
i.min(0)
} else {
return Err(parse_failure(
s,
FailKind::InvalidPropInteger(format!("{}{}", prop, operator)),
));
};
let ease = match it.next() {
Some(v) => {
if let Ok(u) = v.parse::<u8>() {
if (1..5).contains(&u) {
EaseKind::AnswerButton(u)
} else {
return Err(parse_failure(
s,
FailKind::InvalidRatedEase(format!(
"prop:{}{}{}",
prop,
operator,
days.to_string()
)),
));
}
} else {
return Err(parse_failure(
s,
FailKind::InvalidPropInteger(format!("{}{}", prop, operator)),
));
}
}
None => EaseKind::AnyAnswerButton,
};
PropertyKind::Rated(days, ease)
} else if prop == "resched" {
if let Ok(days) = num.parse::<i32>() {
PropertyKind::Rated(days.min(0), EaseKind::ManualReschedule)
} else {
return Err(parse_failure(
s,
FailKind::InvalidPropInteger(format!("{}{}", prop, operator)),
));
}
} else if let Ok(u) = num.parse::<u32>() {
match prop {
"ivl" => PropertyKind::Interval(u),
@ -443,8 +494,8 @@ fn parse_edited(s: &str) -> ParseResult<SearchNode> {
/// second arg must be between 1-4
fn parse_rated(s: &str) -> ParseResult<SearchNode> {
let mut it = s.splitn(2, ':');
if let Ok(d) = it.next().unwrap().parse::<u32>() {
let days = d.max(1).min(365);
if let Ok(days) = it.next().unwrap().parse::<u32>() {
let days = days.max(1);
let ease = if let Some(tail) = it.next() {
if let Ok(u) = tail.parse::<u8>() {
if u > 0 && u < 5 {
@ -452,13 +503,13 @@ fn parse_rated(s: &str) -> ParseResult<SearchNode> {
} else {
return Err(parse_failure(
s,
FailKind::InvalidRatedEase(days.to_string()),
FailKind::InvalidRatedEase(format!("rated:{}", days.to_string())),
));
}
} else {
return Err(parse_failure(
s,
FailKind::InvalidRatedEase(days.to_string()),
FailKind::InvalidRatedEase(format!("rated:{}", days.to_string())),
));
}
} else {
@ -872,10 +923,10 @@ mod test {
assert_err_kind("rated:", InvalidRatedDays);
assert_err_kind("rated:foo", InvalidRatedDays);
assert_err_kind("rated:1:", InvalidRatedEase("1".to_string()));
assert_err_kind("rated:2:-1", InvalidRatedEase("2".to_string()));
assert_err_kind("rated:3:1.1", InvalidRatedEase("3".to_string()));
assert_err_kind("rated:0:foo", InvalidRatedEase("1".to_string()));
assert_err_kind("rated:1:", InvalidRatedEase("rated:1".to_string()));
assert_err_kind("rated:2:-1", InvalidRatedEase("rated:2".to_string()));
assert_err_kind("rated:3:1.1", InvalidRatedEase("rated:3".to_string()));
assert_err_kind("rated:0:foo", InvalidRatedEase("rated:1".to_string()));
assert_err_kind("resched:", FailKind::InvalidResched);
assert_err_kind("resched:-1", FailKind::InvalidResched);

View File

@ -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(">", -i64::from(*days), ease)?,
SearchNode::Tag(tag) => self.write_tag(&norm(tag))?,
SearchNode::State(state) => self.write_state(state)?,
@ -214,14 +214,32 @@ impl SqlWriter<'_> {
Ok(())
}
fn write_rated(&mut self, days: u32, ease: &EaseKind) -> Result<()> {
fn write_rated(&mut self, op: &str, days: i64, 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,
)
let target_cutoff_ms = (today_cutoff + 86_400 * days) * 1_000;
let day_before_cutoff_ms = (today_cutoff + 86_400 * (days - 1)) * 1_000;
write!(self.sql, "c.id in (select cid from revlog where id").unwrap();
match op {
">" => write!(self.sql, " >= {}", target_cutoff_ms),
">=" => write!(self.sql, " >= {}", day_before_cutoff_ms),
"<" => write!(self.sql, " < {}", day_before_cutoff_ms),
"<=" => write!(self.sql, " < {}", target_cutoff_ms),
"=" => write!(
self.sql,
" between {} and {}",
day_before_cutoff_ms,
target_cutoff_ms - 1
),
"!=" => write!(
self.sql,
" not between {} and {}",
day_before_cutoff_ms,
target_cutoff_ms - 1
),
_ => unreachable!("unexpected op"),
}
.unwrap();
match ease {
@ -255,25 +273,25 @@ impl SqlWriter<'_> {
previewrepeat = CardQueue::PreviewRepeat as i8,
cutoff = timing.next_day_at,
days = days
)
).unwrap()
}
PropertyKind::Position(pos) => {
write!(
self.sql,
"(c.type = {t} and due {op} {pos})",
t = CardType::New as u8,
op = op,
pos = pos
)
}
PropertyKind::Interval(ivl) => write!(self.sql, "ivl {} {}", op, ivl),
PropertyKind::Reps(reps) => write!(self.sql, "reps {} {}", op, reps),
PropertyKind::Lapses(days) => write!(self.sql, "lapses {} {}", op, days),
PropertyKind::Position(pos) => write!(
self.sql,
"(c.type = {t} and due {op} {pos})",
t = CardType::New as u8,
op = op,
pos = pos
)
.unwrap(),
PropertyKind::Interval(ivl) => write!(self.sql, "ivl {} {}", op, ivl).unwrap(),
PropertyKind::Reps(reps) => write!(self.sql, "reps {} {}", op, reps).unwrap(),
PropertyKind::Lapses(days) => write!(self.sql, "lapses {} {}", op, days).unwrap(),
PropertyKind::Ease(ease) => {
write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32)
write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32).unwrap()
}
PropertyKind::Rated(days, ease) => self.write_rated(op, i64::from(*days), ease)?,
}
.unwrap();
Ok(())
}
@ -719,15 +737,15 @@ mod test {
assert_eq!(
s(ctx, "rated:2").0,
format!(
"(c.id in (select cid from revlog where id>{} and ease > 0))",
"(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))",
(timing.next_day_at - (86_400 * 365)) * 1_000
"(c.id in (select cid from revlog where id >= {} and ease = 1))",
(timing.next_day_at - (86_400 * 400)) * 1_000
)
);
assert_eq!(s(ctx, "rated:0").0, s(ctx, "rated:1").0);
@ -736,8 +754,8 @@ mod test {
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
"(c.id in (select cid from revlog where id >= {} and ease = 0))",
(timing.next_day_at - (86_400 * 400)) * 1_000
)
);
@ -752,6 +770,7 @@ mod test {
cutoff = timing.next_day_at
)
);
assert_eq!(s(ctx, "prop:rated>-5:3").0, s(ctx, "rated:5:3").0);
// note types by name
assert_eq!(

View File

@ -195,6 +195,11 @@ fn write_property(operator: &str, kind: &PropertyKind) -> String {
Lapses(u) => format!("\"prop:lapses{}{}\"", operator, u),
Ease(f) => format!("\"prop:ease{}{}\"", operator, f),
Position(u) => format!("\"prop:pos{}{}\"", operator, u),
Rated(u, ease) => match ease {
EaseKind::AnswerButton(val) => format!("\"prop:rated{}{}:{}\"", operator, u, val),
EaseKind::AnyAnswerButton => format!("\"prop:rated{}{}\"", operator, u),
EaseKind::ManualReschedule => format!("\"prop:resched{}{}\"", operator, u),
},
}
}