feat: render fixed-width text for nex
This commit is contained in:
parent
2f38b8b929
commit
9e1cfed284
19 changed files with 1160 additions and 44 deletions
105
text/src/inline.rs
Normal file
105
text/src/inline.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
//! Inline markup flattened to plain text.
|
||||
//!
|
||||
//! Plain text has no markup to carry emphasis, so it is dropped and the words
|
||||
//! kept. A link becomes `label (url)`: self-contained, and readable without
|
||||
//! scrolling to a reference list somewhere else. md2txt's `text` renderer emits
|
||||
//! numbered markers instead but never writes the list they point at, so the
|
||||
//! numbers lead nowhere.
|
||||
|
||||
use itsybitsy_core::ir::{Doc, Inline};
|
||||
|
||||
/// Flatten a run of inlines, spelling out where each link points.
|
||||
pub fn flatten(inline: &[Inline]) -> String {
|
||||
let mut out = String::new();
|
||||
write(inline, &mut out);
|
||||
out
|
||||
}
|
||||
|
||||
fn write(inline: &[Inline], out: &mut String) {
|
||||
for item in inline {
|
||||
match item {
|
||||
Inline::Text(text) => out.push_str(text),
|
||||
// Backticks are the only markup worth keeping: they mark where a
|
||||
// literal begins and end, which prose otherwise cannot show.
|
||||
Inline::Code(code) => out.push_str(&format!("`{code}`")),
|
||||
Inline::Emph(inner) | Inline::Strong(inner) | Inline::Strike(inner) => {
|
||||
write(inner, out)
|
||||
}
|
||||
Inline::Link { href, label, .. } => {
|
||||
let text = Doc::plain_text(label);
|
||||
write_target(&text, href, out);
|
||||
}
|
||||
Inline::Image { src, alt, .. } => {
|
||||
let text = Doc::plain_text(alt);
|
||||
write_target(&text, src, out);
|
||||
}
|
||||
Inline::SoftBreak => out.push(' '),
|
||||
// A hard break is honoured by the layout, which splits on it.
|
||||
Inline::HardBreak => out.push('\n'),
|
||||
// Raw markup shown literally would be worse than leaving it out.
|
||||
Inline::Html(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `label (url)`, or just the url when the label would repeat it.
|
||||
fn write_target(label: &str, url: &str, out: &mut String) {
|
||||
let label = label.trim();
|
||||
if label.is_empty() || label == url {
|
||||
out.push_str(url);
|
||||
} else {
|
||||
out.push_str(&format!("{label} ({url})"));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn text(value: &str) -> Vec<Inline> {
|
||||
vec![Inline::Text(value.into())]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emphasis_is_dropped_and_its_words_kept() {
|
||||
let inline = vec![
|
||||
Inline::Text("a ".into()),
|
||||
Inline::Strong(text("b")),
|
||||
Inline::Text(" ".into()),
|
||||
Inline::Emph(text("c")),
|
||||
];
|
||||
assert_eq!(flatten(&inline), "a b c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_keeps_its_backticks_to_mark_where_a_literal_ends() {
|
||||
assert_eq!(flatten(&[Inline::Code("x = 1".into())]), "`x = 1`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_link_spells_out_where_it_points() {
|
||||
let inline =
|
||||
vec![Inline::Link { href: "/about".into(), title: None, label: text("about page") }];
|
||||
assert_eq!(flatten(&inline), "about page (/about)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_link_whose_label_is_its_url_is_not_repeated() {
|
||||
let url = "https://example.com/";
|
||||
let inline = vec![Inline::Link { href: url.into(), title: None, label: text(url) }];
|
||||
assert_eq!(flatten(&inline), url);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_image_is_treated_like_a_link() {
|
||||
let inline =
|
||||
vec![Inline::Image { src: "/i.png".into(), title: None, alt: text("a photo") }];
|
||||
assert_eq!(flatten(&inline), "a photo (/i.png)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_html_is_left_out() {
|
||||
let inline = vec![Inline::Text("a".into()), Inline::Html("<br>".into())];
|
||||
assert_eq!(flatten(&inline), "a");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue