Merge pull request #926 from hgiesel/ratedextension
Introduce "prop:rated" and "prop:resched"
This commit is contained in:
commit
4c30f5506a
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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!(
|
||||
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user