Markout.Templates
0.1.1
dotnet add package Markout.Templates --version 0.1.1
NuGet\Install-Package Markout.Templates -Version 0.1.1
<PackageReference Include="Markout.Templates" Version="0.1.1" />
<PackageVersion Include="Markout.Templates" Version="0.1.1" />
<PackageReference Include="Markout.Templates" />
paket add Markout.Templates --version 0.1.1
#r "nuget: Markout.Templates, 0.1.1"
#:package Markout.Templates@0.1.1
#addin nuget:?package=Markout.Templates&version=0.1.1
#tool nuget:?package=Markout.Templates&version=0.1.1
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 view 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 ArtistView(
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(ArtistView.Name))]
public record ArtistView(
string Name,
string Genre,
string Origin,
int DebutYear,
string BestKnownFor);
[MarkoutContext(typeof(ArtistView))]
public partial class ArtistContext : MarkoutSerializerContext { }
Output:
# Sarah McLachlan
Genre: Pop / Adult Contemporary | Origin: Halifax, Nova Scotia | DebutYear: 1988 | BestKnownFor: Angel, Building a Mystery, Adia
Three things: a record, a context, one line of serialization. The TitleProperty becomes a heading; everything else renders as fields.
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
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 view model:
[MarkoutSerializable(TitleProperty = nameof(Title), DescriptionProperty = nameof(Description))]
[MarkoutIgnoreFields(nameof(OneLineWriter))]
public class RepoView
{
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; }
[MarkoutPropertyName("Open Issues")]
[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.
Stars: 17,698 | Forks: 5,350 | Open Issues: 8,385 | Language: C# | License: MIT License
## Languages
| Category | Count | % |
| -------- | ----- | - |
| C# | 80 | 83 |
| C++ | 9 | 9 |
| C | 7 | 7 |
## Top Contributors
| Label | Value |
| ----------- | ----- |
| vargaz | 11910 |
| stephentoub | 10417 |
| kumpera | 4074 |
| jkotas | 3244 |
## Releases
| Tag | Name | Published |
| ------- | ----------- | ---------- |
| v10.0.3 | .NET 10.0.3 | 2026-02-10 |
| v10.0.2 | .NET 10.0.2 | 2026-01-14 |
| v9.0.13 | v9.0.13 | 2026-02-10 |
One-line output — same view model, --oneline flag, shows the Releases table only:
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 view model, serialize. Markout handles the rest.
Shape Library
Each property on a view 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 renderers. The serializer writes through MarkoutWriter — swap the writer, change the output.
| Renderer | Output | Use case |
|---|---|---|
| MarkdownWriter | GitHub-Flavored Markdown | Documentation, LLM tool output, rendered reports |
| MarkoutWriter | Plain text, space-padded | Log files, piped output, terminals without ANSI |
| OneLineWriter | Tables only, no headings | Compact summaries, grep-friendly output |
| DiagramWriter | Trees and structural diagrams | Dependency graphs, file trees |
Optional packages:
| Package | Renderer | Use case |
|---|---|---|
| Markout.Ansi | AnsiWriter |
Colored terminal output with bold, gradients |
| Markout.Ansi.Spectre | SpectreWriter |
Rich terminal UI via Spectre.Console |
Renderers declare which shapes they support via SupportedShapes. 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 writer
var plainWriter = new MarkoutWriter();
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
IMarkoutFormattableandMarkoutTypeInfo<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
};
var writer = new MarkdownWriter(Console.Out, options);
Layer 3 — Renderer Subclass (code): Override any shape for custom visual treatment.
public class MyWriter : MarkdownWriter
{
protected override void WriteDescription(Description item)
{
// Custom rendering for descriptions
Writer.WriteLine($"{item.Term}: {item.Text}");
}
}
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 view models
- GitHubRepo — GitHub API → fields, breakdowns, metrics, and tables with Spectre/Markdown/OneLineWriter 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 view models to generate API inspection reports, diff analysis, dependency trees, and security summaries.
Documentation
- User Guide — Complete tutorial with attribute reference
- Shape System Design — Data projection model, shape tiers, admission criteria
- Specification — Format grammar and type inference rules
- Nested Lists Guide — Strategies for nested data structures
License
MIT
| Product | Versions 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. |
-
net10.0
- MarkdownTable.Formatting (>= 0.3.1)
- Markout (>= 0.9.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.