Markout 0.10.0

dotnet add package Markout --version 0.10.0
                    
NuGet\Install-Package Markout -Version 0.10.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Markout" Version="0.10.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Markout" Version="0.10.0" />
                    
Directory.Packages.props
<PackageReference Include="Markout" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Markout --version 0.10.0
                    
#r "nuget: Markout, 0.10.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Markout@0.10.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Markout&version=0.10.0
                    
Install as a Cake Addin
#tool nuget:?package=Markout&version=0.10.0
                    
Install as a Cake Tool

Markout

Markup adds instructions to content. Markout removes structure from data.

Markout is a source-generated .NET serializer that projects objects into human-readable documents instead of data formats like JSON. You annotate your models with attributes that describe data relationships — identity, enumeration, tabulation, measurement, hierarchy — and the source generator emits code that writes through an abstract renderer. The same object graph produces Markdown tables, ANSI terminal output with colored bars, plain text with aligned columns, or one-line summaries, without the developer making visual decisions.

Quick Start

using Markout;

var artist = new Artist(
    Name: "Sarah McLachlan",
    Genre: "Pop / Adult Contemporary",
    Origin: "Halifax, Nova Scotia",
    DebutYear: 1988,
    BestKnownFor: "Angel, Building a Mystery, Adia");

MarkoutSerializer.Serialize(artist, Console.Out, ArtistContext.Default);

[MarkoutSerializable(TitleProperty = nameof(Artist.Name))]
public record Artist(
    string Name,
    string Genre,
    string Origin,
    int DebutYear,
    string BestKnownFor);

[MarkoutContext(typeof(Artist))]
public partial class ArtistContext : MarkoutSerializerContext { }

Output:

# Sarah McLachlan

| Field | Value |
| ----- | ----- |
| Genre | Pop / Adult Contemporary |
| Origin | Halifax, Nova Scotia |
| Debut Year | 1988 |
| Best Known For | Angel, Building a Mystery, Adia |

Three things: a record, a context, one line of serialization. The TitleProperty becomes a heading; everything else renders as a field table.

Field Layouts

The same model renders differently with FieldLayout. The default is a two-column table. Switch to Inline for a compact summary line:

[MarkoutSerializable(TitleProperty = nameof(Artist.Name), FieldLayout = FieldLayout.Inline)]
public record Artist( ... );
# Sarah McLachlan

Genre: Pop / Adult Contemporary | Origin: Halifax, Nova Scotia | Debut Year: 1988 | Best Known For: Angel, Building a Mystery, Adia

Or Bulleted for a list:

[MarkoutSerializable(TitleProperty = nameof(Artist.Name), FieldLayout = FieldLayout.Bulleted)]
public record Artist( ... );
# Sarah McLachlan

- Genre: Pop / Adult Contemporary
- Origin: Halifax, Nova Scotia
- Debut Year: 1988
- Best Known For: Angel, Building a Mystery, Adia

Or Numbered:

# Sarah McLachlan

1. Genre: Pop / Adult Contemporary
2. Origin: Halifax, Nova Scotia
3. Debut Year: 1988
4. Best Known For: Angel, Building a Mystery, Adia

Or Plain — bare lines, no markers. Each line ends with two trailing spaces, which is the markdown signal for <br>:

# Sarah McLachlan

Genre: Pop / Adult Contemporary  
Origin: Halifax, Nova Scotia  
Debut Year: 1988  
Best Known For: Angel, Building a Mystery, Adia  

The data model doesn't change — only the attribute controls the shape.

Adding Sections and Tables

Add [MarkoutSection] to group properties with headings, and use List<T> for tables:

[MarkoutSerializable(TitleProperty = nameof(Title))]
public class CityReport
{
    public string Title { get; set; } = "";
    public string Province { get; set; } = "";
    public int Population { get; set; }

    [MarkoutSection(Name = "Landmarks")]
    public List<LandmarkRow>? Landmarks { get; set; }
}

