use super::{ReferenceScanStop, ReferenceWorkBudget};
use crate::Inline;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReferenceLabel {
pub text: String,
pub truncated: bool,
}
pub fn reference_label(
nodes: &[Inline],
depth: usize,
budget: &mut ReferenceWorkBudget,
max_bytes: usize,
) -> Result<ReferenceLabel, ReferenceScanStop> {
let mut label = ReferenceLabel {
text: String::new(),
truncated: false,
};
append(nodes, depth, budget, max_bytes.min(4096), &mut label)?;
Ok(label)
}
fn append(
nodes: &[Inline],
depth: usize,
budget: &mut ReferenceWorkBudget,
limit: usize,
label: &mut ReferenceLabel,
) -> Result<(), ReferenceScanStop> {
for node in nodes {
budget.consume(depth.saturating_add(1), 1, 0)?;
match node {
Inline::Text { value } | Inline::Code { value } => {
let inspect = value
.len()
.min(limit.saturating_sub(label.text.len()).saturating_add(4));
budget.consume(depth.saturating_add(1), 0, inspect)?;
for character in value.chars() {
if character.len_utf8() > limit.saturating_sub(label.text.len()) {
label.truncated = true;
return Ok(());
}
label.text.push(character);
}
}
Inline::LineBreak => {
budget.consume(depth.saturating_add(1), 0, 1)?;
if label.text.len() == limit {
label.truncated = true;
return Ok(());
}
label.text.push('\n');
}
Inline::Strong { children }
| Inline::Emphasis { children }
| Inline::Link { children, .. } => {
append(children, depth.saturating_add(1), budget, limit, label)?;
}
Inline::Anchor { .. } => {}
}
if label.truncated {
break;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ReferenceScanLimits;
#[test]
fn multibyte_label_prefix_is_bounded_without_reading_huge_suffix() {
let mut budget = ReferenceWorkBudget::new(ReferenceScanLimits {
bytes: 8,
..Default::default()
});
let label = reference_label(
&[Inline::Text {
value: format!("éé{}", "x".repeat(100_000)),
}],
0,
&mut budget,
3,
)
.unwrap();
assert_eq!(label.text, "é");
assert!(label.truncated);
assert!(budget.remaining_bytes() > 0);
}
}