diff --git a/README.md b/README.md index 3f83fb5..43e7c00 100644 --- a/README.md +++ b/README.md @@ -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") @@ -80,7 +98,7 @@ 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); @@ -88,17 +106,19 @@ var request = new D2SequenceDiagram("request", "Find user") 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", @@ -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 - -Buy Me A Coffee! - +- 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) -
+If this project is useful to you, you can [support its development](https://www.buymeacoffee.com/stephanvs). -Copyright © 2023 [Stephan van Stekelenburg](https://stephanvs.com) - Provided under [MIT License](LICENSE) +Copyright © 2023–present Stephan van Stekelenburg. Provided under the [MIT License](LICENSE). diff --git a/src/CompatibilitySuppressions.xml b/src/CompatibilitySuppressions.xml new file mode 100644 index 0000000..3259629 --- /dev/null +++ b/src/CompatibilitySuppressions.xml @@ -0,0 +1,138 @@ + + + + + + CP0001 + T:d2.Utils + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0002 + M:d2.D2Connection.{Clone}$ + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0002 + M:d2.D2Diagram.Deconstruct(System.Collections.Generic.IEnumerable{d2.D2Shape}@,System.Collections.Generic.IEnumerable{d2.D2Connection}@) + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0002 + M:d2.D2Diagram.set_Connections(System.Collections.Generic.IEnumerable{d2.D2Connection}) + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0002 + M:d2.D2Diagram.set_Shapes(System.Collections.Generic.IEnumerable{d2.D2Shape}) + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0002 + M:d2.D2Shape.{Clone}$ + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0002 + 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}) + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0002 + 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}@) + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0002 + M:d2.D2Style.get_Opacity + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0002 + M:d2.D2Text.{Clone}$ + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0012 + M:d2.D2Connection.get_EqualityContract + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0012 + M:d2.D2Connection.PrintMembers(System.Text.StringBuilder) + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0012 + M:d2.D2Shape.get_EqualityContract + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0012 + M:d2.D2Shape.PrintMembers(System.Text.StringBuilder) + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0012 + M:d2.D2Text.get_EqualityContract + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0012 + M:d2.D2Text.PrintMembers(System.Text.StringBuilder) + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0012 + P:d2.D2Connection.EqualityContract + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0012 + P:d2.D2Shape.EqualityContract + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + + CP0012 + P:d2.D2Text.EqualityContract + lib/net7.0/d2lang-cs.dll + lib/netstandard2.0/d2lang-cs.dll + true + + diff --git a/src/D2Board.cs b/src/D2Board.cs index 5c4e91d..2d2d923 100644 --- a/src/D2Board.cs +++ b/src/D2Board.cs @@ -9,15 +9,22 @@ public sealed record D2Board : D2Statement, IEnumerable { private readonly List _statements; + /// Gets the board name. public string Name { get; } + /// Gets the statements in their serialization order. public IReadOnlyList Statements => _statements; + /// Initializes an empty board. + /// The board name. public D2Board(string name) : this(name, Array.Empty()) { } + /// Initializes a board with an ordered statement collection. + /// The board name. + /// The statements to add to the board. public D2Board(string name, IEnumerable statements) { Name = ValidateName(name); @@ -33,6 +40,8 @@ public D2Board(string name, IEnumerable statements) } } + /// Adds a statement to the end of the board. + /// The statement to add. public void Add(D2Statement statement) { if (statement is null) @@ -46,8 +55,10 @@ public void Add(D2Statement statement) internal override IEnumerable Lines() => D2Writer.BlockIdentifier(Name, _statements.SelectMany(statement => statement.Lines())); + /// public override string ToString() => string.Join(Environment.NewLine, Lines()); + /// public IEnumerator GetEnumerator() => _statements.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/src/D2BoardCollection.cs b/src/D2BoardCollection.cs index 4cf66e7..e5fd63f 100644 --- a/src/D2BoardCollection.cs +++ b/src/D2BoardCollection.cs @@ -2,10 +2,14 @@ namespace d2; +/// Identifies a D2 composition-board collection. public enum D2BoardKind { + /// A collection of alternate diagram layers. Layers, + /// A collection of scenarios based on a board. Scenarios, + /// An ordered collection of board steps. Steps, } @@ -16,15 +20,22 @@ public sealed record D2BoardCollection : D2Statement, IEnumerable { private readonly List _boards; + /// Gets the kind of boards contained by this collection. public D2BoardKind Kind { get; } + /// Gets the boards in their serialization order. public IReadOnlyList Boards => _boards; + /// Initializes an empty board collection. + /// The collection kind. public D2BoardCollection(D2BoardKind kind) : this(kind, Array.Empty()) { } + /// Initializes a collection with a sequence of boards. + /// The collection kind. + /// The boards to include. public D2BoardCollection(D2BoardKind kind, IEnumerable boards) { if (!Enum.IsDefined(typeof(D2BoardKind), kind)) @@ -45,6 +56,8 @@ public D2BoardCollection(D2BoardKind kind, IEnumerable boards) } } + /// Adds a board to the end of the collection. + /// The board to add. public void Add(D2Board board) { if (board is null) @@ -58,8 +71,10 @@ public void Add(D2Board board) internal override IEnumerable Lines() => D2Writer.Block(Keyword(Kind), _boards.SelectMany(board => board.Lines())); + /// public override string ToString() => string.Join(Environment.NewLine, Lines()); + /// public IEnumerator GetEnumerator() => _boards.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/src/D2Class.cs b/src/D2Class.cs index 17a5b22..ada8e0b 100644 --- a/src/D2Class.cs +++ b/src/D2Class.cs @@ -24,6 +24,8 @@ public sealed record D2ClassParameter public string Type { get; } /// Creates a class method parameter. + /// The parameter name. + /// The parameter type displayed by D2. public D2ClassParameter(string name, string type) { _ = D2Writer.Identifier(name); @@ -91,6 +93,9 @@ public sealed class D2ClassField : D2ClassMember public string? Type { get; } /// Creates a class field. + /// The field name. + /// The optional field type. + /// The UML visibility prefix. public D2ClassField(string name, string? type = null, D2Visibility visibility = D2Visibility.Default) : base(visibility) { @@ -114,6 +119,10 @@ public sealed class D2ClassMethod : D2ClassMember public IReadOnlyList Parameters { get; } /// Creates a class method. + /// The method name. + /// The optional return type; represents void. + /// The UML visibility prefix. + /// The method parameters in display order. public D2ClassMethod( string name, string? returnType = null, @@ -158,6 +167,8 @@ public sealed record D2Class : D2Statement, IEnumerable public IReadOnlyList Members => _members; /// Creates an empty UML class. + /// The class key or dotted path. + /// An optional displayed class label. public D2Class(string name, string? label = null) { _ = D2Writer.Reference(name); @@ -166,6 +177,7 @@ public D2Class(string name, string? label = null) } /// Adds a typed member. Supports collection initializer syntax. + /// The field or method to add. public void Add(D2ClassMember member) { if (member is null) throw new ArgumentNullException(nameof(member)); diff --git a/src/D2Comment.cs b/src/D2Comment.cs index d82abb0..12d4707 100644 --- a/src/D2Comment.cs +++ b/src/D2Comment.cs @@ -6,8 +6,11 @@ namespace d2; /// public sealed record D2Comment : D2Statement { + /// Gets the comment text. public string Text { get; } + /// Initializes a safely serialized line comment. + /// The comment text, which may contain multiple lines. public D2Comment(string text) { if (text is null) @@ -21,5 +24,6 @@ public D2Comment(string text) internal override IEnumerable Lines() => D2Writer.Lines(Text).Select(line => line.Length == 0 ? "#" : $"# {line}"); + /// public override string ToString() => string.Join(Environment.NewLine, Lines()); } diff --git a/src/D2Connection.cs b/src/D2Connection.cs index 528ab48..9616453 100644 --- a/src/D2Connection.cs +++ b/src/D2Connection.cs @@ -2,6 +2,11 @@ namespace d2; +/// Represents a directed or undirected connection between two D2 objects. +/// The first endpoint reference. +/// The second endpoint reference. +/// The connection direction. +/// An optional displayed label. public record class D2Connection( string First, string Second, @@ -11,6 +16,7 @@ public record class D2Connection( { private readonly List _statements = new(); + /// Gets the additional connection-body statements in serialization order. public IReadOnlyList Statements => _statements; /// An optional icon URL displayed on this connection. @@ -25,8 +31,12 @@ public record class D2Connection( /// Optional typed styles for this connection. public D2Style? Style { get; set; } + /// Adds a safely serialized property to the connection body. + /// The property to add. public void Add(D2Property property) => Add((D2Statement)property); + /// Adds a statement to the connection body. + /// The statement to add. public void Add(D2Statement statement) { if (statement is null) @@ -78,9 +88,11 @@ internal override IEnumerable Lines() .Append("}"); } + /// public override string ToString() => string.Join(Environment.NewLine, Lines()); + /// public IEnumerator GetEnumerator() => _statements.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/src/D2Diagram.cs b/src/D2Diagram.cs index 9bc32a6..4d6232c 100644 --- a/src/D2Diagram.cs +++ b/src/D2Diagram.cs @@ -8,17 +8,25 @@ public record class D2Diagram { private readonly IReadOnlyList _statements; + /// Gets all statements in their serialization order. public IReadOnlyList Statements => _statements; + /// Gets the top-level shape statements. public IEnumerable Shapes => _statements.OfType(); + /// Gets the top-level connection statements. public IEnumerable Connections => _statements.OfType(); + /// Initializes a diagram with shapes followed by connections. + /// The shapes to include. + /// The connections to include. public D2Diagram(IEnumerable Shapes, IEnumerable Connections) : this(Combine(Shapes, Connections)) { } + /// Initializes a diagram while retaining the supplied statement order. + /// The statements to include. public D2Diagram(IEnumerable statements) { if (statements is null) @@ -35,10 +43,19 @@ public D2Diagram(IEnumerable statements) _statements = materialized; } + /// Returns a new diagram with a shape appended. + /// The shape to append. + /// A diagram containing the existing statements and the supplied shape. public D2Diagram Add(D2Shape shape) => Add((D2Statement)shape); + /// Returns a new diagram with a connection appended. + /// The connection to append. + /// A diagram containing the existing statements and the supplied connection. public D2Diagram Add(D2Connection connection) => Add((D2Statement)connection); + /// Returns a new diagram with a statement appended. + /// The statement to append. + /// A diagram containing the existing statements and the supplied statement. public D2Diagram Add(D2Statement statement) { if (statement is null) @@ -51,6 +68,7 @@ public D2Diagram Add(D2Statement statement) internal IEnumerable Lines() => _statements.SelectMany(statement => statement.Lines()); + /// public override string ToString() => string.Join(Environment.NewLine, Lines()); private static IEnumerable Combine( diff --git a/src/D2DiagramBuilder.cs b/src/D2DiagramBuilder.cs index e69aca9..a2dac91 100644 --- a/src/D2DiagramBuilder.cs +++ b/src/D2DiagramBuilder.cs @@ -16,6 +16,7 @@ public D2DiagramBuilder() } /// Adds a statement. Supports collection initializer syntax. + /// The statement to append. public void Add(D2Statement statement) { if (statement is null) throw new ArgumentNullException(nameof(statement)); @@ -23,6 +24,8 @@ public void Add(D2Statement statement) } /// Appends any statement and returns this builder. + /// The statement to append. + /// This builder for continued fluent configuration. public D2DiagramBuilder Then(D2Statement statement) { Add(statement); @@ -30,6 +33,11 @@ public D2DiagramBuilder Then(D2Statement statement) } /// Appends a shape and returns this builder. + /// The shape key or dotted path. + /// An optional displayed label. + /// An optional D2 shape kind. + /// Optional typed styles. + /// This builder for continued fluent configuration. public D2DiagramBuilder AddShape( string name, string? label = null, @@ -38,6 +46,11 @@ public D2DiagramBuilder AddShape( => Then(new D2Shape(name, label, shape, style)); /// Appends a connection and returns this builder. + /// The first endpoint reference. + /// The second endpoint reference. + /// The connection direction; defaults to . + /// An optional displayed label. + /// This builder for continued fluent configuration. public D2DiagramBuilder AddConnection( string first, string second, @@ -46,6 +59,7 @@ public D2DiagramBuilder AddConnection( => Then(new D2Connection(first, second, direction ?? Direction.To, label)); /// Creates an immutable snapshot of the current statements. + /// A new diagram containing the builder's current statements. public D2Diagram Build() => new(_statements); /// diff --git a/src/D2Property.cs b/src/D2Property.cs index c26e1fd..08c94d3 100644 --- a/src/D2Property.cs +++ b/src/D2Property.cs @@ -8,14 +8,21 @@ public sealed record D2Property : D2Statement { private readonly IReadOnlyList? _statements; + /// Gets the property name or dotted property path. public string Name { get; } + /// Gets the scalar property value, or for a block property. public object? Value { get; } + /// Gets whether this property contains a statement block. public bool IsBlock => _statements is not null; + /// Gets the block statements, or an empty list for a scalar property. public IReadOnlyList Statements => _statements ?? Array.Empty(); + /// Initializes a string-valued property. + /// The property name or dotted path. + /// The string value. public D2Property(string name, string value) { Name = ValidateName(name); @@ -26,24 +33,36 @@ public D2Property(string name, string value) Value = value; } + /// Initializes a Boolean-valued property. + /// The property name or dotted path. + /// The Boolean value. public D2Property(string name, bool value) { Name = ValidateName(name); Value = value; } + /// Initializes an integer-valued property. + /// The property name or dotted path. + /// The integer value. public D2Property(string name, int value) { Name = ValidateName(name); Value = value; } + /// Initializes a numeric property serialized with invariant formatting. + /// The property name or dotted path. + /// The finite floating-point value. public D2Property(string name, double value) { Name = ValidateName(name); Value = value; } + /// Initializes a block property containing ordered statements. + /// The property name or dotted path. + /// The statements in the property block. public D2Property(string name, IEnumerable statements) { Name = ValidateName(name); @@ -60,6 +79,7 @@ internal override IEnumerable Lines() return D2Writer.Block(Name, _statements.SelectMany(statement => statement.Lines())); } + /// public override string ToString() => string.Join(Environment.NewLine, Lines()); private string SerializeValue() => Value switch diff --git a/src/D2RawStatement.cs b/src/D2RawStatement.cs index 77087a2..327e4d3 100644 --- a/src/D2RawStatement.cs +++ b/src/D2RawStatement.cs @@ -6,8 +6,11 @@ namespace d2; /// public sealed record D2RawStatement : D2Statement { + /// Gets the unescaped D2 source. public string Source { get; } + /// Initializes a raw statement from trusted D2 source. + /// The D2 source to emit without escaping. public D2RawStatement(string source) { if (source is null) @@ -20,5 +23,6 @@ public D2RawStatement(string source) internal override IEnumerable Lines() => D2Writer.Lines(Source); + /// public override string ToString() => string.Join(Environment.NewLine, Lines()); } diff --git a/src/D2SequenceDiagram.cs b/src/D2SequenceDiagram.cs index 8a0e999..f8aa8ac 100644 --- a/src/D2SequenceDiagram.cs +++ b/src/D2SequenceDiagram.cs @@ -24,6 +24,8 @@ public sealed record D2SequenceDiagram : D2Statement, IEnumerable public IReadOnlyList Statements => _statements; /// Creates an empty sequence diagram. + /// The sequence diagram key or dotted path. + /// An optional displayed label. public D2SequenceDiagram(string name, string? label = null) { _ = D2Writer.Reference(name); @@ -32,6 +34,7 @@ public D2SequenceDiagram(string name, string? label = null) } /// Adds an ordered statement. Supports collection initializer syntax. + /// The statement to add. public void Add(D2Statement statement) { if (statement is null) throw new ArgumentNullException(nameof(statement)); @@ -39,6 +42,10 @@ public void Add(D2Statement statement) } /// Adds an actor or participant and returns this diagram. + /// The participant key. + /// An optional displayed participant label. + /// An optional participant shape. + /// This sequence diagram for continued fluent configuration. public D2SequenceDiagram AddParticipant(string name, string? label = null, Shape? shape = null) { Add(new D2Shape(name, label, shape)); @@ -46,6 +53,11 @@ public D2SequenceDiagram AddParticipant(string name, string? label = null, Shape } /// Adds an ordered message and returns this diagram. + /// The first participant reference. + /// The second participant reference. + /// An optional message label. + /// The message direction; defaults to . + /// This sequence diagram for continued fluent configuration. public D2SequenceDiagram AddMessage( string first, string second, @@ -57,6 +69,9 @@ public D2SequenceDiagram AddMessage( } /// Adds a labeled sequence group and returns this diagram. + /// The group key. + /// The ordered statements inside the group. + /// This sequence diagram for continued fluent configuration. public D2SequenceDiagram AddGroup(string name, params D2Statement[] statements) { if (statements is null) throw new ArgumentNullException(nameof(statements)); diff --git a/src/D2Shape.cs b/src/D2Shape.cs index 2faebda..bd209e1 100644 --- a/src/D2Shape.cs +++ b/src/D2Shape.cs @@ -18,6 +18,7 @@ public record class D2Shape( { private readonly List _statements = new(); + /// Gets the shape-body statements in serialization order. public IReadOnlyList Statements => _statements; /// An optional icon URL. @@ -35,12 +36,20 @@ public record class D2Shape( /// An optional fixed height for a non-container shape. public int? Height { get; set; } + /// Adds a nested shape. Supports collection initializer syntax. + /// The nested shape to add. public void Add(D2Shape shape) => Add((D2Statement)shape); + /// Adds a connection to this shape's body. + /// The connection to add. public void Add(D2Connection connection) => Add((D2Statement)connection); + /// Adds a block-string statement to this shape's body. + /// The block-string statement to add. public void Add(D2Text text) => Add((D2Statement)text); + /// Adds any supported statement to this shape's body. + /// The statement to add. public void Add(D2Statement statement) { if (statement is null) @@ -91,9 +100,11 @@ internal override IEnumerable Lines() return D2Writer.Object(Name, Label, properties); } + /// public override string ToString() => string.Join(Environment.NewLine, Lines()); + /// public IEnumerator GetEnumerator() => _statements.OfType().GetEnumerator(); diff --git a/src/D2SqlTable.cs b/src/D2SqlTable.cs index e6d1ce9..c50cd3a 100644 --- a/src/D2SqlTable.cs +++ b/src/D2SqlTable.cs @@ -22,6 +22,7 @@ private D2SqlConstraint(string value) /// Creates a custom SQL constraint value. /// The constraint text shown by D2. + /// A constraint containing the supplied value. public static D2SqlConstraint Custom(string value) { if (string.IsNullOrWhiteSpace(value)) @@ -48,6 +49,9 @@ public sealed record D2SqlColumn : D2Statement public IReadOnlyList Constraints { get; } /// Creates a SQL column. + /// The column key. + /// The SQL type displayed by D2. + /// The column constraints. public D2SqlColumn(string name, string type, params D2SqlConstraint[] constraints) { _ = D2Writer.Identifier(name); @@ -107,6 +111,8 @@ public sealed record D2SqlTable : D2Statement, IEnumerable public IReadOnlyList Columns => _columns; /// Creates an empty SQL table. + /// The table key or dotted path. + /// An optional displayed table label. public D2SqlTable(string name, string? label = null) { _ = D2Writer.Reference(name); @@ -115,6 +121,7 @@ public D2SqlTable(string name, string? label = null) } /// Adds a typed column. Supports collection initializer syntax. + /// The column to add. public void Add(D2SqlColumn column) { if (column is null) throw new ArgumentNullException(nameof(column)); diff --git a/src/D2Style.cs b/src/D2Style.cs index de281f6..f82af17 100644 --- a/src/D2Style.cs +++ b/src/D2Style.cs @@ -78,6 +78,7 @@ public record class D2Style( D2TextTransform? TextTransform = null) { /// Serializes this style as a D2 style block. + /// The serialized lines of the style block, or an empty sequence when no values are set. public IEnumerable Lines() { ValidateRange(StrokeWidth, 1, 15, nameof(StrokeWidth)); diff --git a/src/D2Text.cs b/src/D2Text.cs index ead295d..bd400aa 100644 --- a/src/D2Text.cs +++ b/src/D2Text.cs @@ -1,5 +1,10 @@ namespace d2; +/// Represents a formatted D2 block string assigned to a property. +/// The target property name or dotted path. +/// The block-string contents. +/// The D2 block-string format, such as md or latex. +/// The minimum number of pipe characters in the delimiter. public record D2Text( string Property, string Text, @@ -39,6 +44,7 @@ private static bool IsAsciiFormatCharacter(char value) or >= '0' and <= '9' or '_' or '-'; + /// public override string ToString() => string.Join(Environment.NewLine, Lines()); } diff --git a/src/Direction.cs b/src/Direction.cs index 33c3d31..2ed743c 100644 --- a/src/Direction.cs +++ b/src/Direction.cs @@ -1,5 +1,7 @@ namespace d2; +/// Represents the arrowhead direction of a D2 connection. +/// The D2 connection operator. public abstract record Direction(string Value) { /// A connection pointing from the first endpoint to the second. @@ -23,10 +25,15 @@ public abstract record Direction(string Value) /// Legacy alias for . [Obsolete("Use Direction.None instead.")] public static readonly None NONE = None; + /// public sealed override string ToString() => Value; } +/// A connection pointing from its first endpoint to its second. public sealed record To() : Direction("->"); +/// A connection pointing from its second endpoint to its first. public sealed record From() : Direction("<-"); +/// A connection with arrowheads at both endpoints. public sealed record Both() : Direction("<->"); +/// A connection with no arrowhead. public sealed record None() : Direction("--"); diff --git a/src/Shape.cs b/src/Shape.cs index eece824..4f8db2e 100644 --- a/src/Shape.cs +++ b/src/Shape.cs @@ -1,54 +1,103 @@ namespace d2; +/// Represents a D2 shape kind. +/// The D2 shape keyword. public abstract record Shape(string Value) { + /// The default rectangular shape. public readonly static Rectangle Rectangle = new(); + /// A square shape. public readonly static Square Square = new(); + /// A page shape. public readonly static Page Page = new(); + /// A parallelogram shape. public readonly static Parallelogram Parallelogram = new(); + /// A document shape. public readonly static Document Document = new(); + /// A cylinder shape, commonly used for data stores. public readonly static Cylinder Cylinder = new(); + /// A queue shape. public readonly static Queue Queue = new(); + /// A package shape. public readonly static Package Package = new(); + /// A step shape. public readonly static Step Step = new(); + /// A callout shape. public readonly static Callout Callout = new(); + /// A stored-data shape. public readonly static StoredData StoredData = new(); + /// A person shape. public readonly static Person Person = new(); + /// A diamond shape. public readonly static Diamond Diamond = new(); + /// An oval shape. public readonly static Oval Oval = new(); + /// A circle shape. public readonly static Circle Circle = new(); + /// A hexagon shape. public readonly static Hexagon Hexagon = new(); + /// A cloud shape. public readonly static Cloud Cloud = new(); + /// A text-only shape. public readonly static Text Text = new(); + /// A code shape. public readonly static Code Code = new(); + /// A SQL-table shape. public readonly static SqlTable SqlTable = new(); + /// An image shape. public readonly static Image Image = new(); + /// A UML class shape. public readonly static Class Class = new(); + /// A sequence-diagram container shape. public readonly static SequenceDiagram SequenceDiagram = new(); + /// public sealed override string ToString() => Value; } +/// Represents D2's rectangle shape. public sealed record Rectangle() : Shape("rectangle"); +/// Represents D2's square shape. public sealed record Square() : Shape("square"); +/// Represents D2's page shape. public sealed record Page() : Shape("page"); +/// Represents D2's parallelogram shape. public sealed record Parallelogram() : Shape("parallelogram"); +/// Represents D2's document shape. public sealed record Document() : Shape("document"); +/// Represents D2's cylinder shape. public sealed record Cylinder() : Shape("cylinder"); +/// Represents D2's queue shape. public sealed record Queue() : Shape("queue"); +/// Represents D2's package shape. public sealed record Package() : Shape("package"); +/// Represents D2's step shape. public sealed record Step() : Shape("step"); +/// Represents D2's callout shape. public sealed record Callout() : Shape("callout"); +/// Represents D2's stored_data shape. public sealed record StoredData() : Shape("stored_data"); +/// Represents D2's person shape. public sealed record Person() : Shape("person"); +/// Represents D2's diamond shape. public sealed record Diamond() : Shape("diamond"); +/// Represents D2's oval shape. public sealed record Oval() : Shape("oval"); +/// Represents D2's circle shape. public sealed record Circle() : Shape("circle"); +/// Represents D2's hexagon shape. public sealed record Hexagon() : Shape("hexagon"); +/// Represents D2's cloud shape. public sealed record Cloud() : Shape("cloud"); +/// Represents D2's text shape. public sealed record Text() : Shape("text"); +/// Represents D2's code shape. public sealed record Code() : Shape("code"); +/// Represents D2's sql_table shape. public sealed record SqlTable() : Shape("sql_table"); +/// Represents D2's image shape. public sealed record Image() : Shape("image"); +/// Represents D2's class shape. public sealed record Class() : Shape("class"); +/// Represents D2's sequence_diagram shape. public sealed record SequenceDiagram() : Shape("sequence_diagram"); diff --git a/src/d2lang-cs.csproj b/src/d2lang-cs.csproj index e13368c..3b6a6cd 100644 --- a/src/d2lang-cs.csproj +++ b/src/d2lang-cs.csproj @@ -4,6 +4,8 @@ net10.0 enable enable + true + true d2 d2lang-cs 1.0.0 diff --git a/test/Usings.cs b/test/Usings.cs index ae3337a..052e8d1 100644 --- a/test/Usings.cs +++ b/test/Usings.cs @@ -1,2 +1,4 @@ global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using d2; \ No newline at end of file +global using d2; + +[assembly: DoNotParallelize]