[MarkoutSerializable]
public class LandmarkRow
{
    public string Name { get; set; } = "";
    public string Type { get; set; } = "";
    public int Year { get; set; }
}

[MarkoutContext(typeof(CityReport))]
[MarkoutContext(typeof(LandmarkRow))]
public partial class ReportContext : MarkoutSerializerContext { }

MarkoutSerializer.Serialize(city, Console.Out, ReportContext.Default);

Output:

# Vancouver

| Field | Value |
| ----- | ----- |
| Province | British Columbia |
| Population | 2632000 |

## Landmarks

| Name | Type | Year |
| ----- | ----- | ----- |
| Stanley Park | Park | 1888 |
| Gastown | Historic | 1867 |
| Science World | Museum | 1989 |

Real-World Example: GitHub Repository Report

The GitHubRepo sample fetches four GitHub API endpoints in parallel and projects the combined JSON into a single report — fields, bar charts, contributor metrics, and release tables — all from one model:

[MarkoutSerializable(TitleProperty = nameof(Title), DescriptionProperty = nameof(Description))]
public class RepoInfo
{
    public string Title { get; set; } = "";

    [MarkoutIgnore]
    public string Description { get; set; } = "";

    [MarkoutDisplayFormat("{0:N0}")]
    public int Stars { get; set; }

    [MarkoutDisplayFormat("{0:N0}")]
    public int Forks { get; set; }

    [MarkoutDisplayFormat("{0:N0}")]
    public int OpenIssues { get; set; }

    public string Language { get; set; } = "";
    public string License { get; set; } = "";

    [MarkoutIgnoreInTable]
    [MarkoutSkipDefault]
    public Callout ArchivedWarning { get; set; }

    [MarkoutSection(Name = "Languages")]
    [MarkoutIgnoreInTable]
    public List<Breakdown>? Languages { get; set; }

    [MarkoutSection(Name = "Top Contributors")]
    [MarkoutIgnoreInTable]
    public List<Metric>? TopContributors { get; set; }

    [MarkoutSection(Name = "Releases")]
    public List<ReleaseRow>? Releases { get; set; }
}

Run it:

dotnet run samples/GitHubRepo/GitHubRepo.cs                                     # ANSI terminal (default)
dotnet run samples/GitHubRepo/GitHubRepo.cs -- dotnet/runtime --format markdown # Markdown
dotnet run samples/GitHubRepo/GitHubRepo.cs -- dotnet/runtime --format oneline  # Compact table

Markdown output for dotnet/runtime:

# dotnet/runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.

| Field | Value |
| ----- | ----- |
| Stars | 17,703 |
| Forks | 5,353 |
| Open Issues | 8,371 |
| Language | C# |
| License | MIT License |

## Languages

| Category | Count | % |
| -------- | ----- | - |
| C# | 80 | 83 |
| C++ | 9 | 9 |
| C | 7 | 7 |

## Top Contributors

| Label | Value |
| ----- | ----- |
| vargaz | 11910 |
| stephentoub | 10418 |
| kumpera | 4074 |
| jkotas | 3245 |

## Releases

| Tag | Name | Published |
| --- | ---- | --------- |
| v8.0.24 | .NET 8.0.24 | 2026-02-10 |
| v9.0.13 | v9.0.13 | 2026-02-10 |
| v10.0.3 | .NET 10.0.3 | 2026-02-10 |

One-line output — same model, --oneline flag:

FIELD        VALUE
Stars        17,703
Forks        5,353
Open Issues  8,371
Language     C#
License      MIT License

TAG      NAME         PUBLISHED
v8.0.24  .NET 8.0.24  2026-02-10
v9.0.13  v9.0.13      2026-02-10
v10.0.3  .NET 10.0.3  2026-02-10

The pattern is always the same: deserialize your API data, project to a model, serialize. Markout handles the rest.

Shape Library

Each property on a model maps to a data relationship, not a visual element. Renderers decide how to present each shape.

