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
60 changes: 60 additions & 0 deletions src/D2Board.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Collections;

namespace d2;

/// <summary>
/// A named board within a layer, scenario, or step collection.
/// </summary>
public sealed record D2Board : D2Statement, IEnumerable<D2Statement>
{
private readonly List<D2Statement> _statements;

public string Name { get; }

public IReadOnlyList<D2Statement> Statements => _statements;

public D2Board(string name)
: this(name, Array.Empty<D2Statement>())
{
}

public D2Board(string name, IEnumerable<D2Statement> statements)
{
Name = ValidateName(name);
if (statements is null)
{
throw new ArgumentNullException(nameof(statements));
}

_statements = statements.ToList();
if (_statements.Any(statement => statement is null))
{
throw new ArgumentException("A board cannot contain a null statement.", nameof(statements));
}
}

public void Add(D2Statement statement)
{
if (statement is null)
{
throw new ArgumentNullException(nameof(statement));
}

_statements.Add(statement);
}

internal override IEnumerable<string> Lines()
=> D2Writer.BlockIdentifier(Name, _statements.SelectMany(statement => statement.Lines()));

public override string ToString() => string.Join(Environment.NewLine, Lines());

public IEnumerator<D2Statement> GetEnumerator() => _statements.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

private static string ValidateName(string name)
{
_ = D2Writer.Identifier(name);
return name;
}
}
74 changes: 74 additions & 0 deletions src/D2BoardCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Collections;

namespace d2;

public enum D2BoardKind
{
Layers,
Scenarios,
Steps,
}

/// <summary>
/// A D2 composition block containing layers, scenarios, or steps.
/// </summary>
public sealed record D2BoardCollection : D2Statement, IEnumerable<D2Board>
{
private readonly List<D2Board> _boards;

public D2BoardKind Kind { get; }

public IReadOnlyList<D2Board> Boards => _boards;

public D2BoardCollection(D2BoardKind kind)
: this(kind, Array.Empty<D2Board>())
{
}

public D2BoardCollection(D2BoardKind kind, IEnumerable<D2Board> boards)
{
if (!Enum.IsDefined(typeof(D2BoardKind), kind))
{
throw new ArgumentOutOfRangeException(nameof(kind), kind, "Unknown D2 board kind.");
}

if (boards is null)
{
throw new ArgumentNullException(nameof(boards));
}

Kind = kind;
_boards = boards.ToList();
if (_boards.Any(board => board is null))
{
throw new ArgumentException("A board collection cannot contain null.", nameof(boards));
}
}

public void Add(D2Board board)
{
if (board is null)
{
throw new ArgumentNullException(nameof(board));
}

_boards.Add(board);
}

internal override IEnumerable<string> Lines()
=> D2Writer.Block(Keyword(Kind), _boards.SelectMany(board => board.Lines()));

public override string ToString() => string.Join(Environment.NewLine, Lines());

public IEnumerator<D2Board> GetEnumerator() => _boards.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

private static string Keyword(D2BoardKind kind) => kind switch
{
D2BoardKind.Layers => "layers",
D2BoardKind.Scenarios => "scenarios",
D2BoardKind.Steps => "steps",
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, "Unknown D2 board kind."),
};
}
25 changes: 25 additions & 0 deletions src/D2Comment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace d2;

/// <summary>
/// A D2 line comment. Every input line is prefixed so multiline text cannot
/// escape the comment.
/// </summary>
public sealed record D2Comment : D2Statement
{
public string Text { get; }

public D2Comment(string text)
{
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}

Text = text;
}

internal override IEnumerable<string> Lines()
=> D2Writer.Lines(Text).Select(line => line.Length == 0 ? "#" : $"# {line}");

public override string ToString() => string.Join(Environment.NewLine, Lines());
}
41 changes: 36 additions & 5 deletions src/D2Connection.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,54 @@
using System.Collections;

namespace d2;

public record class D2Connection(
string First,
string Second,
Direction Direction,
string? Label = ""
)
) : D2Statement, IEnumerable<D2Statement>
{
internal IEnumerable<string> Lines()
private readonly List<D2Statement> _statements = new();

public IReadOnlyList<D2Statement> Statements => _statements;

public void Add(D2Property property) => Add((D2Statement)property);

public void Add(D2Statement statement)
{
if (statement is null)
{
throw new ArgumentNullException(nameof(statement));
}

_statements.Add(statement);
}

internal override IEnumerable<string> Lines()
{
var @base = $"{D2Writer.Reference(First)} {Direction} {D2Writer.Reference(Second)}";
if (!string.IsNullOrWhiteSpace(Label))
var hasLabel = !string.IsNullOrWhiteSpace(Label);
if (hasLabel)
{
@base += $": {D2Writer.String(Label)}";
@base += $": {D2Writer.String(Label!)}";
}

return new List<string> { @base };
if (_statements.Count == 0)
{
return new[] { @base };
}

var openingLine = hasLabel ? $"{@base} {{" : $"{@base}: {{";
return new[] { openingLine }
.Concat(D2Writer.Indent(_statements.SelectMany(statement => statement.Lines())))
.Append("}");
}

public override string ToString()
=> string.Join(Environment.NewLine, Lines());

public IEnumerator<D2Statement> GetEnumerator() => _statements.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
79 changes: 64 additions & 15 deletions src/D2Diagram.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,72 @@
namespace d2;

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

internal IEnumerable<string> Lines()
public IReadOnlyList<D2Statement> Statements => _statements;

public IEnumerable<D2Shape> Shapes => _statements.OfType<D2Shape>();

public IEnumerable<D2Connection> Connections => _statements.OfType<D2Connection>();

public D2Diagram(IEnumerable<D2Shape> Shapes, IEnumerable<D2Connection> Connections)
: this(Combine(Shapes, Connections))
{
}

public D2Diagram(IEnumerable<D2Statement> statements)
{
var shapes = Shapes.SelectMany(s => s.Lines()).ToList();
var connections = Connections.SelectMany(c => c.Lines()).ToList();
if (statements is null)
{
throw new ArgumentNullException(nameof(statements));
}

var materialized = statements.ToList();
if (materialized.Any(statement => statement is null))
{
throw new ArgumentException("A diagram cannot contain a null statement.", nameof(statements));
}

_statements = materialized;
}

return shapes.Concat(connections);
public D2Diagram Add(D2Shape shape) => Add((D2Statement)shape);

public D2Diagram Add(D2Connection connection) => Add((D2Statement)connection);

public D2Diagram Add(D2Statement statement)
{
if (statement is null)
{
throw new ArgumentNullException(nameof(statement));
}
return new D2Diagram(_statements.Append(statement));
}

public override string ToString()
=> string.Join(Environment.NewLine, Lines());
}
internal IEnumerable<string> Lines()
=> _statements.SelectMany(statement => statement.Lines());

public override string ToString() => string.Join(Environment.NewLine, Lines());

private static IEnumerable<D2Statement> Combine(
IEnumerable<D2Shape> shapes,
IEnumerable<D2Connection> connections)
{
if (shapes is null)
{
throw new ArgumentNullException(nameof(shapes));
}

if (connections is null)
{
throw new ArgumentNullException(nameof(connections));
}

return shapes.Cast<D2Statement>().Concat(connections);
}
}
Loading