diff --git a/README.md b/README.md index 078d63d..02ada78 100644 --- a/README.md +++ b/README.md @@ -1,137 +1,201 @@ -# MarkupString +

+ MarkupString logo: a terminal prompt, an M, and three styled text runs +

+ +

MarkupString

+ +

+ Immutable, Unicode-aware styled text for terminals and the web.
+ Build once. Preserve every layer. Render anywhere. +

+ +

+ Build status + MarkupString NuGet version + NuGet downloads + Requires .NET 10 + Native AOT ready + Apache-2.0 license +

+ +

+ Quick start · + Packages · + Layout · + Documentation · + Contributing +

+ +--- + +`MarkupText` pairs a plain string with immutable, layered markup runs. The same value can render +as ANSI, HTML, Pueblo, MXP, BBCode, or plain text—and round-trip through JSON without discarding +markup a reader does not understand. + +```text + ┌─ ANSI + ├─ HTML +plain text + runs ┼─ MXP / Pueblo + ├─ BBCode + └─ plain text +``` -[![CI](https://github.com/SharpMUSH/MarkupString/actions/workflows/ci.yml/badge.svg)](https://github.com/SharpMUSH/MarkupString/actions/workflows/ci.yml) -[![MarkupString](https://img.shields.io/nuget/v/MarkupString?label=MarkupString)](https://www.nuget.org/packages/MarkupString) -[![MarkupString.Ansi](https://img.shields.io/nuget/v/MarkupString.Ansi?label=MarkupString.Ansi)](https://www.nuget.org/packages/MarkupString.Ansi) -[![MarkupString.Html](https://img.shields.io/nuget/v/MarkupString.Html?label=MarkupString.Html)](https://www.nuget.org/packages/MarkupString.Html) -[![MarkupString.Mxp](https://img.shields.io/nuget/v/MarkupString.Mxp?label=MarkupString.Mxp)](https://www.nuget.org/packages/MarkupString.Mxp) -[![MarkupString.Pueblo](https://img.shields.io/nuget/v/MarkupString.Pueblo?label=MarkupString.Pueblo)](https://www.nuget.org/packages/MarkupString.Pueblo) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) +String operations preserve those runs. Layout uses terminal display cells, understands wide CJK, +combining marks, and emoji sequences, and never cuts a grapheme cluster in half. -**Immutable styled text for terminals and the web.** One value — a plain string plus layered -markup runs over it — renders to ANSI, HTML, Pueblo, MXP, BBCode or plain text, and round-trips -through JSON without losing a layer the reader does not understand. +## One value, native output -```csharp -var text = MarkupText.Concat( - MarkupText.Plain("Hello, "), - MarkupText.Wrap(AnsiCodeParser.Parse("hr"), "world")); +Choose the renderer at the boundary. The underlying `MarkupText` stays the same while each target +gets the representation it understands best. -text.Render(MarkupFormat.Ansi); // Hello, \e[1;31mworld\e[0m -text.Render(MarkupFormat.Html); // Hello, world -text.Render(MarkupFormat.Plain); // Hello, world -``` +### Box drawing becomes browser structure + +The ANSI renderer emits exact terminal cells and SGR colour. The HTML renderer turns that same +block tree into responsive fieldsets, definitions, flex rows, rules, and meters. + +![The same MarkupString block rendered as ANSI box drawing and semantic HTML](docs/assets/showcase-box-drawing.png) + +### Images become useful terminal art + +A `Figure` sends text art and flowing copy to a terminal, then becomes a real image with alt text +and naturally flowing content in HTML. -Slicing, padding, wrapping, trimming and the rest of the string operations carry the markup with -them, and measure in **display cells** — wide CJK, combining marks and emoji sequences count -correctly, and no operation ever cuts a grapheme cluster in half. On top of them sits a column -layout engine: wrap, justify, fill, and assemble columns into aligned rows. +![The same MarkupString figure rendered as ANSI terminal art and an HTML image](docs/assets/showcase-image-drawing.png) -## Install +## Why MarkupString? + +- **One source of truth** — keep content and semantics together, then choose the output format at + the boundary. +- **Immutable values** — slicing, editing, wrapping, padding, and composition return new values + while preserving markup. +- **Unicode-correct layout** — distinguish UTF-16 length, grapheme count, and terminal display + width. +- **Extensible formats** — add an `IMarkup`, emitter, and codec through an explicit registry; no + runtime discovery or reflection. +- **Streaming output** — render directly to an `IBufferWriter` when allocating a string is + unnecessary. +- **Native AOT ready** — every package is trimming-safe and free of reflection and dynamic code. + +## Quick start + +Install the core model and the markup kinds you need: ```sh dotnet add package MarkupString dotnet add package MarkupString.Ansi +``` + +Register those kinds once at startup, build a value, and render it explicitly: + +```csharp +using MarkupString; +using MarkupString.Ansi; + +MarkupRegistry.Default = MarkupRegistry.Empty.WithAnsi(); + +var greeting = MarkupText.Concat( +[ + MarkupText.Plain("Hello, "), + MarkupText.Wrap(AnsiCodeParser.Parse("hr"), "world"), + MarkupText.Plain("!") +]); + +greeting.Render(MarkupFormat.Ansi); // Hello, \e[1;31mworld\e[0m! +greeting.Render(MarkupFormat.Html); // Hello, world! +greeting.Render(MarkupFormat.Plain); // Hello, world! +greeting.ToString(); // Hello, world! — always plain text +``` + +Need a buffer instead of a new string? + +```csharp +greeting.RenderTo(MarkupFormat.Ansi, bufferWriter); +``` + +See [Getting started](docs/getting-started.md) for registry lifetime, direct style construction, +parsing existing ANSI, and serialization. + +## Packages + +Choose only the markup kinds your application uses. The core package deliberately has no +rendering opinions. + +| Package | Purpose | +|---|---| +| [`MarkupString`](https://www.nuget.org/packages/MarkupString) | `MarkupText`, runs, formats, registry and emitter contracts, JSON serialization, Unicode helpers, layout, and the shared sound/image/pane/gauge vocabulary. | +| [`MarkupString.Ansi`](https://www.nuget.org/packages/MarkupString.Ansi) | ANSI colours and attributes, links, code and escape-sequence parsers, multi-format emitters, and the matching HTML stylesheet. | +| [`MarkupString.Html`](https://www.nuget.org/packages/MarkupString.Html) | Checked raw HTML markup with configurable tag policies for trusted and untrusted input. | +| [`MarkupString.Mxp`](https://www.nuget.org/packages/MarkupString.Mxp) | The shared vocabulary expressed as client-supported MXP elements. | +| [`MarkupString.Pueblo`](https://www.nuget.org/packages/MarkupString.Pueblo) | The shared vocabulary expressed through Pueblo client extensions. | + +```sh dotnet add package MarkupString.Html dotnet add package MarkupString.Mxp dotnet add package MarkupString.Pueblo ``` -| Package | What it gives you | -|---|---| -| [`MarkupString`](https://www.nuget.org/packages/MarkupString) | The `MarkupText` type, runs, formats, the registry, the emitter/codec contracts, the JSON serializer, grapheme and display-width helpers, and the shared vocabulary — sounds, pictures, panes, gauges — that the format packages write. No rendering opinions. | -| [`MarkupString.Ansi`](https://www.nuget.org/packages/MarkupString.Ansi) | Terminal styling: colours (16 / xterm-256 / truecolor), attributes, links; an `ansi()` code parser and an escape-sequence parser; emitters for ANSI, HTML, Pueblo, MXP and BBCode; and `AnsiCss`, the stylesheet for the `ms-*` classes the HTML emitters write. | -| [`MarkupString.Html`](https://www.nuget.org/packages/MarkupString.Html) | Raw HTML tag markup — an anchor, a `
`, a `` — with checked construction and tag policies for untrusted input; the shared vocabulary for a browser. |
-| [`MarkupString.Mxp`](https://www.nuget.org/packages/MarkupString.Mxp) | The shared vocabulary as MXP's elements, held to what the client said it supports. |
-| [`MarkupString.Pueblo`](https://www.nuget.org/packages/MarkupString.Pueblo) | The shared vocabulary in the Pueblo client's own extensions. |
+All packages share one version and are released together.
 
-The core package renders nothing on its own: emitters live in the kind packages, so a consumer
-that only needs one of them pays for one of them, and a kind of your own is a first-class peer
-rather than a fork.
+## Unicode-aware layout
 
-## Getting started
+MarkupString keeps three measurements separate because they answer different questions:
 
-```csharp
-using MarkupString;
-using MarkupString.Ansi;
-using MarkupString.Html;
-using MarkupString.Mxp;
-using MarkupString.Pueblo;
-
-// Once, at startup. Set-once: a second, different registry throws.
-MarkupRegistry.Default = MarkupRegistry.Empty.WithAnsi().WithHtml().WithMxp().WithPueblo();
-
-// A command link: each format writes it in its own dialect —  for Pueblo,
-//  for MXP, a clickable anchor for HTML — and a terminal shows the text.
-var prompt = MarkupText.Wrap(
-  AnsiMarkup.Create(linkUrl: "north", linkKind: LinkKind.Command),
-  MarkupText.Wrap(AnsiCodeParser.Parse("hc"), "Go north"));
-
-Console.WriteLine(prompt.Render(MarkupFormat.Ansi));
-
-// A sound and a picture, said once. MXP gets  and , Pueblo its xch_ tags, a browser
-//