simplify write_props() and associated translations

This is a work in progress; see associated PR discussion to follow.
This commit is contained in:
Damien Elmes 2021-01-18 10:41:03 +10:00
parent a4ec467284
commit 0b83efb63e
3 changed files with 78 additions and 96 deletions

View File

@ -22,6 +22,7 @@ search-invalid-prop-operator = `prop:{ $val }` must be followed by one of the co
search-invalid-prop-float = `prop:{ $val }` must be followed by a decimal number. 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. search-invalid-prop-integer = `prop:{ $val }` must be followed by a whole number.
search-invalid-prop-unsigned = `prop:{ $val }` must be followed by a non-negative whole number. search-invalid-prop-unsigned = `prop:{ $val }` must be followed by a non-negative whole number.
search-invalid-number = `{ $val }`: invalid number provided
search-invalid-did = `did:` must be followed by a valid deck id. search-invalid-did = `did:` must be followed by a valid deck id.
search-invalid-mid = `mid:` must be followed by a note type id. search-invalid-mid = `mid:` must be followed by a note type id.
search-invalid-other = please check for typing mistakes. search-invalid-other = please check for typing mistakes.

View File

@ -163,6 +163,9 @@ impl AnkiError {
SearchErrorKind::InvalidRatedEase(ctx) => i18n SearchErrorKind::InvalidRatedEase(ctx) => i18n
.trn(TR::SearchInvalidRatedEase, tr_strs!["val"=>(ctx)]) .trn(TR::SearchInvalidRatedEase, tr_strs!["val"=>(ctx)])
.into(), .into(),
SearchErrorKind::InvalidNumber(ctx) => i18n
.trn(TR::SearchInvalidNumber, tr_strs!["val"=>(ctx)])
.into(),
SearchErrorKind::InvalidResched => i18n SearchErrorKind::InvalidResched => i18n
.trn( .trn(
TR::SearchInvalidFollowedByPositiveDays, TR::SearchInvalidFollowedByPositiveDays,
@ -454,6 +457,7 @@ pub enum SearchErrorKind {
InvalidPropFloat(String), InvalidPropFloat(String),
InvalidPropInteger(String), InvalidPropInteger(String),
InvalidPropUnsigned(String), InvalidPropUnsigned(String),
InvalidNumber(String),
InvalidDid, InvalidDid,
InvalidMid, InvalidMid,
Regex(String), Regex(String),

View File

@ -17,7 +17,11 @@ use nom::{
sequence::{preceded, separated_pair}, sequence::{preceded, separated_pair},
}; };
use regex::{Captures, Regex}; use regex::{Captures, Regex};
use std::borrow::Cow; use std::{
borrow::Cow,
num::{ParseFloatError, ParseIntError},
str::FromStr,
};
type IResult<'a, O> = std::result::Result<(&'a str, O), nom::Err<ParseError<'a>>>; type IResult<'a, O> = std::result::Result<(&'a str, O), nom::Err<ParseError<'a>>>;
type ParseResult<'a, O> = std::result::Result<O, nom::Err<ParseError<'a>>>; type ParseResult<'a, O> = std::result::Result<O, nom::Err<ParseError<'a>>>;
@ -362,7 +366,7 @@ fn parse_resched(s: &str) -> ParseResult<SearchNode> {
} }
/// eg prop:ivl>3, prop:ease!=2.5 /// eg prop:ivl>3, prop:ease!=2.5
fn parse_prop(s: &str) -> ParseResult<SearchNode> { fn parse_prop(prop_clause: &str) -> ParseResult<SearchNode> {
let (tail, prop) = alt::<_, _, ParseError, _>(( let (tail, prop) = alt::<_, _, ParseError, _>((
tag("ivl"), tag("ivl"),
tag("due"), tag("due"),
@ -372,8 +376,13 @@ fn parse_prop(s: &str) -> ParseResult<SearchNode> {
tag("pos"), tag("pos"),
tag("rated"), tag("rated"),
tag("resched"), tag("resched"),
))(s) ))(prop_clause)
.map_err(|_| parse_failure(s, FailKind::InvalidPropProperty(s.into())))?; .map_err(|_| {
parse_failure(
prop_clause,
FailKind::InvalidPropProperty(prop_clause.into()),
)
})?;
let (num, operator) = alt::<_, _, ParseError, _>(( let (num, operator) = alt::<_, _, ParseError, _>((
tag("<="), tag("<="),
@ -383,87 +392,21 @@ fn parse_prop(s: &str) -> ParseResult<SearchNode> {
tag("<"), tag("<"),
tag(">"), tag(">"),
))(tail) ))(tail)
.map_err(|_| parse_failure(s, FailKind::InvalidPropOperator(prop.to_string())))?; .map_err(|_| parse_failure(prop_clause, FailKind::InvalidPropOperator(prop.to_string())))?;
let kind = if prop == "ease" { let kind = match prop {
if let Ok(f) = num.parse::<f32>() { "ease" => PropertyKind::Ease(parse_prop_float(num, prop_clause)?),
PropertyKind::Ease(f) "due" => PropertyKind::Due(parse_prop_integer(num, prop_clause)?),
} else { "rated" => parse_prop_rated(num, prop_clause)?,
return Err(parse_failure( "resched" => PropertyKind::Rated(
s, parse_prop_integer::<i32>(num, prop_clause)?.min(0),
FailKind::InvalidPropFloat(format!("{}{}", prop, operator)), EaseKind::ManualReschedule,
)); ),
} "ivl" => PropertyKind::Interval(parse_prop_integer(num, prop_clause)?),
} else if prop == "due" { "reps" => PropertyKind::Reps(parse_prop_integer(num, prop_clause)?),
if let Ok(i) = num.parse::<i32>() { "lapses" => PropertyKind::Lapses(parse_prop_integer(num, prop_clause)?),
PropertyKind::Due(i) "pos" => PropertyKind::Position(parse_prop_integer(num, prop_clause)?),
} else {
return Err(parse_failure(
s,
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),
"reps" => PropertyKind::Reps(u),
"lapses" => PropertyKind::Lapses(u),
"pos" => PropertyKind::Position(u),
_ => unreachable!(), _ => unreachable!(),
}
} else {
return Err(parse_failure(
s,
FailKind::InvalidPropUnsigned(format!("{}{}", prop, operator)),
));
}; };
Ok(SearchNode::Property { Ok(SearchNode::Property {
@ -472,6 +415,43 @@ fn parse_prop(s: &str) -> ParseResult<SearchNode> {
}) })
} }
fn parse_prop_float<'a, N>(num: &str, prop_clause: &'a str) -> ParseResult<'a, N>
where
N: FromStr,
<N as FromStr>::Err: PartialEq<ParseFloatError>,
{
num.parse()
.map_err(|_e| parse_failure(prop_clause, FailKind::InvalidNumber(prop_clause.to_owned())))
}
fn parse_prop_integer<'a, N>(num: &str, prop_clause: &'a str) -> ParseResult<'a, N>
where
N: FromStr,
<N as FromStr>::Err: PartialEq<ParseIntError>,
{
num.parse()
.map_err(|_e| parse_failure(prop_clause, FailKind::InvalidNumber(prop_clause.to_owned())))
}
fn parse_prop_rated<'a>(num: &str, prop_clause: &'a str) -> ParseResult<'a, PropertyKind> {
let mut it = num.splitn(2, ':');
let days = parse_prop_integer::<i32>(it.next().unwrap(), prop_clause)?.min(0);
let ease = match it.next() {
Some(v) => match parse_prop_integer(v, prop_clause)? {
u @ 1..=5 => EaseKind::AnswerButton(u),
_ => {
return Err(parse_failure(
prop_clause,
FailKind::InvalidRatedEase(prop_clause.to_owned()),
))
}
},
None => EaseKind::AnyAnswerButton,
};
Ok(PropertyKind::Rated(days, ease))
}
/// eg added:1 /// eg added:1
fn parse_added(s: &str) -> ParseResult<SearchNode> { fn parse_added(s: &str) -> ParseResult<SearchNode> {
if let Ok(days) = s.parse::<u32>() { if let Ok(days) = s.parse::<u32>() {
@ -947,20 +927,17 @@ mod test {
assert_err_kind("prop:pos~1", InvalidPropOperator("pos".to_string())); assert_err_kind("prop:pos~1", InvalidPropOperator("pos".to_string()));
assert_err_kind("prop:reps10", InvalidPropOperator("reps".to_string())); assert_err_kind("prop:reps10", InvalidPropOperator("reps".to_string()));
assert_err_kind("prop:ease>", InvalidPropFloat("ease>".to_string())); assert_err_kind("prop:ease>", InvalidNumber("ease>".to_string()));
assert_err_kind("prop:ease!=one", InvalidPropFloat("ease!=".to_string())); assert_err_kind("prop:ease!=one", InvalidNumber("ease!=one".to_string()));
assert_err_kind("prop:ease<1,3", InvalidPropFloat("ease<".to_string())); assert_err_kind("prop:ease<1,3", InvalidNumber("ease<1,3".to_string()));
assert_err_kind("prop:due>", InvalidPropInteger("due>".to_string())); assert_err_kind("prop:due>", InvalidNumber("due>".to_string()));
assert_err_kind("prop:due=0.5", InvalidPropInteger("due=".to_string())); assert_err_kind("prop:due=0.5", InvalidNumber("due=0.5".to_string()));
assert_err_kind("prop:due<foo", InvalidPropInteger("due<".to_string())); assert_err_kind("prop:due<foo", InvalidNumber("due<foo".to_string()));
assert_err_kind("prop:ivl>", InvalidPropUnsigned("ivl>".to_string())); assert_err_kind("prop:ivl>", InvalidNumber("ivl>".to_string()));
assert_err_kind("prop:reps=1.1", InvalidPropUnsigned("reps=".to_string())); assert_err_kind("prop:reps=1.1", InvalidNumber("reps=1.1".to_string()));
assert_err_kind( assert_err_kind("prop:lapses!=-1", InvalidNumber("lapses!=-1".to_string()));
"prop:lapses!=-1",
InvalidPropUnsigned("lapses!=".to_string()),
);
Ok(()) Ok(())
} }