.forge/prs/5519019f-99b8-4923-b089-234385a7c909/patch.diff 18.5 KiB 382 lines Raw
1 From bf991339b9474f18e33f3684c79a2c2e6013e90e Mon Sep 17 00:00:00 20012 From: gl0w <gl0w@world>3 Date: Thu, 10 Sep 2026 08:23:50 +01004 Subject: [PATCH] m6/owner-logo: owner block-art ansi+plain, tty banner5 6 ---7  src/logo.rs          | 126 ++++++++++++++++++++++++++++++++++++++++---8  src/main.rs          |   6 +++9  static/ascii.txt     |  17 ++++--10  static/logo-ansi.txt |  12 +++++11  tests/logo.rs        | 110 ++++++++++++++++++++++++++++++-------12  5 files changed, 239 insertions(+), 32 deletions(-)13  create mode 100644 static/logo-ansi.txt14 15 diff --git a/src/logo.rs b/src/logo.rs16 index f580b2b..f71358c 10064417 --- a/src/logo.rs18 +++ b/src/logo.rs19 @@ -1,29 +1,44 @@20 -//! hu13 logo + ASCII banner (m6, f0rge issues #15 + #27 + #28).21 +//! hu13 logo + ASCII banner (m6, f0rge issues #15 + #26 + #27 + #28).22  //!23  //! The lockup lives hand-written in `static/logo.svg` (diamond-hub mark +24  //! hu13 lettered as <path>, no <text>/fonts; reads at 24px header size)25  //! plus `static/wordmark.svg` (legacy horizontal lockup); the simplified26  //! 16px favicon variant lives in `static/favicon.svg` (mark only, no27  //! letters — small sizes stay legible, full lettering stays in logo.svg);28 -//! the text banner lives in `static/ascii.txt`.29 +//! the text banner lives in `static/ascii.txt` (owner block-art, 12 lines,30 +//! stripped from `static/logo-ansi.txt`).31  //!32 -//! [`ASCII_ART`], [`LOGO_SVG`], [`FAVICON_SVG`] and [`WORDMARK_SVG`]33 -//! re-expose those static files for later issues (navbar #16, favicon #17,34 -//! wiki README #19, agents page) with no build step.35 +//! [`ASCII_ART`], [`LOGO_ANSI_RAW`], [`LOGO_SVG`], [`FAVICON_SVG`] and36 +//! [`WORDMARK_SVG`] re-expose those static files for later issues (navbar37 +//! #16, favicon #17, wiki README #19, agents page) with no build step.38  //!39  //! [`ascii_pre`] renders the banner as an escaped `<pre>` block (see40 -//! [`crate::layout::esc`]): the art itself is plain `|/-_` plus spaces so41 +//! [`crate::layout::esc`]): the art itself is block glyphs plus spaces so42  //! escaping is a no-op today, but the wrapper keeps the invariant that any43  //! future art containing `<>&"` stays inert inside HTML.44 +//!45 +//! `static/logo-ansi.txt` stores the owner's 12-line ANSI art with literal46 +//! `[...m` codes and NO raw ESC bytes (repo stays ESC-free); terminal use47 +//! goes through [`logo_ansi_for_tty`], which prefixes each SGR code with48 +//! ESC at runtime so a tty renders colors while pipes stay plain.49  50  use crate::layout::esc;51  52  // TODO(m6+): stubs only — navbar (#16), favicon (#17), wiki README (#19)53  // and the agents page will call these; allow until wired.54  #[allow(dead_code)]55 -/// ASCII banner, byte-for-byte `static/ascii.txt` (5 lines, <=60 cols).56 +/// ASCII banner, byte-for-byte `static/ascii.txt` (owner block-art,57 +/// 12 lines, 42 cols, block glyphs + spaces).58  pub const ASCII_ART: &str = include_str!("../static/ascii.txt");59  60 +#[allow(dead_code)]61 +/// Owner ANSI block-art, byte-for-byte `static/logo-ansi.txt` (12 lines).62 +///63 +/// Stored with literal `[...m` SGR codes and NO raw ESC bytes (see raw64 +/// file); use [`logo_ansi_for_tty`] for terminal output and65 +/// [`strip_ansi_sgr`] for the plain render.66 +pub const LOGO_ANSI_RAW: &str = include_str!("../static/logo-ansi.txt");67 +68  #[allow(dead_code)]69  /// Header lockup, byte-for-byte `static/logo.svg` (mark + hu13 as <path>).70  pub const LOGO_SVG: &str = include_str!("../static/logo.svg");71 @@ -50,6 +65,80 @@ pub fn ascii_pre() -> String {72      )73  }74  75 +/// Strip SGR color codes from owner ANSI art.76 +///77 +/// Removes literal `[...m` sequences (`[` + digits/`;` + `m`) plus the same78 +/// sequences with a leading ESC byte (`\x1b[...m`, as produced by79 +/// [`interpret_ansi_sgr`]), leaving the plain block glyphs. Equivalent to80 +/// `sed 's/\[[0-9;]*m//g'` on the ESC-free stored file.81 +#[allow(dead_code)]82 +#[must_use]83 +pub fn strip_ansi_sgr(s: &str) -> String {84 +    let bytes = s.as_bytes();85 +    let mut out = String::with_capacity(s.len());86 +    let mut i = 0;87 +    while i < bytes.len() {88 +        let mut j = i;89 +        // Optional single ESC prefix (interpreted form).90 +        if bytes[j] == 0x1b && j + 1 < bytes.len() && bytes[j + 1] == b'[' {91 +            j += 1;92 +        }93 +        if bytes[j] == b'[' {94 +            let mut k = j + 1;95 +            while k < bytes.len() && (bytes[k].is_ascii_digit() || bytes[k] == b';') {96 +                k += 1;97 +            }98 +            if k < bytes.len() && bytes[k] == b'm' && k > j + 1 {99 +                i = k + 1;100 +                continue;101 +            }102 +        }103 +        let ch = s[i..].chars().next().expect("char boundary");104 +        out.push(ch);105 +        i += ch.len_utf8();106 +    }107 +    out108 +}109 +110 +/// Prefix each literal SGR code (`[...m`) with ESC for tty output.111 +///112 +/// The stored file is ESC-free so the repo never holds raw ESC bytes;113 +/// this converts at runtime so a terminal renders magenta/cyan/grey.114 +/// A `[` not followed by digits/`;` + `m` is left alone.115 +#[allow(dead_code)]116 +#[must_use]117 +pub fn interpret_ansi_sgr(s: &str) -> String {118 +    let bytes = s.as_bytes();119 +    let mut out = String::with_capacity(s.len() + 64);120 +    let mut i = 0;121 +    while i < bytes.len() {122 +        if bytes[i] == b'[' {123 +            let mut k = i + 1;124 +            while k < bytes.len() && (bytes[k].is_ascii_digit() || bytes[k] == b';') {125 +                k += 1;126 +            }127 +            if k < bytes.len() && bytes[k] == b'm' && k > i + 1 {128 +                out.push('\u{1b}');129 +                let ch = s[i..].chars().next().expect("char boundary");130 +                out.push(ch);131 +                i += ch.len_utf8();132 +                continue;133 +            }134 +        }135 +        let ch = s[i..].chars().next().expect("char boundary");136 +        out.push(ch);137 +        i += ch.len_utf8();138 +    }139 +    out140 +}141 +142 +/// Owner ANSI art interpreted for a tty (ESC-prefixed SGR codes).143 +#[allow(dead_code)]144 +#[must_use]145 +pub fn logo_ansi_for_tty() -> String {146 +    interpret_ansi_sgr(LOGO_ANSI_RAW)147 +}148 +149  #[cfg(test)]150  mod tests {151      use super::*;152 @@ -80,6 +169,29 @@ mod tests {153          assert!(!pre.contains("<script"), "no script smuggling");154      }155  156 +    #[test]157 +    fn ansi_raw_is_esc_free_with_codes() {158 +        // Owner file (#26): literal `[...m` codes, block glyphs, no ESC.159 +        assert!(!LOGO_ANSI_RAW.contains('\u{1b}'), "no raw ESC bytes stored");160 +        assert!(LOGO_ANSI_RAW.contains("[0;"), "holds SGR codes");161 +        assert!(LOGO_ANSI_RAW.contains('█'), "holds block glyphs");162 +        assert_eq!(LOGO_ANSI_RAW.lines().count(), 12, "12 lines");163 +    }164 +165 +    #[test]166 +    fn strip_yields_ascii() {167 +        assert_eq!(strip_ansi_sgr(LOGO_ANSI_RAW), ASCII_ART, "strip == ascii");168 +        assert_eq!(strip_ansi_sgr(ASCII_ART), ASCII_ART, "plain is stable");169 +    }170 +171 +    #[test]172 +    fn tty_interpretation_holds_esc_and_strips_back() {173 +        let tty = logo_ansi_for_tty();174 +        assert!(tty.contains('\u{1b}'), "tty form carries ESC");175 +        assert!(tty.contains('█'), "glyphs kept");176 +        assert_eq!(strip_ansi_sgr(&tty), ASCII_ART, "tty strips to ascii");177 +    }178 +179      #[test]180      fn svg_consts_match_tiny_marks() {181          for (name, svg) in [182 diff --git a/src/main.rs b/src/main.rs183 index bcacf00..cffc87e 100644184 --- a/src/main.rs185 +++ b/src/main.rs186 @@ -464,6 +464,12 @@ fn real_main() -> i32 {187      match rest[0].as_str() {188          "--version" | "-V" | "version" => {189              println!("hu13 {VERSION}");190 +            // Owner logo (#26): on a tty also show the ANSI block-art191 +            // interpreted (ESC-prefixed so colors render); pipes stay a192 +            // single plain version line. Zero-dep via IsTerminal.193 +            if std::io::IsTerminal::is_terminal(&std::io::stdout()) {194 +                print!("{}", crate::logo::logo_ansi_for_tty());195 +            }196              0197          }198          "--help" | "-h" | "help" => {199 diff --git a/static/ascii.txt b/static/ascii.txt200 index 1e39422..020ad10 100644201 --- a/static/ascii.txt202 +++ b/static/ascii.txt203 @@ -1,5 +1,12 @@204 -|             /|   ___205 -|___           |  |   |206 -|   | |   |    |  |---207 -|   | |   |    |  |   |208 -|   | |___|  __|_ |___|209 + ███   ███  ███   ███  █████      ▄███▄   210 + ██▓ ░ ▓██  ██▓ ░ ▓██ ▄▄ ▒▓█ ▄▄▀ █▓░ ░▓█ ▀211 + ▓▓░ ▒ ░▓▓  ▓▓░ ▒ ░▓▓ ▒▒ ░▒▓ ▒▒ ▓▓░▌▄▐░▓▓ 212 + ▓▒░ ▀ ░▒▓  ▓▒░ ▀ ░▒▓ ▀▀ ░░▒ ▀▀ ▓▒░ ▀ ░▒▓ 213 + ░░░ ▀ ░░░  ░░▒ ░ ░░░ ░░ ░░░ ░░▄▄▄▄ ▀ ░░░ 214 + ░░▒▀▀▀▒░░  ░▒▓ ▒ ▒░░ ▒▒ ▒░░ ▒▒▓▓▓▓ ░░░░▒ 215 + ░▒▒ ▓ ▒▒░  ▒▓▓ ▓ ▓▒░ ▓▓ ▒▒░ ▓▓▀▀▀▀ ▄ ▒▒▓ 216 + ▒▒▓ ▒ ▓▒▒  ▓▓█ █ ▓▓▒ ██ ▓▒▒ ██ ▓▓▓ ▓ ██▓ 217 + ▓▓█ ▄ █▓▓  ▓█▓ ▄ ▓█▓ ▄▄ █▓▓ ▄▄ █▓▓ ▄ ▓██ 218 + ▓██ ▒ ██▓  ███ ▒ ▓██ ▒▒ ██▓ ▒▒ ██▓ ▀ ███ 219 + ███ ▀ ███  ███▄ ▄███ ▀▀ ███ ▀▀▄ ███ ███ ▄220 + ███   ███  ▀███████▀  ███████    ▀███▀   221 diff --git a/static/logo-ansi.txt b/static/logo-ansi.txt222 new file mode 100644223 index 0000000..992e446224 --- /dev/null225 +++ b/static/logo-ansi.txt226 @@ -0,0 +1,12 @@227 +[0;37;40m [0;95;40m███[0;37;40m   [0;95;40m███[0;37;40m  [0;95;40m███[0;37;40m   [0;95;40m███[0;37;40m  [0;95;40m█[0;95;45m██[0;95;40m██[0;37;40m      [0;95;40m▄███▄[0;37;40m   [0m228 +[0;37;40m [0;95;40m██[0;95;45m▓[0;37;40m [0;36;40m░[0;37;40m [0;95;45m▓[0;95;40m██[0;37;40m  [0;95;40m██[0;95;45m▓[0;37;40m [0;36;40m░[0;37;40m [0;95;45m▓[0;95;40m██[0;37;40m [0;90;40m▄▄[0;37;40m [0;95;45m▒▓█[0;37;40m [0;90;40m▄▄▀[0;37;40m [0;95;40m█[0;95;45m▓░[0;37;40m [0;95;45m░▓[0;95;40m█[0;37;40m [0;90;40m▀[0m229 +[0;37;40m [0;95;45m▓▓░[0;37;40m [0;36;40m▒[0;37;40m [0;95;45m░▓▓[0;37;40m  [0;95;45m▓▓░[0;37;40m [0;36;40m▒[0;37;40m [0;95;45m░▓▓[0;37;40m [0;36;40m▒▒[0;37;40m [0;95;45m░▒▓[0;37;40m [0;36;40m▒▒[0;37;40m [0;95;45m▓▓░[0;35;40m▌[0;90;40m▄[0;35;40m▐[0;95;45m░▓▓[0;37;40m [0m230 +[0;37;40m [0;95;45m▓▒[0;96;46m░[0;37;40m [0;90;40m▀[0;37;40m [0;96;46m░[0;95;45m▒▓[0;37;40m  [0;95;45m▓▒[0;96;46m░[0;37;40m [0;90;40m▀[0;37;40m [0;96;46m░[0;95;45m▒▓[0;37;40m [0;90;40m▀▀[0;37;40m [0;96;46m░[0;95;45m░▒[0;37;40m [0;90;40m▀▀[0;37;40m [0;95;45m▓▒░[0;37;40m [0;90;40m▀[0;37;40m [0;96;46m░[0;95;45m▒▓[0;37;40m [0m231 +[0;37;40m [0;95;45m░[0;96;46m░░[0;37;40m [0;34;40m▀[0;37;40m [0;96;46m░░[0;95;45m░[0;37;40m  [0;95;45m░[0;96;46m░▒[0;37;40m [0;34;40m░[0;37;40m [0;96;46m░░[0;95;45m░[0;37;40m [0;34;40m░░[0;37;40m [0;96;46m░░[0;95;45m░[0;37;40m [0;34;40m░░▄▄▄▄[0;37;40m [0;34;40m▀[0;37;40m [0;96;46m░░[0;95;45m░[0;37;40m [0m232 +[0;37;40m [0;96;46m░░▒[0;96;40m▀▀▀[0;96;46m▒░░[0;37;40m  [0;96;46m░▒▓[0;37;40m [0;34;40m▒[0;37;40m [0;96;46m▒░░[0;37;40m [0;34;40m▒▒[0;37;40m [0;96;46m▒░░[0;37;40m [0;34;40m▒▒▓▓▓▓[0;37;40m [0;96;46m░░░░▒[0;37;40m [0m233 +[0;37;40m [0;96;46m░▒▒[0;37;40m [0;34;40m▓[0;37;40m [0;96;46m▒▒░[0;37;40m  [0;96;46m▒▓▓[0;37;40m [0;34;40m▓[0;37;40m [0;96;46m▓▒░[0;37;40m [0;34;40m▓▓[0;37;40m [0;96;46m▒▒░[0;37;40m [0;34;40m▓▓▀▀▀▀[0;37;40m [0;34;40m▄[0;37;40m [0;96;46m▒▒▓[0;37;40m [0m234 +[0;37;40m [0;96;46m▒▒▓[0;37;40m [0;34;40m▒[0;37;40m [0;96;46m▓▒▒[0;37;40m  [0;96;40m▓[0;96;46m▓[0;96;40m█[0;37;40m [0;34;40m█[0;37;40m [0;96;46m▓▓▒[0;37;40m [0;34;40m██[0;37;40m [0;96;46m▓▒▒[0;37;40m [0;34;40m██[0;37;40m [0;96;46m▓▓▓[0;37;40m [0;34;40m▓[0;37;40m [0;96;40m██▓[0;37;40m [0m235 +[0;37;40m [0;96;46m▓▓█[0;37;40m [0;90;40m▄[0;37;40m [0;96;46m█▓▓[0;37;40m  [0;96;46m▓█▓[0;37;40m [0;90;40m▄[0;37;40m [0;96;46m▓█▓[0;37;40m [0;90;40m▄▄[0;37;40m [0;96;46m█▓▓[0;37;40m [0;90;40m▄▄[0;37;40m [0;96;40m█[0;96;46m▓▓[0;37;40m [0;90;40m▄[0;37;40m [0;96;46m▓██[0;37;40m [0m236 +[0;37;40m [0;96;46m▓██[0;37;40m [0;36;40m▒[0;37;40m [0;96;46m██▓[0;37;40m  [0;96;46m███[0;37;40m [0;36;40m▒[0;37;40m [0;96;46m▓[0;96;40m██[0;37;40m [0;36;40m▒▒[0;37;40m [0;96;46m██▓[0;37;40m [0;36;40m▒▒[0;37;40m [0;96;40m██[0;96;46m▓[0;37;40m [0;90;40m▀[0;37;40m [0;96;46m███[0;37;40m [0m237 +[0;37;40m [0;96;40m███[0;37;40m [0;90;40m▀[0;37;40m [0;96;40m███[0;37;40m  [0;96;40m█[0;96;46m██[0;96;40m▄[0;37;40m [0;96;40m▄[0;96;46m█[0;96;40m██[0;37;40m [0;90;40m▀▀[0;37;40m [0;96;46m███[0;37;40m [0;90;40m▀▀▄[0;37;40m [0;96;40m█[0;96;46m██[0;37;40m [0;96;46m███[0;37;40m [0;90;40m▄[0m238 +[0;37;40m [0;96;46m███[0;37;40m   [0;96;46m███[0;37;40m  [0;96;40m▀██[0;96;46m███[0;96;40m██▀[0;37;40m  [0;96;40m███████[0;37;40m    [0;96;40m▀[0;96;46m███[0;96;40m▀[0;37;40m   [0m239 diff --git a/tests/logo.rs b/tests/logo.rs240 index 08884b5..21c3518 100644241 --- a/tests/logo.rs242 +++ b/tests/logo.rs243 @@ -1,7 +1,9 @@244 -//! Logo suite (m6, f0rge issues #15 + #27 + #28):245 +//! Logo suite (m6, f0rge issues #15 + #26 + #27 + #28):246  //! `static/logo.svg` (lockup with hu13 as <path>) + `static/favicon.svg`247  //! (simplified mark-only 16px variant) exist + valid XML + <2KB total,248 -//! wordmark variant, ASCII banner limits, `<pre>` rendering invariant.249 +//! wordmark variant, owner block-art banner (`static/ascii.txt` +250 +//! `static/logo-ansi.txt`, 12 lines) + purity/strip tests + `<pre>`251 +//! rendering invariant.252  //!253  //! File-level suite (hu13 is a binary crate, so `src/logo.rs` unit tests254  //! cover the `ascii_pre()` helper itself; here we assert on disk).255 @@ -209,34 +211,47 @@ fn wordmark_variant_exists_tiny() {256  257  #[test]258  fn ascii_banner_limits_and_aesthetic() {259 +    // Owner block-art (#26): 12-line block glyphs reading "hu13".260 +    // Charset is space + block set (█ ▓ ▒ ░ ▄ ▀ ▌ ▐) plus the legacy261 +    // line-art chars (| - / _ +) for forward compat — still no letters,262 +    // no `<>&"` so `<pre>` stays verbatim via esc().263      let art = read_static("ascii.txt");264      assert!(!art.trim().is_empty(), "banner is non-empty");265      let lines: Vec<&str> = art.lines().collect();266 -    assert!(267 -        lines.len() <= 12,268 -        "banner is {} lines, cap is 12",269 -        lines.len()270 -    );271 -    assert!(lines.len() >= 2, "banner is a banner, not one line");272 -    let mut saw_pipe = false;273 -    let mut saw_hyphen = false;274 -    let mut saw_slash = false;275 +    assert_eq!(lines.len(), 12, "owner banner is 12 lines");276 +    let mut saw_block = false;277      for line in &lines {278          assert!(line.chars().count() <= 60, "banner line too wide: {line:?}");279          for ch in line.chars() {280 -            saw_pipe |= ch == '|';281 -            saw_hyphen |= ch == '-';282 -            saw_slash |= ch == '/';283 +            assert!(!ch.is_ascii_alphabetic(), "no letters, got {ch:?}");284              assert!(285 -                matches!(ch, '|' | '-' | '/' | '_' | '+' | ' '),286 -                "sharp-corner aesthetic only (| - / _ + space), got {ch:?}"287 +                !matches!(ch, '<' | '>' | '&' | '"'),288 +                "nothing to escape, got {ch:?}"289 +            );290 +            saw_block |= matches!(ch, '█' | '▓' | '▒' | '░' | '▄' | '▀' | '▌' | '▐');291 +            assert!(292 +                matches!(293 +                    ch,294 +                    ' ' | '█'295 +                        | '▓'296 +                        | '▒'297 +                        | '░'298 +                        | '▄'299 +                        | '▀'300 +                        | '▌'301 +                        | '▐'302 +                        | '|'303 +                        | '-'304 +                        | '/'305 +                        | '_'306 +                        | '+'307 +                ),308 +                "block set + legacy (| - / _ + space), got {ch:?}"309              );310          }311      }312 -    assert!(313 -        saw_pipe && saw_hyphen && saw_slash,314 -        "uses pipes+hyphens+slashes"315 -    );316 +    assert!(saw_block, "uses block glyphs");317 +    assert!(art.contains('█'), "block fill present");318  }319  320  #[test]321 @@ -250,3 +265,58 @@ fn ascii_renders_inside_pre_unescaped_except_esc() {322      assert!(pre.contains("<pre>"), "inside <pre>");323      assert!(pre.contains(&art), "verbatim, unescaped-except-esc()");324  }325 +326 +/// Strip literal SGR codes (`[...m`, no ESC byte) the way the rubric327 +/// prescribes: `sed 's/\[[0-9;]*m//g'`.328 +fn strip_literal_sgr(s: &str) -> String {329 +    let bytes = s.as_bytes();330 +    let mut out = String::with_capacity(s.len());331 +    let mut i = 0;332 +    while i < bytes.len() {333 +        if bytes[i] == b'[' {334 +            let mut k = i + 1;335 +            while k < bytes.len() && (bytes[k].is_ascii_digit() || bytes[k] == b';') {336 +                k += 1;337 +            }338 +            if k < bytes.len() && bytes[k] == b'm' && k > i + 1 {339 +                i = k + 1;340 +                continue;341 +            }342 +        }343 +        let ch = s[i..].chars().next().expect("char boundary");344 +        out.push(ch);345 +        i += ch.len_utf8();346 +    }347 +    out348 +}349 +350 +#[test]351 +fn ansi_strips_to_ascii_byte_for_byte() {352 +    // Owner ANSI original (#26) strips to the plain banner byte-for-byte.353 +    let ansi = read_static("logo-ansi.txt");354 +    let ascii = read_static("ascii.txt");355 +    assert_eq!(strip_literal_sgr(&ansi), ascii, "strip(ansi) == ascii");356 +    assert_eq!(ansi.lines().count(), 12, "ansi is 12 lines");357 +    assert_eq!(ascii.lines().count(), 12, "ascii is 12 lines");358 +}359 +360 +#[test]361 +fn logo_ansi_is_esc_free_with_codes() {362 +    // Stored with literal `[...m` codes, block glyphs, NO raw ESC bytes.363 +    let ansi = read_static("logo-ansi.txt");364 +    assert!(!ansi.contains('\u{1b}'), "no raw ESC bytes stored");365 +    assert!(ansi.contains("[0;"), "holds SGR codes");366 +    assert!(ansi.contains("[0m"), "holds reset");367 +    assert!(ansi.contains('█'), "holds block glyphs");368 +}369 +370 +#[test]371 +fn static_holds_no_esc_bytes() {372 +    // Success rubric (#26): `grep -r $'\\x1b' static/` is empty.373 +    let dir = manifest().join("static");374 +    for name in ["logo-ansi.txt", "ascii.txt", "logo.svg", "favicon.svg"] {375 +        let bytes =376 +            std::fs::read(dir.join(name)).unwrap_or_else(|_| panic!("static/{name} exists"));377 +        assert!(!bytes.contains(&0x1b), "static/{name} holds no ESC bytes");378 +    }379 +}380 -- 381 2.55.0382