Skip to content

Commit 5e0b96d

Browse files
authored
Merge pull request #7 from Stephanvs/feature/extensible-model
Add an extensible ordered D2 model
2 parents 5614237 + b485fa6 commit 5e0b96d

12 files changed

Lines changed: 756 additions & 46 deletions

src/D2Board.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.Collections;
2+
3+
namespace d2;
4+
5+
/// <summary>
6+
/// A named board within a layer, scenario, or step collection.
7+
/// </summary>
8+
public sealed record D2Board : D2Statement, IEnumerable<D2Statement>
9+
{
10+
private readonly List<D2Statement> _statements;
11+
12+
public string Name { get; }
13+
14+
public IReadOnlyList<D2Statement> Statements => _statements;
15+
16+
public D2Board(string name)
17+
: this(name, Array.Empty<D2Statement>())
18+
{
19+
}
20+
21+
public D2Board(string name, IEnumerable<D2Statement> statements)
22+
{
23+
Name = ValidateName(name);
24+
if (statements is null)
25+
{
26+
throw new ArgumentNullException(nameof(statements));
27+
}
28+
29+
_statements = statements.ToList();
30+
if (_statements.Any(statement => statement is null))
31+
{
32+
throw new ArgumentException("A board cannot contain a null statement.", nameof(statements));
33+
}
34+
}
35+
36+
public void Add(D2Statement statement)
37+
{
38+
if (statement is null)
39+
{
40+
throw new ArgumentNullException(nameof(statement));
41+
}
42+
43+
_statements.Add(statement);
44+
}
45+
46+
internal override IEnumerable<string> Lines()
47+
=> D2Writer.BlockIdentifier(Name, _statements.SelectMany(statement => statement.Lines()));
48+
49+
public override string ToString() => string.Join(Environment.NewLine, Lines());
50+
51+
public IEnumerator<D2Statement> GetEnumerator() => _statements.GetEnumerator();
52+
53+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
54+
55+
private static string ValidateName(string name)
56+
{
57+
_ = D2Writer.Identifier(name);
58+
return name;
59+
}
60+
}

src/D2BoardCollection.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Collections;
2+
3+
namespace d2;
4+
5+
public enum D2BoardKind
6+
{
7+
Layers,
8+
Scenarios,
9+
Steps,
10+
}
11+
12+
/// <summary>
13+
/// A D2 composition block containing layers, scenarios, or steps.
14+
/// </summary>
15+
public sealed record D2BoardCollection : D2Statement, IEnumerable<D2Board>
16+
{
17+
private readonly List<D2Board> _boards;
18+
19+
public D2BoardKind Kind { get; }
20+
21+
public IReadOnlyList<D2Board> Boards => _boards;
22+
23+
public D2BoardCollection(D2BoardKind kind)
24+
: this(kind, Array.Empty<D2Board>())
25+
{
26+
}
27+
28+
public D2BoardCollection(D2BoardKind kind, IEnumerable<D2Board> boards)
29+
{
30+
if (!Enum.IsDefined(typeof(D2BoardKind), kind))
31+
{
32+
throw new ArgumentOutOfRangeException(nameof(kind), kind, "Unknown D2 board kind.");
33+
}
34+
35+
if (boards is null)
36+
{
37+
throw new ArgumentNullException(nameof(boards));
38+
}
39+
40+
Kind = kind;
41+
_boards = boards.ToList();
42+
if (_boards.Any(board => board is null))
43+
{
44+
throw new ArgumentException("A board collection cannot contain null.", nameof(boards));
45+
}
46+
}
47+
48+
public void Add(D2Board board)
49+
{
50+
if (board is null)
51+
{
52+
throw new ArgumentNullException(nameof(board));
53+
}
54+
55+
_boards.Add(board);
56+
}
57+
58+
internal override IEnumerable<string> Lines()
59+
=> D2Writer.Block(Keyword(Kind), _boards.SelectMany(board => board.Lines()));
60+
61+
public override string ToString() => string.Join(Environment.NewLine, Lines());
62+
63+
public IEnumerator<D2Board> GetEnumerator() => _boards.GetEnumerator();
64+
65+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
66+
67+
private static string Keyword(D2BoardKind kind) => kind switch
68+
{
69+
D2BoardKind.Layers => "layers",
70+
D2BoardKind.Scenarios => "scenarios",
71+
D2BoardKind.Steps => "steps",
72+
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, "Unknown D2 board kind."),
73+
};
74+
}

src/D2Comment.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace d2;
2+
3+
/// <summary>
4+
/// A D2 line comment. Every input line is prefixed so multiline text cannot
5+
/// escape the comment.
6+
/// </summary>
7+
public sealed record D2Comment : D2Statement
8+
{
9+
public string Text { get; }
10+
11+
public D2Comment(string text)
12+
{
13+
if (text is null)
14+
{
15+
throw new ArgumentNullException(nameof(text));
16+
}
17+
18+
Text = text;
19+
}
20+
21+
internal override IEnumerable<string> Lines()
22+
=> D2Writer.Lines(Text).Select(line => line.Length == 0 ? "#" : $"# {line}");
23+
24+
public override string ToString() => string.Join(Environment.NewLine, Lines());
25+
}