Relationship C# type What it means Markdown ANSI
Identity string, int, bool Named value Key: value Bold key, value
Enumeration string[] Sequence of items - item Bullet list
Tabulation List<T> Uniform records \| col \| col \| Space-padded table
Section [MarkoutSection] Logical grouping ## Heading Colored heading
Description List<Description> Terms with explanations - **Term:** text Bold term, text
Measurement List<Metric> Comparative quantities Label ████░░ 45 Colored bars
Composition List<Breakdown> Parts of a whole ██▓▓▒░ stacked Colored segments
Hierarchy List<TreeNode> Parent-child structure ├── node Box-drawing tree
Quotation CodeSection Verbatim content ```code``` Syntax display
Attention Callout Important messages > [!WARNING] Colored label

Plus structural shapes: Quotation (prose quotation), Matrix (2D pivot grid), Rule (section separator).

Record Types

Shapes that need structured input provide record types named for what the data is, not what it looks like:

new Metric("Build Time", 4.2)                                    // measurement
new Description("dotnet-inspect", "API surface inspection tool")  // term + explanation
new Breakdown("Jan 2025", [new("Critical", 1), new("High", 3)])  // proportional composition
new Callout(CalloutSeverity.Warning, "3 vulnerabilities found")   // attention
new CodeSection("csharp", "public class Foo { }")                 // verbatim content

Renderers

Markout ships four formatters. The serializer writes through MarkoutWriter (the orchestrator) — swap the formatter, change the output.

Formatter Output Use case
MarkdownFormatter GitHub-Flavored Markdown Documentation, LLM tool output, rendered reports
UnicodeFormatter Plain text, space-padded Log files, piped output, terminals without ANSI
OneLineFormatter Tables only, no headings Compact summaries, grep-friendly output
DiagramFormatter Trees and structural diagrams Dependency graphs, file trees

Optional packages:

Package Formatter Use case
Markout.Ansi AnsiFormatter Colored terminal output with bold, gradients
Markout.Ansi.Spectre SpectreFormatter Rich terminal UI via Spectre.Console

Formatters declare which shapes they support by implementing capability interfaces (ITableFormatter, IFieldFormatter, ITreeFormatter, etc.). Unsupported shapes are silently skipped — the data is never lost, only the visual sophistication changes.

Templates

Templates let you author document structure in Markdown and fill data slots at runtime. The same template renders to any format — Markdown, plain text, or ANSI terminal.

template.md:

# .NET Security Report for {{date}}

The following vulnerabilities were disclosed this month.

{{vuln-table}}

| Level | CVSS Range | Response |
| ----- | ---------- | -------- |
| Critical | 9.0–10.0 | Patch immediately |
| High | 7.0–8.9 | Patch within 30 days |

