more useful template error message
This commit is contained in:
parent
81f7e634d1
commit
b56c9591c0
@ -672,7 +672,12 @@ where c.nid = n.id and c.id in %s group by nid"""
|
||||
(qfmt, afmt) = hooks.card_will_render((qfmt, afmt), fields, model, data)
|
||||
|
||||
# render fields
|
||||
try:
|
||||
qatext = render_card(self, qfmt, afmt, fields, card_ord)
|
||||
except anki.rsbackend.BackendException as e:
|
||||
errmsg = f"Card template has a problem:<br>{e}"
|
||||
qatext = (errmsg, errmsg)
|
||||
|
||||
ret: Dict[str, Any] = dict(q=qatext[0], a=qatext[1], id=card_id)
|
||||
|
||||
# allow add-ons to modify the generated result
|
||||
|
@ -23,7 +23,7 @@ class BackendException(Exception):
|
||||
if kind == "invalid_input":
|
||||
return f"invalid input: {err.invalid_input.info}"
|
||||
elif kind == "template_parse":
|
||||
return f"template parse: {err.template_parse.info}"
|
||||
return err.template_parse.info
|
||||
else:
|
||||
return f"unhandled error: {err}"
|
||||
|
||||
|
@ -44,7 +44,9 @@ def render_card(
|
||||
fields: Dict[str, str],
|
||||
card_ord: int,
|
||||
) -> Tuple[str, str]:
|
||||
"Renders the provided templates, returning rendered q & a text."
|
||||
"""Renders the provided templates, returning rendered q & a text.
|
||||
|
||||
Will raise if the template is invalid."""
|
||||
(qnodes, anodes) = col.backend.render_card(qfmt, afmt, fields, card_ord)
|
||||
|
||||
qtext = apply_custom_filters(qnodes, fields, front_side=None)
|
||||
|
@ -97,7 +97,7 @@ impl Backend {
|
||||
Value::DeckTree(_) => todo!(),
|
||||
Value::FindCards(_) => todo!(),
|
||||
Value::BrowserRows(_) => todo!(),
|
||||
Value::RenderCard(input) => OValue::RenderCard(self.render_template(input)),
|
||||
Value::RenderCard(input) => OValue::RenderCard(self.render_template(input)?),
|
||||
})
|
||||
}
|
||||
|
||||
@ -161,7 +161,7 @@ impl Backend {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_template(&self, input: pt::RenderCardIn) -> pt::RenderCardOut {
|
||||
fn render_template(&self, input: pt::RenderCardIn) -> Result<pt::RenderCardOut> {
|
||||
// convert string map to &str
|
||||
let fields: HashMap<_, _> = input
|
||||
.fields
|
||||
@ -175,13 +175,13 @@ impl Backend {
|
||||
&input.answer_template,
|
||||
&fields,
|
||||
input.card_ordinal as u16,
|
||||
);
|
||||
)?;
|
||||
|
||||
// return
|
||||
pt::RenderCardOut {
|
||||
Ok(pt::RenderCardOut {
|
||||
question_nodes: rendered_nodes_to_proto(qnodes),
|
||||
answer_nodes: rendered_nodes_to_proto(anodes),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,11 +16,30 @@ pub enum AnkiError {
|
||||
|
||||
// error helpers
|
||||
impl AnkiError {
|
||||
pub(crate) fn parse<S: Into<String>>(s: S) -> AnkiError {
|
||||
AnkiError::TemplateParseError { info: s.into() }
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_input<S: Into<String>>(s: S) -> AnkiError {
|
||||
AnkiError::InvalidInput { info: s.into() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TemplateError {
|
||||
NoClosingBrackets(String),
|
||||
ConditionalNotClosed(String),
|
||||
ConditionalNotOpen(String),
|
||||
}
|
||||
|
||||
impl From<TemplateError> for AnkiError {
|
||||
fn from(terr: TemplateError) -> Self {
|
||||
AnkiError::TemplateParseError {
|
||||
info: match terr {
|
||||
TemplateError::NoClosingBrackets(context) => {
|
||||
format!("expected '{{{{field name}}}}', found '{}'", context)
|
||||
}
|
||||
TemplateError::ConditionalNotClosed(tag) => format!("missing '{{{{/{}}}}}'", tag),
|
||||
TemplateError::ConditionalNotOpen(tag) => {
|
||||
format!("missing '{{{{#{}}}}}' or '{{{{^{}}}}}'", tag, tag)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 crate::err::{AnkiError, Result};
|
||||
use crate::err::{Result, TemplateError};
|
||||
use crate::template_filters::apply_filters;
|
||||
use crate::text::strip_sounds;
|
||||
use lazy_static::lazy_static;
|
||||
@ -87,7 +87,7 @@ fn next_token(input: &str) -> nom::IResult<&str, Token> {
|
||||
alt((handle_token, text_token))(input)
|
||||
}
|
||||
|
||||
fn tokens(template: &str) -> impl Iterator<Item = Result<Token>> {
|
||||
fn tokens(template: &str) -> impl Iterator<Item = std::result::Result<Token, TemplateError>> {
|
||||
let mut data = template;
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
@ -99,7 +99,7 @@ fn tokens(template: &str) -> impl Iterator<Item = Result<Token>> {
|
||||
data = i;
|
||||
Some(Ok(o))
|
||||
}
|
||||
Err(e) => Some(Err(AnkiError::parse(format!("{:?}", e)))),
|
||||
Err(_e) => Some(Err(TemplateError::NoClosingBrackets(data.to_string()))),
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -132,16 +132,16 @@ impl ParsedTemplate<'_> {
|
||||
///
|
||||
/// The legacy alternate syntax is not supported, so the provided text
|
||||
/// should be run through without_legacy_template_directives() first.
|
||||
pub fn from_text(template: &str) -> Result<ParsedTemplate> {
|
||||
pub fn from_text(template: &str) -> std::result::Result<ParsedTemplate, TemplateError> {
|
||||
let mut iter = tokens(template);
|
||||
Ok(Self(parse_inner(&mut iter, None)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_inner<'a, I: Iterator<Item = Result<Token<'a>>>>(
|
||||
fn parse_inner<'a, I: Iterator<Item = std::result::Result<Token<'a>, TemplateError>>>(
|
||||
iter: &mut I,
|
||||
open_tag: Option<&'a str>,
|
||||
) -> Result<Vec<ParsedNode<'a>>> {
|
||||
) -> std::result::Result<Vec<ParsedNode<'a>>, TemplateError> {
|
||||
let mut nodes = vec![];
|
||||
|
||||
while let Some(token) = iter.next() {
|
||||
@ -170,16 +170,13 @@ fn parse_inner<'a, I: Iterator<Item = Result<Token<'a>>>>(
|
||||
return Ok(nodes);
|
||||
}
|
||||
}
|
||||
return Err(AnkiError::parse(format!(
|
||||
"unbalanced closing tag: {:?} / {}",
|
||||
open_tag, t
|
||||
)));
|
||||
return Err(TemplateError::ConditionalNotOpen(t.to_string()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(open) = open_tag {
|
||||
Err(AnkiError::parse(format!("unclosed conditional {}", open)))
|
||||
Err(TemplateError::ConditionalNotClosed(open.to_string()))
|
||||
} else {
|
||||
Ok(nodes)
|
||||
}
|
||||
@ -426,7 +423,7 @@ pub fn render_card(
|
||||
afmt: &str,
|
||||
field_map: &HashMap<&str, &str>,
|
||||
card_ord: u16,
|
||||
) -> (Vec<RenderedNode>, Vec<RenderedNode>) {
|
||||
) -> Result<(Vec<RenderedNode>, Vec<RenderedNode>)> {
|
||||
// prepare context
|
||||
let mut context = RenderContext {
|
||||
fields: field_map,
|
||||
@ -438,12 +435,7 @@ pub fn render_card(
|
||||
|
||||
// question side
|
||||
let qnorm = without_legacy_template_directives(qfmt);
|
||||
let qnodes = match ParsedTemplate::from_text(qnorm.as_ref()) {
|
||||
Ok(tmpl) => tmpl.render(&context),
|
||||
Err(e) => vec![RenderedNode::Text {
|
||||
text: format!("{:?}", e),
|
||||
}],
|
||||
};
|
||||
let qnodes = ParsedTemplate::from_text(qnorm.as_ref())?.render(&context);
|
||||
|
||||
// if the question side didn't have any unknown filters, we can pass
|
||||
// FrontSide in now
|
||||
@ -454,14 +446,9 @@ pub fn render_card(
|
||||
// answer side
|
||||
context.question_side = false;
|
||||
let anorm = without_legacy_template_directives(afmt);
|
||||
let anodes = match ParsedTemplate::from_text(anorm.as_ref()) {
|
||||
Ok(tmpl) => tmpl.render(&context),
|
||||
Err(e) => vec![RenderedNode::Text {
|
||||
text: format!("{:?}", e),
|
||||
}],
|
||||
};
|
||||
let anodes = ParsedTemplate::from_text(anorm.as_ref())?.render(&context);
|
||||
|
||||
(qnodes, anodes)
|
||||
Ok((qnodes, anodes))
|
||||
}
|
||||
|
||||
// Field requirements
|
||||
@ -779,7 +766,7 @@ mod test {
|
||||
let clozed_text = "{{c1::one}} {{c2::two::hint}}";
|
||||
let map: HashMap<_, _> = vec![("Text", clozed_text)].into_iter().collect();
|
||||
|
||||
let (qnodes, anodes) = render_card(fmt, fmt, &map, 0);
|
||||
let (qnodes, anodes) = render_card(fmt, fmt, &map, 0).unwrap();
|
||||
assert_eq!(
|
||||
strip_html(get_complete_template(&qnodes).unwrap()),
|
||||
"[...] two"
|
||||
@ -790,11 +777,12 @@ mod test {
|
||||
);
|
||||
|
||||
// FrontSide should render if only standard modifiers were used
|
||||
let (_qnodes, anodes) = render_card("{{kana:text:Text}}", "{{FrontSide}}", &map, 1);
|
||||
let (_qnodes, anodes) =
|
||||
render_card("{{kana:text:Text}}", "{{FrontSide}}", &map, 1).unwrap();
|
||||
assert_eq!(get_complete_template(&anodes).unwrap(), clozed_text);
|
||||
|
||||
// But if a custom modifier was used, it's deferred to the Python code
|
||||
let (_qnodes, anodes) = render_card("{{custom:Text}}", "{{FrontSide}}", &map, 1);
|
||||
let (_qnodes, anodes) = render_card("{{custom:Text}}", "{{FrontSide}}", &map, 1).unwrap();
|
||||
assert_eq!(get_complete_template(&anodes).is_none(), true)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user