supertext

Crates.iosupertext
lib.rssupertext
version
sourcesrc
created_at2025-04-21 05:46:52.506946+00
updated_at2025-04-24 09:23:35.849574+00
descriptionhtml document tools
homepage
repositoryhttp://git.iximeow.net/supertext/
max_upload_size
id1642331
Cargo.toml error:TOML parse error at line 18, column 1 | 18 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include`
size0
iximeow (iximeow)

documentation

README

supertext

hypertext, a bit less hyper

i want to construct HTML documents in some Rust code. i don't want a full templating solution. i just want to append some tags and not forget to escape user-provided input.

you probably don't want to use this, but if you do it should work. public interfaces are basically expected to break. it is 0.0.1 for a reason.

example

pub fn example(items: &[(String, String)]) -> Result<String, std::fmt::Error> {
    let mut out = String::new();
    let mut writer = HtmlWriter::new(&mut out);

    let mut html = HtmlSink::root(&mut writer);

    {
        let mut style = html.open_tag(style());
        style.rule(".table { font-family: monospace; border: 1px solid black; }")?;
        style.rule(".row-item { padding-left: 4px; padding-right: 4px; border-right: 1px solid black; }")?;
        style.rule(".odd-row { background: #eee; }")?;
        style.rule(".even-row { background: #ddd; }")?;
    }

    {
        let mut header = html.open_tag(h1());
        header.text("an example page that is super cool")?;
    }

    {
        let mut description = html.open_tag(p());
        match items.len() {
            0 => description.text("no items")?,
            1 => description.text("one item")?,
            o => description.text(&format!("{} items", o))?,
        };
    }

    let mut table = html.open_tag(table().class("table"));
    {
        let mut header = table.header();
        let th = th().class("row-item");
        let mut add_cell = |name: &'static str| header.open_tag(th.clone()).text(name);
        add_cell("key")?;
        add_cell("value")?;
    }

    let td = td().class("row-item");

    for (i, item) in items.iter().enumerate() {
        let mut row = table.row(tr().class(["even-row", "odd-row"][i % 2]));

        {
            let mut elem = row.open_tag(td.clone());
            elem.open_tag(a().href(&format!("/{}", item.0))).text(&item.0)?;
        }
        {
            row.open_tag(td.clone()).text(&item.1)?;
        }
    }

    table.close();

    html.close();

    Ok(out)
}

non-goals

this crate is intended to avoid trivially injectable HTML document construction. it is not intended to prevent semantically incoherent HTML documents. yet?

for example, this crate allows creating a table with twelve columns in its header, but varying (and not-twelve) rows afterwards. this crate allows tags to reference styles that are undefined. this crate allows intra-page links that are invalid. this crate allows syntactically invalid CSS.

TODO

  • element nesting could be more principled: sections like phrasing content in the HTML spec draw a clearer line about what elements can be nested.
  • really want to fuzz document construction and ensure no inputs produce different trees when parsed than when constructed
  • really want to be able to check if strings need escaping at compile time rather than runtime
  • for strings that need runtime escaping, would really like to not reallocate for replaced strings - retaining borrows of original data and not producing interim replaced copies would be nice. maybe something like a rope instead? or views on the underlying string with buffers of replacements to be evaluated when rendered out.
  • what to do with css rules? each rule gets a newline currently, is that ok?
  • controlling indentation for paragraphs? p.text() lines always just appends escaped text to the writer, maybe those should be spaced with newlines? should text() automatically append a line break?

related work (and things this is not)

build_html looks good, almost is what i want! unfortunately:

Note that escaping strings is also not automatic. You should use the escape_html function if you are displaying untrusted text.

the API was already wanted more ceremony than i was happy with, but this moves it out of the running.

html is comprehensive, but perhaps too much so.

the docs surface area is roughly the product of all kinds of tags by all kinds of tags, to the point i have a bit of a difficult time with navigation. it includes lots of HTML spec that is probably interesting and meaningful if i was doing interesting things with HTML, but i'm not.

needing to set #![recursion_limit = "512"] when depending on this crate is another papercut; between that and its idleness, i'm OK passing here.

maud (book) seems good, but i don't want a template engine, i just want to put a few divs and spans together. more importantly, i want to be able to render elements of a page potentially independently. having to construct an entire HTML document in one go is not great, and having to learn a new syntax for control flow in the document is the opposite direction of what i want.

Commit count: 0

cargo fmt