{{#if commits}}
## Commits

{{commit-table}}
{{/if}}

Program.cs:

using Markout;
using Markout.Templates;

var template = MarkoutTemplate.Load("template.md");
template.TableOptions = new TableFormatterOptions(); // smooth column widths

template.Bind("date", "February 2026");
template.Bind("vuln-table", vulnerabilityData);  // IMarkoutFormattable
template.Bind("commits", hasCommits ? "yes" : null);
template.Bind("commit-table", commitData);

// Markdown output
Console.WriteLine(template.Render(new MarkoutWriterOptions { PrettyTables = true }));

// Plain text output — same template, different formatter
var plainWriter = new MarkoutWriter(Console.Out, new UnicodeFormatter());
template.Render(plainWriter);

Templates support:

  • {{key}} — inline substitution in headings and prose, or block-level data rendering
  • {{#if key}} / {{/if}} — conditional sections (included when key is bound and non-null)
  • Pipe tables — parsed and re-rendered through the writer's table shape, with optional statistical column-width optimization
  • IMarkoutFormattable and MarkoutTypeInfo<T> bindings for shape-aware data rendering
dotnet add package Markout.Templates

Template Runner Tool

A basic CLI tool is included for rendering templates with string bindings:

dotnet run --project tools/markout-template -- template.md date="February 2026" title="Report"

Unbound block placeholders are skipped, so you can render partial templates. Pipe key=value lines on stdin for additional bindings.

Customization Layers

Markout provides multiple layers of control, from zero-config to full custom:

Layer 1 — Attributes (compile-time): Control what's rendered and how.

[MarkoutPropertyName("Born")]          // rename a field
[MarkoutSkipNull]                      // hide when null
[MarkoutSection(Name = "Details")]     // group into a section
[MarkoutDisplayFormat("N0")]           // format numbers
[MarkoutShowWhen(nameof(HasDetails))]  // conditional rendering
[MarkoutMaxItems(10)]                  // truncate long lists
[MarkoutValueMap("k=badge", ...)]      // map values to badge-prefixed output
[MarkoutUnwrap]                        // inline collection items without section heading
[MarkoutIgnoreColumnWhen(...)]         // conditionally hide table columns

Layer 2 — Writer Options (runtime): Control which sections appear.

var options = new MarkoutWriterOptions
{
    IncludeSections = new HashSet<string> { "Summary", "Errors" },  // only these sections
    BoldFieldNames = true
};
MarkoutSerializer.Serialize(view, Console.Out, new MarkdownFormatter(), context, options);

Layer 3 — Custom Formatter (code): Implement capability interfaces for custom rendering.

public class MyFormatter : IMarkoutFormatter, IFieldFormatter, ITableFormatter
{
    // Implement only the interfaces your formatter supports
    void IFieldFormatter.FormatFieldName(TextWriter w, string key, bool bold) { ... }
    void ITableFormatter.FormatTable(TextWriter w, ReadOnlySpan<string> headers, IList<string[]> rows, int skippedRows, MarkoutWriterOptions options) { ... }
}

Installation

dotnet add package Markout

The package includes the source generator — no additional packages needed.

Samples

  • HelloMarkout — Simplest possible example: a class, a context, one line of serialization
  • RecordDemo — Records as data models
  • GitHubRepo — GitHub API → fields, breakdowns, metrics, and tables with Spectre/Markdown/OneLineFormatter output
  • GitHubActivity — User profile and recent events from the GitHub API
  • CanadianContent — Canadian actors and shows with tables, trees, metrics, and multiple renderers
  • LatestCves — .NET security advisories with trees and severity breakdowns
  • DotNetReleases — .NET release information from GitHub
  • Serialization — Shape gallery, section filtering, and writer API examples
  • TemplateDemo — Document template with inline tables, conditional sections, and multi-format rendering

For Coding Agents

If you are an LLM or coding agent building a CLI tool that needs structured, readable output, see SKILL.md for step-by-step integration instructions and attribute reference.

Real-World Usage

Markout was created for dotnet-inspect, which uses all ten data relationships across 49 models to generate API inspection reports, diff analysis, dependency trees, and security summaries.

Documentation

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Markout:

Package Downloads
Markout.Templates

Template engine for Markout — compose human-authored documents with data-driven content

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on Markout:

Repository Stars
richlander/dotnet-inspect
Tool to inspect .NET assets, like docker inspect.
Version Downloads Last Updated
0.10.0 299 3/11/2026
0.9.7 97 2/25/2026
0.9.6 88 2/25/2026
0.9.5 113 2/25/2026
0.9.4 94 2/25/2026
0.9.3 102 2/24/2026
0.9.2 101 2/24/2026
0.9.1 82 2/24/2026
0.9.0 167 2/24/2026
0.8.1 162 2/23/2026
0.8.0 84 2/23/2026
0.7.3 89 2/22/2026
0.7.2 108 2/22/2026
0.7.1 918 2/19/2026
0.7.0 292 2/17/2026
0.6.1 266 2/17/2026
0.6.0 92 2/17/2026
0.5.1 2,318 2/9/2026
0.5.0 100 2/9/2026
0.4.0 130 2/9/2026
Loading failed