src/D2Connection.cs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,54 @@
1+
using System.Collections;
2+
13
namespace d2;
24

35
public record class D2Connection(
46
string First,
57
string Second,
68
Direction Direction,
79
string? Label = ""
8-
)
10+
) : D2Statement, IEnumerable<D2Statement>
911
{
10-
internal IEnumerable<string> Lines()
12+
private readonly List<D2Statement> _statements = new();
13+
14+
public IReadOnlyList<D2Statement> Statements => _statements;
15+
16+
public void Add(D2Property property) => Add((D2Statement)property);
17+
18+
public void Add(D2Statement statement)
19+
{
20+
if (statement is null)
21+
{
22+
throw new ArgumentNullException(nameof(statement));
23+
}
24+
25+
_statements.Add(statement);
26+
}
27+
28+
internal override IEnumerable<string> Lines()
1129
{
1230
var @base = $"{D2Writer.Reference(First)} {Direction} {D2Writer.Reference(Second)}";
13-
if (!string.IsNullOrWhiteSpace(Label))
31+
var hasLabel = !string.IsNullOrWhiteSpace(Label);
32+
if (hasLabel)
1433
{
15-
@base += $": {D2Writer.String(Label)}";
34+
@base += $": {D2Writer.String(Label!)}";
1635
}
1736

18-
return new List<string> { @base };
37+
if (_statements.Count == 0)
38+
{
39+
return new[] { @base };
40+
}
41+
42+
var openingLine = hasLabel ? $"{@base} {{" : $"{@base}: {{";
43+
return new[] { openingLine }
44+
.Concat(D2Writer.Indent(_statements.SelectMany(statement => statement.Lines())))
45+
.Append("}");
1946
}
2047

2148
public override string ToString()
2249
=> string.Join(Environment.NewLine, Lines());
50+
51+
public IEnumerator<D2Statement> GetEnumerator() => _statements.GetEnumerator();
52+
53+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
2354
}

src/D2Diagram.cs

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,72 @@
11
namespace d2;
22

3-
public record class D2Diagram(
4-
IEnumerable<D2Shape> Shapes,
5-
IEnumerable<D2Connection> Connections
6-
)
3+
/// <summary>
4+
/// An ordered D2 document. Statement order is retained exactly, which is
5+
/// significant for features such as sequence diagrams and composition boards.
6+
/// </summary>
7+
public record class D2Diagram
78
{
8-
public D2Diagram Add(D2Shape shape)
9-
=> this with { Shapes = Shapes.Append(shape) };
10-
public D2Diagram Add(D2Connection connection)
11-
=> this with { Connections = Connections.Append(connection) };
9+
private readonly IReadOnlyList<D2Statement> _statements;
1210

13-
internal IEnumerable<string> Lines()
11+
public IReadOnlyList<D2Statement> Statements => _statements;
12+
13+
public IEnumerable<D2Shape> Shapes => _statements.OfType<D2Shape>();
14+
15+
public IEnumerable<D2Connection> Connections => _statements.OfType<D2Connection>();
16+
17+
public D2Diagram(IEnumerable<D2Shape> Shapes, IEnumerable<D2Connection> Connections)
18+
: this(Combine(Shapes, Connections))
19+
{
20+
}
21+
22+
public D2Diagram(IEnumerable<D2Statement> statements)
1423
{
15-
var shapes = Shapes.SelectMany(s => s.Lines()).ToList();
16-
var connections = Connections.SelectMany(c => c.Lines()).ToList();
24+
if (statements is null)
25+
{
26+
throw new ArgumentNullException(nameof(statements));
27+
}
28+
29+
var materialized = statements.ToList();
30+
if (materialized.Any(statement => statement is null))
31+
{
32+
throw new ArgumentException("A diagram cannot contain a null statement.", nameof(statements));
33+
}
34+
35+
_statements = materialized;
36+
}
1737

18-
return shapes.Concat(connections);
38+
public D2Diagram Add(D2Shape shape) => Add((D2Statement)shape);
39+
40+
public D2Diagram Add(D2Connection connection) => Add((D2Statement)connection);
41+
42+
public D2Diagram Add(D2Statement statement)
43+
{
44+
if (statement is null)
45+
{
46+
throw new ArgumentNullException(nameof(statement));
47+
}
48+
return new D2Diagram(_statements.Append(statement));
1949
}
2050

21-
public override string ToString()
22-
=> string.Join(Environment.NewLine, Lines());
23-
}
51+
internal IEnumerable<string> Lines()
52+
=> _statements.SelectMany(statement => statement.Lines());
53+
54+
public override string ToString() => string.Join(Environment.NewLine, Lines());
55+
56+
private static IEnumerable<D2Statement> Combine(
57+
IEnumerable<D2Shape> shapes,
58+
IEnumerable<D2Connection> connections)
59+
{
60+
if (shapes is null)
61+
{
62+
throw new ArgumentNullException(nameof(shapes));
63+
}
64+
65+
if (connections is null)
66+
{
67+
throw new ArgumentNullException(nameof(connections));
68+
}
69+
70+
return shapes.Cast<D2Statement>().Concat(connections);
71+
}
72+
}

0 commit comments

Comments
 (0)