Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 121 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,84 @@
# d2lang-cs

![Banner](docs/assets/img/banner.png)
![d2lang-cs banner](docs/assets/img/banner.png)

An unofficial interface for building [D2](https://github.com/terrastruct/d2) diagram files in C# and dotnet.
`d2lang-cs` is an unofficial .NET library for building [D2](https://github.com/terrastruct/d2) source from strongly typed C# objects. It produces `.d2` text; it does not bundle the D2 renderer or invoke the D2 CLI.

# Installation
Use the library anywhere you need to construct, store, inspect, or send D2 source. Install the separate D2 CLI only when your application also needs to validate or render that source as SVG, PNG, or another output format.

## Install

```bash
dotnet add package d2lang-cs
```

# Usage
The package provides `netstandard2.0`, `net8.0`, and `net10.0` assets. Applications can therefore use the broad .NET Standard library surface or target the current supported .NET LTS releases directly.

## Quick start

```csharp
using d2;

var umbrella = new D2Shape("alphabet", "Alphabet Inc", Shape.Rectangle);
var company = new D2Shape("google", null, Shape.Rectangle);

company.Add(new D2Shape("gmail", "Gmail", Shape.Rectangle));
company.Add(new D2Shape("meet", "Meet", Shape.Rectangle));
company.Add(new D2Shape("deepmind", "DeepMind", Shape.Rectangle));

company.Icon = "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png";

var connection = new D2Connection(company.Name, umbrella.Name, Direction.To, "BELONGS_TO");

var diagram = new D2Diagram(new[] { umbrella, company }, new[] { connection });

Console.WriteLine(diagram.ToString());
var diagram = new D2DiagramBuilder()
.AddShape("client", "Client", Shape.Person)
.AddShape(
"api",
"API",
Shape.Rectangle,
new D2Style(Fill: "#f4a261", BorderRadius: 8))
.AddConnection("client", "api", label: "GET /users")
.Build();

var source = diagram.ToString();
Console.WriteLine(source);
```

# D2 Output
```d2-lang
alphabet: Alphabet Inc {
shape: rectangle
This produces:

```d2
client: Client {
shape: person
}
google: {
gmail: Gmail {
shape: rectangle
}
meet: Meet {
shape: rectangle
}
deepmind: DeepMind {
shape: rectangle
}
icon: https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png
api: API {
shape: rectangle
style: {
fill: "#f4a261"
border-radius: 8
}
}
google -> alphabet: BELONGS_TO
client -> api: GET /users
```

# Diagram Output
![Diagram](docs/assets/img/diagram.png)
To render it, write `source` to a file and use an independently installed D2 CLI:

# Documentation
```bash
d2 diagram.d2 diagram.svg
```

## Core model

A `D2Diagram` is an ordered collection of `D2Statement` values. Shapes, connections, comments, properties, composition boards, typed special shapes, and explicit raw statements all participate in that model. Statement order is retained, including inside containers and sequence diagrams.

The immutable `D2Diagram` API works well when composing values, while `D2DiagramBuilder` provides a mutable fluent path. Collection initializer syntax is supported by containers and typed special shapes.

```csharp
var service = new D2Shape("service", "User service", Shape.Rectangle)
{
new D2Shape("cache", "Cache", Shape.Cylinder),
new D2Comment("Connections remain in this exact position"),
new D2Connection("cache", "database", Direction.To, "miss"),
};

var diagram = new D2Diagram(new D2Statement[]
{
new D2Property("direction", "right"),
service,
});
```

## Typed diagrams
## Typed D2 features

Special D2 shapes have typed helpers while still participating in the ordered
`D2Statement` model:
SQL tables, UML classes, and sequence diagrams have dedicated helpers:

```csharp
var users = new D2SqlTable("users", "Users")
Expand All @@ -80,25 +98,27 @@ var service = new D2Class("user_service", "User service")
};

var request = new D2SequenceDiagram("request", "Find user")
.AddParticipant("client", "Client")
.AddParticipant("client", "Client", Shape.Person)
.AddParticipant("service", "Service")
.AddMessage("client", "service", "find(42)")
.AddMessage("service", "client", "User", Direction.From);

var diagram = new D2Diagram(new D2Statement[] { users, service, request });
```

All style arguments are optional and named arguments keep declarations compact:
All style settings are optional, so named arguments keep declarations compact:

```csharp
var api = new D2Shape(
"api",
Shape: Shape.Rectangle,
Style: new D2Style(
Stroke: "#e76f51",
Fill: "#f4a261",
Opacity: 0.9,
BorderRadius: 8,
Font: D2Font.Mono,
Animated: true))
Bold: true))
{
Link = "https://example.com/docs#api",
Tooltip = "Open API docs",
Expand All @@ -107,34 +127,66 @@ var api = new D2Shape(
};
```

## Supported
- [x] Shapes (nodes)
- [x] Connections (edges)
- [x] Full documented style catalog
- [x] Containers (nodes/links in nodes)
- [x] Arrow directions
- [x] Markdown / latex / block strings / code in shapes
- [x] Shape icons
- [x] Shape dimensions, links, and tooltips
- [x] Connection icons, styles, links, and tooltips
- [x] Typed SQL table shapes and constraints
- [x] Typed UML classes, members, parameters, and visibility
- [x] Ordered sequence-diagram helpers
- [x] Comments, root properties, composition boards, and raw escape hatches

# Inspiration & Thanks
- [Kreshnik/d2lang-js](https://github.com/Kreshnik/d2lang-js)
- [MrBlenny/py-d2](https://github.com/MrBlenny/py-d2)
## Safe serialization and raw D2

Public model values are serialized as data. Identifiers, labels, colors, URLs, tooltips, property values, and other user-controlled strings are quoted and escaped when D2 syntax requires it. Numeric values use invariant formatting, and dotted references preserve D2 path semantics while escaping each segment.

Use `D2Property` for safely serialized scalar or block properties:

```csharp
var config = new D2Property("vars", new D2Statement[]
{
new D2Property("d2-config", new D2Statement[]
{
new D2Property("theme-id", 300),
new D2Property("center", true),
}),
});
```

`D2RawStatement` is the deliberate escape hatch for syntax that is not yet modeled. Its contents are emitted without escaping, so create raw statements only from trusted source—not from user input.

```csharp
var import = new D2RawStatement("...@architecture.d2");
```

For Markdown, LaTeX, and other D2 block strings, use `D2Text`. It normalizes line endings and automatically lengthens the pipe delimiter when the contents would otherwise close the block.

# Thank me!
If you like what I'm doing and you would like to thank me, please consider:
## Supported features

<a href="https://www.buymeacoffee.com/stephanvs" target="_blank">
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee!" style="height: 60px !important;width: 217px !important;" >
</a>
- Shapes, nested containers, the modeled D2 shape kinds, icons, dimensions, links, tooltips, and relative placement
- Connections with every arrow direction, labels, icons, links, tooltips, styles, and ordered body properties
- The documented typed style catalog, including colors, opacity, patterns, typography, borders, and animation
- Markdown, LaTeX, code, and other formatted block strings
- Typed SQL tables, constraints, UML classes, members, parameters, and visibility
- Ordered sequence-diagram participants, messages, and groups
- Layers, scenarios, and steps through composition boards
- Comments and root or nested scalar/block properties
- An explicit raw-source escape hatch for advanced D2 syntax

Thank you for your support!
## Roadmap

The general statement/property model can already represent most D2 constructs. Planned work focuses on richer typed helpers for imports, variables, globs, and configuration; more typed sequence-diagram constructs; and deeper parser-backed compatibility coverage as D2 evolves.

Rendering and layout remain the responsibility of D2 itself. A renderer-process wrapper may be provided separately in the future, but it is intentionally outside the core source-model package.

## Development

Build and test with the .NET SDK selected by `global.json`:

```bash
dotnet restore
dotnet build --configuration Release
dotnet test --configuration Release
```

Parser-backed tests use `d2 validate` when the D2 CLI is available and are reported as inconclusive when it is not installed.

## Inspiration and thanks

- [Kreshnik/d2lang-js](https://github.com/Kreshnik/d2lang-js)
- [MrBlenny/py-d2](https://github.com/MrBlenny/py-d2)

<hr />
If this project is useful to you, you can [support its development](https://www.buymeacoffee.com/stephanvs).

Copyright &copy; 2023 [Stephan van Stekelenburg](https://stephanvs.com) - Provided under [MIT License](LICENSE)
Copyright © 2023–present Stephan van Stekelenburg. Provided under the [MIT License](LICENSE).
138 changes: 138 additions & 0 deletions src/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Intentional breaking changes for the 2.0 API. Keep baseline validation enabled so any additional breaks fail the build. -->
<!-- Diagnostic reference: https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:d2.Utils</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:d2.D2Connection.{Clone}$</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:d2.D2Diagram.Deconstruct(System.Collections.Generic.IEnumerable{d2.D2Shape}@,System.Collections.Generic.IEnumerable{d2.D2Connection}@)</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:d2.D2Diagram.set_Connections(System.Collections.Generic.IEnumerable{d2.D2Connection})</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:d2.D2Diagram.set_Shapes(System.Collections.Generic.IEnumerable{d2.D2Shape})</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:d2.D2Shape.{Clone}$</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:d2.D2Style.#ctor(System.String,System.Nullable{System.Int32},System.String,System.Nullable{System.Boolean},System.Nullable{System.Int32},System.Nullable{System.Int32},System.Nullable{System.Boolean})</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:d2.D2Style.Deconstruct(System.String@,System.Nullable{System.Int32}@,System.String@,System.Nullable{System.Boolean}@,System.Nullable{System.Int32}@,System.Nullable{System.Int32}@,System.Nullable{System.Boolean}@)</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:d2.D2Style.get_Opacity</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:d2.D2Text.{Clone}$</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>M:d2.D2Connection.get_EqualityContract</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>M:d2.D2Connection.PrintMembers(System.Text.StringBuilder)</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>M:d2.D2Shape.get_EqualityContract</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>M:d2.D2Shape.PrintMembers(System.Text.StringBuilder)</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>M:d2.D2Text.get_EqualityContract</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>M:d2.D2Text.PrintMembers(System.Text.StringBuilder)</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>P:d2.D2Connection.EqualityContract</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>P:d2.D2Shape.EqualityContract</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>P:d2.D2Text.EqualityContract</Target>
<Left>lib/net7.0/d2lang-cs.dll</Left>
<Right>lib/netstandard2.0/d2lang-cs.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
Loading