Modify card rendering output to specify if rendered card is empty (#3890)

* modify render_card to return whether card was empty

* plumbing

* add flag to proto message

* plumbing: pass flag along to PartiallyRenderedCard

* add tests

* Use a custom return type for clarity (dae)
This commit is contained in:
llama 2025-03-31 18:51:28 +08:00 committed by GitHub
parent 1798620d64
commit ccab18b7ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 39 additions and 14 deletions

View file

@ -127,6 +127,7 @@ message RenderCardResponse {
repeated RenderedTemplateNode answer_nodes = 2;
string css = 3;
bool latex_svg = 4;
bool is_empty = 5;
}
message RenderedTemplateNode {

View file

@ -60,6 +60,7 @@ class PartiallyRenderedCard:
anodes: TemplateReplacementList
css: str
latex_svg: bool
is_empty: bool
@classmethod
def from_proto(
@ -68,7 +69,9 @@ class PartiallyRenderedCard:
qnodes = cls.nodes_from_proto(out.question_nodes)
anodes = cls.nodes_from_proto(out.answer_nodes)
return PartiallyRenderedCard(qnodes, anodes, out.css, out.latex_svg)
return PartiallyRenderedCard(
qnodes, anodes, out.css, out.latex_svg, out.is_empty
)
@staticmethod
def nodes_from_proto(

View file

@ -219,6 +219,7 @@ impl From<RenderCardOutput> for anki_proto::card_rendering::RenderCardResponse {
answer_nodes: rendered_nodes_to_proto(o.anodes),
css: o.css,
latex_svg: o.latex_svg,
is_empty: o.is_empty,
}
}
}

View file

@ -20,6 +20,7 @@ pub struct RenderCardOutput {
pub anodes: Vec<RenderedNode>,
pub css: String,
pub latex_svg: bool,
pub is_empty: bool,
}
impl RenderCardOutput {
@ -136,7 +137,7 @@ impl Collection {
)
};
let (qnodes, anodes) = render_card(RenderCardRequest {
let response = render_card(RenderCardRequest {
qfmt,
afmt,
field_map: &field_map,
@ -147,10 +148,11 @@ impl Collection {
partial_render,
})?;
Ok(RenderCardOutput {
qnodes,
anodes,
qnodes: response.qnodes,
anodes: response.anodes,
css: nt.config.css.clone(),
latex_svg: nt.config.latex_svg,
is_empty: response.is_empty,
})
}

View file

@ -592,6 +592,13 @@ pub struct RenderCardRequest<'a> {
pub partial_render: bool,
}
pub struct RenderCardResponse {
pub qnodes: Vec<RenderedNode>,
pub anodes: Vec<RenderedNode>,
pub is_empty: bool,
}
/// Returns `(qnodes, anodes, is_empty)`
pub fn render_card(
RenderCardRequest {
qfmt,
@ -603,7 +610,7 @@ pub fn render_card(
tr,
partial_render: partial_for_python,
}: RenderCardRequest<'_>,
) -> Result<(Vec<RenderedNode>, Vec<RenderedNode>)> {
) -> Result<RenderCardResponse> {
// prepare context
let mut context = RenderContext {
fields: field_map,
@ -638,7 +645,11 @@ pub fn render_card(
};
if let Some(text) = empty_message {
qnodes.push(RenderedNode::Text { text: text.clone() });
return Ok((qnodes, vec![RenderedNode::Text { text }]));
return Ok(RenderCardResponse {
qnodes,
anodes: vec![RenderedNode::Text { text }],
is_empty: true,
});
}
// answer side
@ -654,7 +665,11 @@ pub fn render_card(
.and_then(|tmpl| tmpl.render(&context, tr))
.map_err(|e| template_error_to_anki_error(e, false, browser, tr))?;
Ok((qnodes, anodes))
Ok(RenderCardResponse {
qnodes,
anodes,
is_empty: false,
})
}
fn cloze_is_empty(field_map: &HashMap<&str, Cow<str>>, card_ord: u16) -> bool {
@ -1338,14 +1353,15 @@ mod test {
tr: &tr,
partial_render: true,
};
let qnodes = super::render_card(req.clone()).unwrap().0;
let response = super::render_card(req.clone()).unwrap();
assert_eq!(
qnodes[0],
response.qnodes[0],
FN::Text {
text: "test".into()
}
);
if let FN::Text { ref text } = qnodes[1] {
assert!(response.is_empty);
if let FN::Text { ref text } = response.qnodes[1] {
assert!(text.contains("card is blank"));
} else {
unreachable!();
@ -1354,9 +1370,9 @@ mod test {
// a popular card template expects {{FrontSide}} to resolve to an empty
// string on the front side :-(
req.qfmt = "{{FrontSide}}{{N}}";
let qnodes = super::render_card(req.clone()).unwrap().0;
let response = super::render_card(req.clone()).unwrap();
assert_eq!(
&qnodes,
&response.qnodes,
&[
FN::Replacement {
field_name: "FrontSide".into(),
@ -1366,8 +1382,10 @@ mod test {
FN::Text { text: "N".into() }
]
);
assert!(!response.is_empty);
req.partial_render = false;
let qnodes = super::render_card(req.clone()).unwrap().0;
assert_eq!(&qnodes, &[FN::Text { text: "N".into() }]);
let response = super::render_card(req.clone()).unwrap();
assert_eq!(&response.qnodes, &[FN::Text { text: "N".into() }]);
assert!(!response.is_empty);
}
}