Something went wrong. Try again.
atproto git client
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210//! The field of DNA as markup, for the `<pre>` the post-login page hangs it//! behind.//!//! [`crate::art`] draws it; this writes the finished grid out as HTML. The//! two renderings walk the same [`Canvas::rows`](crate::art::Canvas::rows),//! so the browser gets the terminal's art glyph for glyph — which is a claim//! the tests below hold rather than a hope.
use crate::art::{self, Canvas, Ink};
/// The field prerendered into the post-login page, in cells. This one does/// not follow anything the way `about`'s frame follows a terminal: the page/// is drawn once, by the process handling the OAuth callback, and has no idea/// what it will be opened on. So it is cut generously — wider and taller than/// a laptop screen holds at the type size the page asks for — and the page/// fades it out at its own edges, which makes a screen too large to cover/// look like art floating in space rather than a texture that ran out.////// 79 rows is a whole number of half turns past the first, the property/// [`art::snap_rows`] exists to preserve, so the bottom edge is as wide as/// the top one. Barely visible under the fade, and free to keep.const PAGE_COLUMNS: i32 = 240;const PAGE_ROWS: i32 = 79;
/// The CSS class for a cell's ink, given what a caller wants done with/// [`Ink::Muted`].////// The bases always get one. Muted is the choice: in the field it is every/// bond glyph — most of the grid — and wrapping each run of `-` and `=` in/// an element to say "this is the colour the `<pre>` is already painted in"/// would multiply the page's largest asset for nothing. On the panel it is/// the border and the labels, which have to be *dimmer* than the text beside/// them, so there it is worth an element.////// [`Ink::Default`] never gets one either way. That is what the word means:/// whatever colour the block it landed in is set in.fn class_for(ink: Ink, muted: &'static str) -> &'static str { match ink { Ink::A => "a", Ink::T => "t", Ink::G => "g", Ink::C => "c", Ink::Muted => muted, Ink::Default => "", }}
/// One canvas as markup: a run of one color becomes one `<i>` with a/// one-letter class instead of one SGR escape. Uncolored runs are emitted/// bare, so a field's bonds and blanks cost nothing but their own glyphs.////// Written for a `<pre>`, where the newlines and the leading blanks are the/// layout. The glyphs are escaped even though the field draws none of the/// three that need it — the canvas is general, and a page assembled from it/// should not depend on what happens to be drawn on it.fn render(canvas: &Canvas, muted: &'static str) -> String { let mut out = String::new(); for row in canvas.rows() { let mut open = ""; for cell in row { let class = class_for(cell.ink, muted); if class != open { if !open.is_empty() { out.push_str("</i>"); } if !class.is_empty() { out.push_str("<i class="); out.push_str(class); out.push('>'); } open = class; } match cell.ch { '&' => out.push_str("&"), '<' => out.push_str("<"), '>' => out.push_str(">"), ch => out.push(ch), } } if !open.is_empty() { out.push_str("</i>"); } out.push('\n'); } out}
/// The field, with nothing punched out of it, ready for a `<pre>`.////// Prerendered here rather than drawn in the browser — it is the same art/// `atgc about` shows, from the same code, and a page that arrives finished/// has nothing to lay out twice or to get wrong with a font that loads late.pub(crate) fn field() -> String { let mut canvas = Canvas::new(PAGE_COLUMNS, PAGE_ROWS); art::draw_field(&mut canvas); render(&canvas, "")}
/// Any canvas as markup, with the muted ink marked up too: for a block whose/// text is the subject rather than the texture, where a border has to read/// as quieter than the line inside it.////// Only `site` wants this, so it is compiled where that is — it is/// behind the `www` feature, which is why this is not a doc link.#[cfg(any(feature = "www", test))]pub(crate) fn panel(canvas: &Canvas) -> String { render(canvas, "m")}
#[cfg(test)]mod tests { use super::*;
/// CSS class per base, in [`art::STRAND`] order. The mapping lives in /// [`class_for`]; this is the list the tests below check it against. const CLASSES: [&str; 4] = ["a", "t", "g", "c"];
/// The inverse of what `render_html` adds: drop the elements, put the /// three escaped characters back, and the glyphs are all that is left. fn strip_tags(html: &str) -> String { let mut out = String::new(); let mut rest = html; while let Some(open) = rest.find('<') { out.push_str(&rest[..open]); let close = open + rest[open..].find('>').expect("unclosed element"); rest = &rest[close + 1..]; } out.push_str(rest); out.replace("<", "<") .replace(">", ">") .replace("&", "&") }
// -- the field in HTML -----------------------------------------------
/// The browser gets the same art as the terminal, from the same code: /// strip the markup back out of the HTML and what is left is the plain /// render of the same canvas, glyph for glyph. This is the claim the /// whole prerendering rests on — everything else about the page is CSS. #[test] fn the_html_field_is_the_terminal_field_with_markup_around_it() { let mut canvas = Canvas::new(PAGE_COLUMNS, PAGE_ROWS); art::draw_field(&mut canvas);
let stripped = strip_tags(&render(&canvas, "")); assert_eq!(stripped, canvas.render(false)); assert_eq!(stripped.lines().count(), PAGE_ROWS as usize); // And `field` draws that canvas rather than one of its own. assert_eq!(field(), render(&canvas, "")); }
/// Only the bases are wrapped. The bonds are the bulk of the field's /// glyphs and they are left bare for the `<pre>` to color, which is what /// keeps the page to tens of kilobytes instead of hundreds. #[test] fn only_the_bases_are_wrapped_in_an_element() { let html = field(); let bases = html .chars() .filter(|c| matches!(c, 'A' | 'T' | 'G' | 'C')) .count(); assert_eq!(html.matches("<i class=").count(), bases); assert_eq!(html.matches("</i>").count(), bases); // Every class is one of the four, and every one of the four is used. for class in CLASSES { assert!( html.contains(&format!("<i class={class}>")), "no {class} in the field" ); } let classes: Vec<&str> = html .split("<i class=") .skip(1) .map(|rest| rest.split('>').next().unwrap_or("")) .collect(); for class in classes { assert!(CLASSES.contains(&class), "unknown class {class}"); } // Bonds are the runs left uncolored, so a long one arrives as text. assert!(html.contains("==="), "no bare bond run: {}", &html[..200]); }
#[test] fn a_class_is_the_base_it_colors_and_nothing_else_has_one() { for base in art::STRAND { let class = class_for(Ink::of(base), "m"); assert_eq!(class, base.to_ascii_lowercase().to_string()); } // Bonds, borders and the default ink take the page's own color. assert_eq!(class_for(Ink::Muted, ""), ""); assert_eq!(class_for(Ink::Default, ""), ""); // Asked for, the muted ink takes the class it was given; the // default ink never takes one, which is the whole difference. assert_eq!(class_for(Ink::Muted, "m"), "m"); assert_eq!(class_for(Ink::Default, "m"), ""); }
/// The canvas escapes what it renders. The field draws none of the three /// characters that need it, so this asks the canvas directly — the point /// is that a page built from one cannot be broken by what was drawn. #[test] fn html_escapes_the_glyphs_that_would_be_markup() { let mut canvas = Canvas::new(6, 1); canvas.write(0, 0, "<&>", Ink::Default); canvas.put(3, 0, '<', Ink::of('A')); assert_eq!(render(&canvas, ""), "<&><i class=a><</i>\n"); assert!(!field().contains("<"), "the field draws no markup"); }}