From 041df1b3ccc9fc0fba4566152d6ec94da5522c52 Mon Sep 17 00:00:00 2001
From: jamesshenry <79054685+jamesshenry@users.noreply.github.com>
Date: Tue, 16 Dec 2025 15:46:21 +0000
Subject: [PATCH 01/17] remove irrelevant projects from branch
---
Kuddle.slnx | 2 -
sampleApp/Program.cs | 2 -
sampleApp/sampleApp.csproj | 10 -----
.../Kuddle.Generators.csproj | 16 -------
src/Kuddle.Generators/TheGenerator.cs | 45 -------------------
5 files changed, 75 deletions(-)
delete mode 100644 sampleApp/Program.cs
delete mode 100644 sampleApp/sampleApp.csproj
delete mode 100644 src/Kuddle.Generators/Kuddle.Generators.csproj
delete mode 100644 src/Kuddle.Generators/TheGenerator.cs
diff --git a/Kuddle.slnx b/Kuddle.slnx
index d6f6259..283aa83 100644
--- a/Kuddle.slnx
+++ b/Kuddle.slnx
@@ -5,9 +5,7 @@
-
-
diff --git a/sampleApp/Program.cs b/sampleApp/Program.cs
deleted file mode 100644
index 3751555..0000000
--- a/sampleApp/Program.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-// See https://aka.ms/new-console-template for more information
-Console.WriteLine("Hello, World!");
diff --git a/sampleApp/sampleApp.csproj b/sampleApp/sampleApp.csproj
deleted file mode 100644
index ed9781c..0000000
--- a/sampleApp/sampleApp.csproj
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- Exe
- net10.0
- enable
- enable
-
-
-
diff --git a/src/Kuddle.Generators/Kuddle.Generators.csproj b/src/Kuddle.Generators/Kuddle.Generators.csproj
deleted file mode 100644
index f86a614..0000000
--- a/src/Kuddle.Generators/Kuddle.Generators.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- netstandard2.0
- latest
- enable
- enable
- true
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
diff --git a/src/Kuddle.Generators/TheGenerator.cs b/src/Kuddle.Generators/TheGenerator.cs
deleted file mode 100644
index 45aee6f..0000000
--- a/src/Kuddle.Generators/TheGenerator.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System.Collections.Immutable;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
-namespace Kuddle.Generators;
-
-[Generator]
-public class TheGenerator : IIncrementalGenerator
-{
- public void Initialize(IncrementalGeneratorInitializationContext context)
- {
- var provider = context
- .SyntaxProvider.CreateSyntaxProvider(
- predicate: static (node, _) => node is ClassDeclarationSyntax,
- transform: static (ctx, _) => ctx.Node as ClassDeclarationSyntax
- )
- .Where(m => m is not null);
-
- var compilation = context.CompilationProvider.Combine(provider.Collect());
-
- context.RegisterSourceOutput(compilation, Execute);
- }
-
- private void Execute(
- SourceProductionContext context,
- (Compilation Left, ImmutableArray Right) tuple
- )
- {
- var (compilation, list) = tuple;
-
- var theCode = """
-namespace ClassListGenerator;
-
-public static class ClassNames
-{
- public static List Names = new()
- {
- "SomeClass"
- };
-}
-""";
-
- context.AddSource("YourClassList.g.cs", theCode);
- }
-}
From 511baffbd0f6dbb0857538e16204c2135ed90d10 Mon Sep 17 00:00:00 2001
From: jamesshenry <79054685+jamesshenry@users.noreply.github.com>
Date: Tue, 16 Dec 2025 23:44:42 +0000
Subject: [PATCH 02/17] eip: rename types
---
readme.md | 195 ++--------------
src/Kuddle.Benchmarks/ParserBenchmarks.cs | 4 +-
src/Kuddle.Tests/Errors/ErrorHandlingTests.cs | 3 +-
src/Kuddle.Tests/Extensions/DxTests.cs | 23 +-
.../Grammar/CommentParserTests.cs | 6 +-
src/Kuddle.Tests/Grammar/NodeParserTests.cs | 20 +-
.../Grammar/NumberParsersTests.cs | 46 ++--
src/Kuddle.Tests/Grammar/StringParserTests.cs | 54 ++---
.../Grammar/WhiteSpaceParsersTests.cs | 16 +-
.../Serialization/DocumentToObjectTests.cs | 3 +
.../Serialization/KuddleParsingTests.cs | 219 +++++++++++-------
.../Serialization/KuddleWriterTests.cs | 14 +-
.../Serialization/NodeToObjectTests.cs | 127 ++++++++++
.../Serialization/ObjectMapperTests.cs | 59 +++--
.../Validation/ReservedTypeValidatorTests.cs | 15 +-
src/Kuddle/AST/KdlDocument.cs | 6 +-
...NodeExtensions.cs => KdlNodeExtensions.cs} | 2 +-
...lueExtensions.cs => KdlValueExtensions.cs} | 2 +-
src/Kuddle/Extensions/SpanExtensions.cs | 18 +-
.../{KuddleGrammar.cs => KdlGrammar.cs} | 5 +-
.../Attributes/KdlPropertyAttribute.cs | 10 +
.../{KuddleReader.cs => KdlReader.cs} | 22 +-
src/Kuddle/Serialization/KdlReaderOptions.cs | 7 +
.../{Attributes => }/KdlSerializer.cs | 120 +++++-----
.../{KuddleWriter.cs => KdlWriter.cs} | 12 +-
...leWriterOptions.cs => KdlWriterOptions.cs} | 4 +-
.../Serialization/KuddleReaderOptions.cs | 7 -
.../Validation/KuddleReservedTypeValidator.cs | 8 +-
28 files changed, 531 insertions(+), 496 deletions(-)
create mode 100644 src/Kuddle.Tests/Serialization/DocumentToObjectTests.cs
create mode 100644 src/Kuddle.Tests/Serialization/NodeToObjectTests.cs
rename src/Kuddle/Extensions/{KuddleNodeExtensions.cs => KdlNodeExtensions.cs} (97%)
rename src/Kuddle/Extensions/{KuddleValueExtensions.cs => KdlValueExtensions.cs} (98%)
rename src/Kuddle/Parser/{KuddleGrammar.cs => KdlGrammar.cs} (99%)
rename src/Kuddle/Serialization/{KuddleReader.cs => KdlReader.cs} (71%)
create mode 100644 src/Kuddle/Serialization/KdlReaderOptions.cs
rename src/Kuddle/Serialization/{Attributes => }/KdlSerializer.cs (81%)
rename src/Kuddle/Serialization/{KuddleWriter.cs => KdlWriter.cs} (95%)
rename src/Kuddle/Serialization/{KuddleWriterOptions.cs => KdlWriterOptions.cs} (76%)
delete mode 100644 src/Kuddle/Serialization/KuddleReaderOptions.cs
diff --git a/readme.md b/readme.md
index d85a754..df52e51 100644
--- a/readme.md
+++ b/readme.md
@@ -1,188 +1,27 @@
-```text
-document := bom? version? nodes
-
-// Nodes
-nodes := (line-space* node)* line-space*
-
-base-node := slashdash? type? node-space* string
- (node-space+ slashdash? node-prop-or-arg)*
- // slashdashed node-children must always be after props and args.
- (node-space+ slashdash node-children)*
- (node-space+ node-children)?
- (node-space+ slashdash node-children)*
- node-space*
-node := base-node node-terminator
-final-node := base-node node-terminator?
-
-// Entries
-node-prop-or-arg := prop | value
-node-children := '{' nodes final-node? '}'
-node-terminator := single-line-comment | newline | ';' | eof
-
-prop := string node-space* '=' node-space* value
-value := type? node-space* (string | number | keyword)
-type := '(' node-space* string node-space* ')'
-
-// Strings
-string := identifier-string | quoted-string | raw-string ¶
-
-identifier-string := unambiguous-ident | signed-ident | dotted-ident
-unambiguous-ident :=
- ((identifier-char - digit - sign - '.') identifier-char*)
- - disallowed-keyword-strings
-signed-ident :=
- sign ((identifier-char - digit - '.') identifier-char*)?
-dotted-ident :=
- sign? '.' ((identifier-char - digit) identifier-char*)?
-identifier-char :=
- unicode - unicode-space - newline - [\\/(){};\[\]"#=]
- - disallowed-literal-code-points
-disallowed-keyword-identifiers :=
- 'true' | 'false' | 'null' | 'inf' | '-inf' | 'nan'
-
-quoted-string :=
- '"' single-line-string-body '"' |
- '"""' newline
- (multi-line-string-body newline)?
- (unicode-space | ws-escape)* '"""'
-single-line-string-body := (string-character - newline)*
-multi-line-string-body := (('"' | '""')? string-character)*
-string-character :=
- '\\' (["\\bfnrts] |
- 'u{' hex-unicode '}') |
- ws-escape |
- [^\\"] - disallowed-literal-code-points
-ws-escape := '\\' (unicode-space | newline)+
-hex-digit := [0-9a-fA-F]
-hex-unicode := hex-digit{1, 6} - surrogates
-surrogates := [dD][8-9a-fA-F]hex-digit{2}
-// U+D800-DFFF: D 8 00
-// D F FF
-
-raw-string := '#' raw-string-quotes '#' | '#' raw-string '#'
-raw-string-quotes :=
- '"' single-line-raw-string-body '"' |
- '"""' newline
- (multi-line-raw-string-body newline)?
- unicode-space* '"""'
-single-line-raw-string-body :=
- '' |
- (single-line-raw-string-char - '"')
- single-line-raw-string-char*? |
- '"' (single-line-raw-string-char - '"')
- single-line-raw-string-char*?
-single-line-raw-string-char :=
- unicode - newline - disallowed-literal-code-points
-multi-line-raw-string-body :=
- (unicode - disallowed-literal-code-points)*?
-
-// Numbers
-number := keyword-number | hex | octal | binary | decimal
+# Kuddle.Net
-decimal := sign? integer ('.' integer)? exponent?
-exponent := ('e' | 'E') sign? integer
-integer := digit (digit | '_')*
-digit := [0-9]
-sign := '+' | '-'
+Kuddle.Net is a .NET implementation of a [KDL](https://kdl.dev) parser/serializer targeting [v2](https://kdl.dev/spec/) of the spec.
+KDL is concise, human-readable language built for configuration and data exchange. Head to for more specifics on the KDL document language itself.
-hex := sign? '0x' hex-digit (hex-digit | '_')*
-octal := sign? '0o' [0-7] [0-7_]*
-binary := sign? '0b' ('0' | '1') ('0' | '1' | '_')*
+## Installation
-// Keywords and booleans.
-keyword := boolean | '#null'
-keyword-number := '#inf' | '#-inf' | '#nan'
-boolean := '#true' | '#false'
-
-// Specific code points
-bom := '\u{FEFF}'
-disallowed-literal-code-points :=
- See Table (Disallowed Literal Code Points)
-unicode := Any Unicode Scalar Value
-unicode-space := See Table
- (All White_Space unicode characters which are not `newline`)
-
-// Comments
-single-line-comment := '//' ^newline* (newline | eof)
-multi-line-comment := '/*' commented-block
-commented-block :=
- '*/' | (multi-line-comment | '*' | '/' | [^*/]+) commented-block
-slashdash := '/-' line-space*
-
-// Whitespace
-ws := unicode-space | multi-line-comment
-escline := '\\' ws* (single-line-comment | newline | eof)
-newline := See Table (All Newline White_Space)
-// Whitespace where newlines are allowed.
-line-space := node-space | newline | single-line-comment
-// Whitespace within nodes,
-// where newline-ish things must be esclined.
-node-space := ws* escline ws* | ws+
-
-// Version marker
-version :=
- '/-' unicode-space* 'kdl-version' unicode-space+ ('1' | '2')
- unicode-space* newline
+```text
+dotnet add package Kuddle.Net
```
-## Whitespace
+## Usage
-The following characters should be treated as non-Newline ({{newline}}) [white
-space](https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt):
+There are a few ways of using the library, the lower level `KdlReader` and `KdlWriter` classes, and the utility `KdlSerializer` class. For most use cases `KdlSerializer.Serialize`/`KdlSerializer.Deserialize` will be sufficient.
-| Name | Code Pt |
-| ------------------------- | -------- |
-| Character Tabulation | `U+0009` |
-| Space | `U+0020` |
-| No-Break Space | `U+00A0` |
-| Ogham Space Mark | `U+1680` |
-| En Quad | `U+2000` |
-| Em Quad | `U+2001` |
-| En Space | `U+2002` |
-| Em Space | `U+2003` |
-| Three-Per-Em Space | `U+2004` |
-| Four-Per-Em Space | `U+2005` |
-| Six-Per-Em Space | `U+2006` |
-| Figure Space | `U+2007` |
-| Punctuation Space | `U+2008` |
-| Thin Space | `U+2009` |
-| Hair Space | `U+200A` |
-| Narrow No-Break Space | `U+202F` |
-| Medium Mathematical Space | `U+205F` |
-| Ideographic Space | `U+3000` |
+```cs
+var dbKdl = """
+database main port=5432
+""";
-## Newline
-
-The following character sequences [should be treated as new
-lines](https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-5/#G41643):
-
-| Acronym | Name | Code Pt |
-| ------- | ----------------------------- | ------------------- |
-| CRLF | Carriage Return and Line Feed | `U+000D` + `U+000A` |
-| CR | Carriage Return | `U+000D` |
-| LF | Line Feed | `U+000A` |
-| NEL | Next Line | `U+0085` |
-| VT | Vertical tab | `U+000B` |
-| FF | Form Feed | `U+000C` |
-| LS | Line Separator | `U+2028` |
-| PS | Paragraph Separator | `U+2029` |
-
-Note that for the purpose of new lines, the specific sequence `CRLF` is
-considered _a single newline_.
-
-## Disallowed Literal Code Points
+var dbConfig = KdlSerializer.Deserialize(dbKdl);
+```
-The following code points may not appear literally anywhere in the document.
-They may be represented in Strings (but not Raw Strings) using Unicode Escapes ({{escapes}}) (`\u{...}`,
-except for non Unicode Scalar Value, which can't be represented even as escapes).
+KDL differs from other configuration languages like
+yaml or toml in that it is node-based. The top-level document can consist of a collection of nodes, not args (e.g. `#true`, `#false`, `0xAF`) or properties (`key=#false`). To adhere to this, the serialization API depends on the use of several provided attributes to facilitate mapping `KDL` constructs to user defined types:
-- The codepoints `U+0000-0008` or the codepoints `U+000E-001F` (various
- control characters).
-- `U+007F` (the Delete control character).
-- Any codepoint that is not a [Unicode Scalar
- Value](https://unicode.org/glossary/#unicode_scalar_value) (`U+D800-DFFF`).
-- `U+200E-200F`, `U+202A-202E`, and `U+2066-2069`, the [unicode
- "direction control"
- characters](https://www.w3.org/International/questions/qa-bidi-unicode-controls)
-- `U+FEFF`, aka Zero-width Non-breaking Space (ZWNBSP)/Byte Order Mark (BOM),
- except as the first code point in a document.
+### Attributes
diff --git a/src/Kuddle.Benchmarks/ParserBenchmarks.cs b/src/Kuddle.Benchmarks/ParserBenchmarks.cs
index d3ba66f..72b9743 100644
--- a/src/Kuddle.Benchmarks/ParserBenchmarks.cs
+++ b/src/Kuddle.Benchmarks/ParserBenchmarks.cs
@@ -64,8 +64,8 @@ port 8080
}
_largeDocument = largeDocBuilder.ToString();
- _compiledParser = KuddleGrammar.Document.Compile();
- _nonCompiledParser = KuddleGrammar.Document;
+ _compiledParser = KdlGrammar.Document.Compile();
+ _nonCompiledParser = KdlGrammar.Document;
}
[Benchmark]
diff --git a/src/Kuddle.Tests/Errors/ErrorHandlingTests.cs b/src/Kuddle.Tests/Errors/ErrorHandlingTests.cs
index 0c4a7bb..84c6ec0 100644
--- a/src/Kuddle.Tests/Errors/ErrorHandlingTests.cs
+++ b/src/Kuddle.Tests/Errors/ErrorHandlingTests.cs
@@ -1,4 +1,5 @@
using Kuddle.Exceptions;
+using Kuddle.Serialization;
namespace Kuddle.Tests.Errors;
@@ -12,7 +13,7 @@ private static async Task AssertParseFails(
int? expectedLine = null
)
{
- var ex = await Assert.ThrowsAsync(async () => KuddleReader.Parse(kdl));
+ var ex = await Assert.ThrowsAsync(async () => KdlReader.Read(kdl));
await Assert
.That(ex.Message)
diff --git a/src/Kuddle.Tests/Extensions/DxTests.cs b/src/Kuddle.Tests/Extensions/DxTests.cs
index a15c293..aa230db 100644
--- a/src/Kuddle.Tests/Extensions/DxTests.cs
+++ b/src/Kuddle.Tests/Extensions/DxTests.cs
@@ -1,6 +1,7 @@
using Kuddle.AST;
using Kuddle.Extensions;
using Kuddle.Parser;
+using Kuddle.Serialization;
namespace Kuddle.Tests.Extensions;
@@ -9,7 +10,7 @@ public class DxTests
[Test]
public async Task TryGet_Int_Success()
{
- var doc = KuddleReader.Parse("node 123");
+ var doc = KdlReader.Read("node 123");
var val = doc.Nodes[0].Arg(0)!;
bool success = val.TryGetInt(out int result);
@@ -21,7 +22,7 @@ public async Task TryGet_Int_Success()
[Test]
public async Task TryGet_Int_Failure_WrongType()
{
- var doc = KuddleReader.Parse("node \"hello\"");
+ var doc = KdlReader.Read("node \"hello\"");
var val = doc.Nodes[0].Arg(0)!;
bool success = val.TryGetInt(out int result);
@@ -34,7 +35,7 @@ public async Task TryGet_Int_Failure_WrongType()
public async Task TryGet_Int_Failure_Overflow()
{
// Value larger than Int32
- var doc = KuddleReader.Parse("node 9999999999");
+ var doc = KdlReader.Read("node 9999999999");
var val = doc.Nodes[0].Arg(0)!;
bool success = val.TryGetInt(out int result);
@@ -45,7 +46,7 @@ public async Task TryGet_Int_Failure_Overflow()
[Test]
public async Task TryGet_Prop_Navigation_Success()
{
- var doc = KuddleReader.Parse("server port=8080");
+ var doc = KdlReader.Read("server port=8080");
var node = doc.Nodes[0];
// Combine finding the prop and converting it
@@ -64,7 +65,7 @@ public async Task TryGet_Prop_Navigation_Success()
[Test]
public async Task TryGet_Prop_Navigation_Missing()
{
- var doc = KuddleReader.Parse("server host=\"localhost\"");
+ var doc = KdlReader.Read("server host=\"localhost\"");
var node = doc.Nodes[0];
var propVal = node.Prop("port")!; // Returns KdlNull
@@ -78,7 +79,7 @@ public async Task TryGetUuid_ValidGuidString_ReturnsTrue()
{
var expected = Guid.NewGuid();
var kdl = $"node \"{expected}\""; // e.g. "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
- var doc = KuddleReader.Parse(kdl);
+ var doc = KdlReader.Read(kdl);
var val = doc.Nodes[0].Arg(0)!;
bool success = val.TryGetUuid(out var result);
@@ -90,7 +91,7 @@ public async Task TryGetUuid_ValidGuidString_ReturnsTrue()
[Test]
public async Task TryGetUuid_InvalidString_ReturnsFalse()
{
- var doc = KuddleReader.Parse("node \"not-a-guid\"");
+ var doc = KdlReader.Read("node \"not-a-guid\"");
var val = doc.Nodes[0].Arg(0)!;
bool success = val.TryGetUuid(out var result);
@@ -104,7 +105,7 @@ public async Task TryGetUuid_FromAnnotatedValue_ReturnsTrue()
{
var expected = Guid.NewGuid();
var kdl = $"node (uuid)\"{expected}\"";
- var doc = KuddleReader.Parse(kdl);
+ var doc = KdlReader.Read(kdl);
var val = doc.Nodes[0].Arg(0)!;
bool success = val.TryGetUuid(out var result);
@@ -138,7 +139,7 @@ public async Task TryGetDateTime_ValidIso8601_ReturnsTrue()
var now = DateTimeOffset.UtcNow;
// Round-trip format "O" is standard for KDL/JSON
var kdl = $"node \"{now:O}\"";
- var doc = KuddleReader.Parse(kdl);
+ var doc = KdlReader.Read(kdl);
var val = doc.Nodes[0].Arg(0)!;
bool success = val.TryGetDateTime(out var result);
@@ -153,7 +154,7 @@ public async Task TryGetDateTime_DateOnly_ReturnsTrue()
{
// YYYY-MM-DD
var kdl = "node \"2023-10-25\"";
- var doc = KuddleReader.Parse(kdl);
+ var doc = KdlReader.Read(kdl);
var val = doc.Nodes[0].Arg(0)!;
bool success = val.TryGetDateTime(out var result);
@@ -167,7 +168,7 @@ public async Task TryGetDateTime_DateOnly_ReturnsTrue()
[Test]
public async Task TryGetDateTime_InvalidString_ReturnsFalse()
{
- var doc = KuddleReader.Parse("node \"tomorrow\"");
+ var doc = KdlReader.Read("node \"tomorrow\"");
var val = doc.Nodes[0].Arg(0)!;
bool success = val.TryGetDateTime(out var result);
diff --git a/src/Kuddle.Tests/Grammar/CommentParserTests.cs b/src/Kuddle.Tests/Grammar/CommentParserTests.cs
index 6448ef5..29b7750 100644
--- a/src/Kuddle.Tests/Grammar/CommentParserTests.cs
+++ b/src/Kuddle.Tests/Grammar/CommentParserTests.cs
@@ -7,7 +7,7 @@ public class CommentParserTests
[Test]
public async Task CanParseSimpleSingleLineComment()
{
- var sut = KuddleGrammar.SingleLineComment;
+ var sut = KdlGrammar.SingleLineComment;
var comment = """// I am a single line comment""";
@@ -20,7 +20,7 @@ public async Task CanParseSimpleSingleLineComment()
[Test]
public async Task CanParseSimpleMultiLineComment()
{
- var sut = KuddleGrammar.MultiLineComment;
+ var sut = KdlGrammar.MultiLineComment;
var comment = """
/*
@@ -38,7 +38,7 @@ public async Task CanParseSimpleMultiLineComment()
[Test]
public async Task CanParseNestedMultiLineComment()
{
- var sut = KuddleGrammar.MultiLineComment;
+ var sut = KdlGrammar.MultiLineComment;
var comment = """
/*
diff --git a/src/Kuddle.Tests/Grammar/NodeParserTests.cs b/src/Kuddle.Tests/Grammar/NodeParserTests.cs
index b9745fb..a8f59d7 100644
--- a/src/Kuddle.Tests/Grammar/NodeParserTests.cs
+++ b/src/Kuddle.Tests/Grammar/NodeParserTests.cs
@@ -10,7 +10,7 @@ public class NodeParsersTests
[Test]
public async Task Type_ParsesSimpleType()
{
- var sut = KuddleGrammar.Type;
+ var sut = KdlGrammar.Type;
var input = "(string)";
bool success = sut.TryParse(input, out var result);
@@ -22,7 +22,7 @@ public async Task Type_ParsesSimpleType()
[Test]
public async Task Prop_ParsesSimpleProperty()
{
- var sut = KuddleGrammar.Node;
+ var sut = KdlGrammar.Node;
var input = "node key=value";
bool success = sut.TryParse(input, out var node);
@@ -39,7 +39,7 @@ public async Task Prop_ParsesSimpleProperty()
[Test]
public async Task Node_ParsesComplexLine()
{
- var sut = KuddleGrammar.Node;
+ var sut = KdlGrammar.Node;
// Test: Name, Arg, Prop, Type Annotation
var input = "(my-type)node 123 key=\"value\";";
@@ -69,7 +69,7 @@ public async Task Node_ParsesComplexLine()
[Test]
public async Task Node_ParsesChildren()
{
- var sut = KuddleGrammar.Node;
+ var sut = KdlGrammar.Node;
var input = "parent { child; }";
bool success = sut.TryParse(input, out var node, out var error);
@@ -84,7 +84,7 @@ public async Task Node_ParsesChildren()
[Test]
public async Task Node_ParsesMixedContent()
{
- var sut = KuddleGrammar.Node;
+ var sut = KdlGrammar.Node;
var input = "(type)node 10 prop=#true { child; }";
bool success = sut.TryParse(input, out var node);
@@ -111,7 +111,7 @@ await Assert
public async Task Node_SlashDash_SkipsNode()
{
// This tests the logic in 'Nodes' (plural) parser usually
- var sut = KuddleGrammar.Document;
+ var sut = KdlGrammar.Document;
var input = "node1; /- node2; node3;";
bool success = sut.TryParse(input, out var doc);
@@ -125,7 +125,7 @@ public async Task Node_SlashDash_SkipsNode()
[Test]
public async Task Node_SlashDash_SkipsArg()
{
- var sut = KuddleGrammar.Node;
+ var sut = KdlGrammar.Node;
var input = "node 1 /- 2 3";
bool success = sut.TryParse(input, out var node);
@@ -145,7 +145,7 @@ public async Task Node_SlashDash_SkipsArg()
[Test]
public async Task SlashDash_SkipsProperty()
{
- var sut = KuddleGrammar.Node;
+ var sut = KdlGrammar.Node;
var input = "node key=1 /- skipped=2 valid=3";
bool success = sut.TryParse(input, out var node);
@@ -163,7 +163,7 @@ public async Task SlashDash_SkipsProperty()
[Test]
public async Task Node_SlashDash_SkipsChildrenBlock()
{
- var sut = KuddleGrammar.Node;
+ var sut = KdlGrammar.Node;
// Parsing a node that has a slash-dashed children block
var input = "node /- { child; }";
@@ -178,7 +178,7 @@ public async Task Node_SlashDash_SkipsChildrenBlock()
[Test]
public async Task Nodes_ParsesNodesWithWhitespace()
{
- var sut = KuddleGrammar.Nodes;
+ var sut = KdlGrammar.Nodes;
var input =
@"
node1;
diff --git a/src/Kuddle.Tests/Grammar/NumberParsersTests.cs b/src/Kuddle.Tests/Grammar/NumberParsersTests.cs
index e9ea2c8..6edcdef 100644
--- a/src/Kuddle.Tests/Grammar/NumberParsersTests.cs
+++ b/src/Kuddle.Tests/Grammar/NumberParsersTests.cs
@@ -7,7 +7,7 @@ public class NumberParsersTests
[Test]
public async Task Decimal_ParsesPositiveInteger()
{
- var sut = KuddleGrammar.Decimal;
+ var sut = KdlGrammar.Decimal;
var input = "42";
bool success = sut.TryParse(input, out var value);
@@ -19,7 +19,7 @@ public async Task Decimal_ParsesPositiveInteger()
[Test]
public async Task Decimal_ParsesNegativeInteger()
{
- var sut = KuddleGrammar.Decimal;
+ var sut = KdlGrammar.Decimal;
var input = "-42";
bool success = sut.TryParse(input, out var value);
@@ -31,7 +31,7 @@ public async Task Decimal_ParsesNegativeInteger()
[Test]
public async Task Decimal_ParsesFractionalNumber()
{
- var sut = KuddleGrammar.Decimal;
+ var sut = KdlGrammar.Decimal;
var input = "3.14159";
bool success = sut.TryParse(input, out var value);
@@ -43,7 +43,7 @@ public async Task Decimal_ParsesFractionalNumber()
[Test]
public async Task Decimal_ParsesScientificNotation()
{
- var sut = KuddleGrammar.Decimal;
+ var sut = KdlGrammar.Decimal;
var input = "1.23e-4";
bool success = sut.TryParse(input, out var value);
@@ -55,7 +55,7 @@ public async Task Decimal_ParsesScientificNotation()
[Test]
public async Task Decimal_ParsesScientificNotationUppercase()
{
- var sut = KuddleGrammar.Decimal;
+ var sut = KdlGrammar.Decimal;
var input = "6.02E23";
bool success = sut.TryParse(input, out var value);
@@ -67,7 +67,7 @@ public async Task Decimal_ParsesScientificNotationUppercase()
[Test]
public async Task Decimal_ParsesWithUnderscoreSeparators()
{
- var sut = KuddleGrammar.Decimal;
+ var sut = KdlGrammar.Decimal;
var input = "1_000_000";
bool success = sut.TryParse(input, out var value);
@@ -79,7 +79,7 @@ public async Task Decimal_ParsesWithUnderscoreSeparators()
[Test]
public async Task Decimal_ParsesFractionalWithUnderscores()
{
- var sut = KuddleGrammar.Decimal;
+ var sut = KdlGrammar.Decimal;
var input = "12_34.56_78";
bool success = sut.TryParse(input, out var value);
@@ -91,7 +91,7 @@ public async Task Decimal_ParsesFractionalWithUnderscores()
[Test]
public async Task Hex_ParsesHexNumbers()
{
- var sut = KuddleGrammar.Hex;
+ var sut = KdlGrammar.Hex;
var input = "0xFF";
bool success = sut.TryParse(input, out var value);
@@ -103,7 +103,7 @@ public async Task Hex_ParsesHexNumbers()
[Test]
public async Task Hex_ParsesHexWithUnderscores()
{
- var sut = KuddleGrammar.Hex;
+ var sut = KdlGrammar.Hex;
var input = "0x123_ABC";
bool success = sut.TryParse(input, out var value);
@@ -115,7 +115,7 @@ public async Task Hex_ParsesHexWithUnderscores()
[Test]
public async Task Hex_ParsesNegativeHex()
{
- var sut = KuddleGrammar.Hex;
+ var sut = KdlGrammar.Hex;
var input = "-0x42";
bool success = sut.TryParse(input, out var value);
@@ -127,7 +127,7 @@ public async Task Hex_ParsesNegativeHex()
[Test]
public async Task Octal_ParsesOctalNumbers()
{
- var sut = KuddleGrammar.Octal;
+ var sut = KdlGrammar.Octal;
var input = "0o777";
bool success = sut.TryParse(input, out var value);
@@ -139,7 +139,7 @@ public async Task Octal_ParsesOctalNumbers()
[Test]
public async Task Octal_ParsesOctalWithUnderscores()
{
- var sut = KuddleGrammar.Octal;
+ var sut = KdlGrammar.Octal;
var input = "0o123_456";
bool success = sut.TryParse(input, out var value);
@@ -151,7 +151,7 @@ public async Task Octal_ParsesOctalWithUnderscores()
[Test]
public async Task Octal_ParsesNegativeOctal()
{
- var sut = KuddleGrammar.Octal;
+ var sut = KdlGrammar.Octal;
var input = "-0o42";
bool success = sut.TryParse(input, out var value);
@@ -163,7 +163,7 @@ public async Task Octal_ParsesNegativeOctal()
[Test]
public async Task Binary_ParsesBinaryNumbers()
{
- var sut = KuddleGrammar.Binary;
+ var sut = KdlGrammar.Binary;
var input = "0b1010";
bool success = sut.TryParse(input, out var value);
@@ -175,7 +175,7 @@ public async Task Binary_ParsesBinaryNumbers()
[Test]
public async Task Binary_ParsesBinaryWithUnderscores()
{
- var sut = KuddleGrammar.Binary;
+ var sut = KdlGrammar.Binary;
var input = "0b1111_0000";
bool success = sut.TryParse(input, out var value);
@@ -187,7 +187,7 @@ public async Task Binary_ParsesBinaryWithUnderscores()
[Test]
public async Task Binary_ParsesNegativeBinary()
{
- var sut = KuddleGrammar.Binary;
+ var sut = KdlGrammar.Binary;
var input = "-0b101";
bool success = sut.TryParse(input, out var value);
@@ -199,7 +199,7 @@ public async Task Binary_ParsesNegativeBinary()
[Test]
public async Task KeywordNumber_ParsesInfinity()
{
- var sut = KuddleGrammar.KeywordNumber;
+ var sut = KdlGrammar.KeywordNumber;
var input = "#inf";
bool success = sut.TryParse(input, out var value);
@@ -211,7 +211,7 @@ public async Task KeywordNumber_ParsesInfinity()
[Test]
public async Task KeywordNumber_ParsesNegativeInfinity()
{
- var sut = KuddleGrammar.KeywordNumber;
+ var sut = KdlGrammar.KeywordNumber;
var input = "#-inf";
bool success = sut.TryParse(input, out var value);
@@ -223,7 +223,7 @@ public async Task KeywordNumber_ParsesNegativeInfinity()
[Test]
public async Task KeywordNumber_ParsesNaN()
{
- var sut = KuddleGrammar.KeywordNumber;
+ var sut = KdlGrammar.KeywordNumber;
var input = "#nan";
bool success = sut.TryParse(input, out var value);
@@ -235,7 +235,7 @@ public async Task KeywordNumber_ParsesNaN()
[Test]
public async Task Number_ParsesDecimal()
{
- var sut = KuddleGrammar.Decimal;
+ var sut = KdlGrammar.Decimal;
var input = "42";
bool success = sut.TryParse(input, out var value);
@@ -247,7 +247,7 @@ public async Task Number_ParsesDecimal()
[Test]
public async Task Number_ParsesHex()
{
- var sut = KuddleGrammar.Hex;
+ var sut = KdlGrammar.Hex;
var input = "0xFF";
bool success = sut.TryParse(input, out var value);
@@ -259,7 +259,7 @@ public async Task Number_ParsesHex()
[Test]
public async Task Number_ParsesKeywordNumber()
{
- var sut = KuddleGrammar.KeywordNumber;
+ var sut = KdlGrammar.KeywordNumber;
var input = "#inf";
bool success = sut.TryParse(input, out var value);
@@ -271,7 +271,7 @@ public async Task Number_ParsesKeywordNumber()
[Test]
public async Task Decimal_RejectsDoubleDots()
{
- var sut = KuddleGrammar.Decimal.Eof();
+ var sut = KdlGrammar.Decimal.Eof();
var input = "12.34.56";
bool success = sut.TryParse(input, out var value);
diff --git a/src/Kuddle.Tests/Grammar/StringParserTests.cs b/src/Kuddle.Tests/Grammar/StringParserTests.cs
index 88050cc..622221f 100644
--- a/src/Kuddle.Tests/Grammar/StringParserTests.cs
+++ b/src/Kuddle.Tests/Grammar/StringParserTests.cs
@@ -20,7 +20,7 @@ public class StringParserTests
[Arguments("-negative")]
public async Task SignedIdent_ParsesSignedIdentifier(string input)
{
- var sut = KuddleGrammar.SignedIdent;
+ var sut = KdlGrammar.SignedIdent;
bool success = sut.TryParse(input, out var value);
@@ -34,7 +34,7 @@ public async Task SignedIdent_ParsesSignedIdentifier(string input)
[Arguments(".three")]
public async Task DottedIdent_ParsesDottedIdentifier(string input)
{
- var sut = KuddleGrammar.DottedIdent;
+ var sut = KdlGrammar.DottedIdent;
bool success = sut.TryParse(input, out var value);
@@ -47,7 +47,7 @@ public async Task DottedIdent_ParsesDottedIdentifier(string input)
[Arguments(".01")]
public async Task DottedIdent_DoesNotParseNumberDottedNumber(string input)
{
- var sut = KuddleGrammar.DottedIdent;
+ var sut = KdlGrammar.DottedIdent;
bool success = sut.TryParse(input, out var value);
@@ -62,7 +62,7 @@ public async Task DottedIdent_DoesNotParseNumberDottedNumber(string input)
[Arguments("test_case")]
public async Task UnambiguousIdent_ParsesUnambiguousIdentifier(string input)
{
- var sut = KuddleGrammar.UnambiguousIdent;
+ var sut = KdlGrammar.UnambiguousIdent;
bool success = sut.TryParse(input, out var value);
@@ -76,7 +76,7 @@ public async Task UnambiguousIdent_ParsesUnambiguousIdentifier(string input)
[Arguments(".one")]
public async Task UnambiguousIdent_DoesNotParseInvalidIdentifier(string input)
{
- var sut = KuddleGrammar.UnambiguousIdent;
+ var sut = KdlGrammar.UnambiguousIdent;
bool success = sut.TryParse(input, out var value);
@@ -92,7 +92,7 @@ public async Task UnambiguousIdent_DoesNotParseInvalidIdentifier(string input)
[Arguments("nan")]
public async Task UnambiguousIdent_DoesNotParseDisallowedKeywordString(string input)
{
- var sut = KuddleGrammar.UnambiguousIdent;
+ var sut = KdlGrammar.UnambiguousIdent;
bool success = sut.TryParse(input, out var value);
@@ -112,7 +112,7 @@ public async Task UnambiguousIdent_DoesNotParseDisallowedKeywordString(string in
[Arguments("-negative")]
public async Task IdentifierString_ParsesIdentifierString(string input)
{
- var sut = KuddleGrammar.IdentifierString;
+ var sut = KdlGrammar.IdentifierString;
bool success = sut.TryParse(input, out var value);
@@ -124,7 +124,7 @@ public async Task IdentifierString_ParsesIdentifierString(string input)
[Arguments("\"\\ \"")]
public async Task WsEscape_ParsesWhiteSpace(string input)
{
- var sut = KuddleGrammar.QuotedString;
+ var sut = KdlGrammar.QuotedString;
bool success = sut.TryParse(input, out var value, out var error);
@@ -135,7 +135,7 @@ public async Task WsEscape_ParsesWhiteSpace(string input)
[Test]
public async Task QuotedString_ParsesSingleLineString()
{
- var sut = KuddleGrammar.QuotedString;
+ var sut = KdlGrammar.QuotedString;
const string input = """
"hello world"
@@ -149,7 +149,7 @@ public async Task QuotedString_ParsesSingleLineString()
[Test]
public async Task QuotedString_ParsesEmptyString()
{
- var sut = KuddleGrammar.QuotedString;
+ var sut = KdlGrammar.QuotedString;
const string input = """
""
@@ -163,7 +163,7 @@ public async Task QuotedString_ParsesEmptyString()
[Test]
public async Task QuotedString_ParsesEmptyMultilineString()
{
- var sut = KuddleGrammar.QuotedString;
+ var sut = KdlGrammar.QuotedString;
const string input = """"
"""
@@ -206,7 +206,7 @@ This is the base indentation
)]
public async Task MultiLineStringBody_HandlesVarious(string input, string expected)
{
- var sut = KuddleGrammar.MultiLineQuoted;
+ var sut = KdlGrammar.MultiLineQuoted;
bool success = sut.TryParse(input, out var value);
Debug.WriteLine(input);
@@ -234,7 +234,7 @@ canis canem edit
)]
public async Task MultiLineQuotedString_CanParseMultiLine(string input, string expected)
{
- var sut = KuddleGrammar.MultiLineQuoted;
+ var sut = KdlGrammar.MultiLineQuoted;
bool success = sut.TryParse(input, out var value, out var error);
await Assert.That(success).IsTrue();
await Assert.That(value.ToString()).IsEqualTo(expected);
@@ -249,7 +249,7 @@ public async Task MultiLineQuotedString_CanParseMultiLine(string input, string e
)]
public async Task QuotedString_HandlesUnicodeEscapes(string input, string expected)
{
- var sut = KuddleGrammar.QuotedString;
+ var sut = KdlGrammar.QuotedString;
bool success = sut.TryParse(input, out var value);
@@ -271,7 +271,7 @@ public async Task QuotedString_HandlesUnicodeEscapes(string input, string expect
)]
public async Task QuotedString_HandlesWhitespaceEscapes(string input)
{
- var sut = KuddleGrammar.QuotedString;
+ var sut = KdlGrammar.QuotedString;
bool success = sut.TryParse(input, out var value, out var error);
@@ -283,7 +283,7 @@ public async Task QuotedString_HandlesWhitespaceEscapes(string input)
[Test]
public async Task RawString_ParsesSimpleRawString()
{
- var sut = KuddleGrammar.RawString;
+ var sut = KdlGrammar.RawString;
var input = """
#"\n will be literal"#
@@ -297,7 +297,7 @@ public async Task RawString_ParsesSimpleRawString()
[Test]
public async Task RawString_ParsesRawStringWithQuotes()
{
- var sut = KuddleGrammar.RawString;
+ var sut = KdlGrammar.RawString;
var input = "#\"content with \"quotes\"\"#";
bool success = sut.TryParse(input, out var value);
@@ -309,7 +309,7 @@ public async Task RawString_ParsesRawStringWithQuotes()
[Test]
public async Task RawString_HandlesMultipleHashDelimiters()
{
- var sut = KuddleGrammar.RawString;
+ var sut = KdlGrammar.RawString;
var input = """
##"hello\n\r\asd"#world"##
@@ -329,7 +329,7 @@ await Assert
[Test]
public async Task RawString_ParsesMultiLineRawString()
{
- var sut = KuddleGrammar.RawString;
+ var sut = KdlGrammar.RawString;
var input = """""
#"""
@@ -354,7 +354,7 @@ without escapes.
[Test]
public async Task IdentifierString_SetsStyleToBare()
{
- var sut = KuddleGrammar.IdentifierString;
+ var sut = KdlGrammar.IdentifierString;
var input = "bare_identifier";
bool success = sut.TryParse(input, out var value);
@@ -367,7 +367,7 @@ public async Task IdentifierString_SetsStyleToBare()
[Test]
public async Task QuotedString_SetsStyleToQuoted()
{
- var sut = KuddleGrammar.QuotedString;
+ var sut = KdlGrammar.QuotedString;
var input = "\"quoted value\"";
bool success = sut.TryParse(input, out var value);
@@ -381,7 +381,7 @@ public async Task QuotedString_SetsStyleToQuoted()
[Test]
public async Task MultiLineString_SetsStyleToMultiline()
{
- var sut = KuddleGrammar.MultiLineQuoted;
+ var sut = KdlGrammar.MultiLineQuoted;
var input = """"
"""
content
@@ -399,7 +399,7 @@ public async Task MultiLineString_SetsStyleToMultiline()
[Test]
public async Task RawString_SingleLine_SetsStyleToRawAndQuoted()
{
- var sut = KuddleGrammar.RawString;
+ var sut = KdlGrammar.RawString;
var input = "#\"\"raw content\"\"#";
bool success = sut.TryParse(input, out var value);
@@ -416,7 +416,7 @@ public async Task RawString_SingleLine_SetsStyleToRawAndQuoted()
[Test]
public async Task RawString_MultiLine_SetsStyleToRawAndMultiline()
{
- var sut = KuddleGrammar.RawString;
+ var sut = KdlGrammar.RawString;
var input = """"
#"""
multi
@@ -438,7 +438,7 @@ public async Task RawString_MultiLine_SetsStyleToRawAndMultiline()
[Test]
public async Task String_UnifiedParser_DetectsBare()
{
- var sut = KuddleGrammar.String;
+ var sut = KdlGrammar.String;
var input = "node_name";
bool success = sut.TryParse(input, out var value);
@@ -450,7 +450,7 @@ public async Task String_UnifiedParser_DetectsBare()
[Test]
public async Task String_UnifiedParser_DetectsQuoted()
{
- var sut = KuddleGrammar.String;
+ var sut = KdlGrammar.String;
var input = "\"node name\"";
bool success = sut.TryParse(input, out var value);
@@ -462,7 +462,7 @@ public async Task String_UnifiedParser_DetectsQuoted()
[Test]
public async Task String_UnifiedParser_DetectsRaw()
{
- var sut = KuddleGrammar.String;
+ var sut = KdlGrammar.String;
var input = @"#""node name""#";
bool success = sut.TryParse(input, out var value);
diff --git a/src/Kuddle.Tests/Grammar/WhiteSpaceParsersTests.cs b/src/Kuddle.Tests/Grammar/WhiteSpaceParsersTests.cs
index 2613baa..4dd9a40 100644
--- a/src/Kuddle.Tests/Grammar/WhiteSpaceParsersTests.cs
+++ b/src/Kuddle.Tests/Grammar/WhiteSpaceParsersTests.cs
@@ -33,7 +33,7 @@ public class WhiteSpaceParsersTests
[Arguments('\u3000')]
public async Task Ws_ParsesUnicodeSpace(char input)
{
- var sut = KuddleGrammar.Ws;
+ var sut = KdlGrammar.Ws;
bool success = sut.TryParse(input.ToString(), out var value);
@@ -44,7 +44,7 @@ public async Task Ws_ParsesUnicodeSpace(char input)
[Test]
public async Task Ws_ParsesMultiLineComment()
{
- var sut = KuddleGrammar.Ws;
+ var sut = KdlGrammar.Ws;
var input = "/* comment */";
bool success = sut.TryParse(input, out var value);
@@ -56,7 +56,7 @@ public async Task Ws_ParsesMultiLineComment()
[Test]
public async Task EscLine_ParsesBackslashContinuation()
{
- var sut = KuddleGrammar.EscLine;
+ var sut = KdlGrammar.EscLine;
var input =
@"\
@@ -69,7 +69,7 @@ public async Task EscLine_ParsesBackslashContinuation()
[Test]
public async Task EscLine_ParsesBackslashWithComment()
{
- var sut = KuddleGrammar.EscLine;
+ var sut = KdlGrammar.EscLine;
var input = @"\ // comment";
bool success = sut.TryParse(input, out var value);
@@ -81,7 +81,7 @@ public async Task EscLine_ParsesBackslashWithComment()
[Test]
public async Task LineSpace_ParsesWhitespace()
{
- var sut = KuddleGrammar.LineSpace;
+ var sut = KdlGrammar.LineSpace;
var input = " ";
bool success = sut.TryParse(input, out var value);
@@ -93,7 +93,7 @@ public async Task LineSpace_ParsesWhitespace()
[Test]
public async Task LineSpace_ParsesNewLine()
{
- var sut = KuddleGrammar.LineSpace;
+ var sut = KdlGrammar.LineSpace;
var input = "\n";
bool success = sut.TryParse(input, out var value);
@@ -105,7 +105,7 @@ public async Task LineSpace_ParsesNewLine()
[Test]
public async Task LineSpace_ParsesComment()
{
- var sut = KuddleGrammar.LineSpace;
+ var sut = KdlGrammar.LineSpace;
var input = "// comment";
bool success = sut.TryParse(input, out var value);
@@ -117,7 +117,7 @@ public async Task LineSpace_ParsesComment()
[Test]
public async Task NodeSpace_ParsesSimpleWhitespace()
{
- var sut = KuddleGrammar.NodeSpace;
+ var sut = KdlGrammar.NodeSpace;
var input = " ";
bool success = sut.TryParse(input, out var value);
diff --git a/src/Kuddle.Tests/Serialization/DocumentToObjectTests.cs b/src/Kuddle.Tests/Serialization/DocumentToObjectTests.cs
new file mode 100644
index 0000000..3c62d07
--- /dev/null
+++ b/src/Kuddle.Tests/Serialization/DocumentToObjectTests.cs
@@ -0,0 +1,3 @@
+namespace Kuddle.Tests.Serialization;
+
+public class DocumentToObjectTests { }
diff --git a/src/Kuddle.Tests/Serialization/KuddleParsingTests.cs b/src/Kuddle.Tests/Serialization/KuddleParsingTests.cs
index 9cdc429..57ad418 100644
--- a/src/Kuddle.Tests/Serialization/KuddleParsingTests.cs
+++ b/src/Kuddle.Tests/Serialization/KuddleParsingTests.cs
@@ -4,7 +4,7 @@ namespace Kuddle.Tests.Serialization;
public class KuddleParsingTests
{
- readonly KuddleWriterOptions _options = new KuddleWriterOptions() with { RoundTrip = false };
+ readonly KdlWriterOptions _options = new KdlWriterOptions() with { RoundTrip = false };
[Test]
[MethodDataSource(
@@ -16,7 +16,7 @@ public async Task TestBlockComment(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -24,7 +24,7 @@ public async Task TestBlockComment(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -38,7 +38,7 @@ public async Task TestEscline(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -46,7 +46,7 @@ public async Task TestEscline(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -60,7 +60,7 @@ public async Task TestMultilineRawString(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -68,7 +68,7 @@ public async Task TestMultilineRawString(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -82,7 +82,7 @@ public async Task TestMultilineString(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -90,18 +90,21 @@ public async Task TestMultilineString(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.ArgTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.ArgTestData)
+ )]
public async Task TestArg(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options); // Assumes KdlDocument has ToString() for serialization
@@ -109,7 +112,7 @@ public async Task TestArg(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -123,7 +126,7 @@ public async Task TestBareIdent(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -131,7 +134,7 @@ public async Task TestBareIdent(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -145,7 +148,7 @@ public async Task TestBinary(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -153,18 +156,21 @@ public async Task TestBinary(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.BlankTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.BlankTestData)
+ )]
public async Task TestBlank(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -172,7 +178,7 @@ public async Task TestBlank(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -186,7 +192,7 @@ public async Task TestBoolean(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -194,18 +200,21 @@ public async Task TestBoolean(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.BomTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.BomTestData)
+ )]
public async Task TestBom(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -213,7 +222,7 @@ public async Task TestBom(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -227,7 +236,7 @@ public async Task TestComment(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -235,7 +244,7 @@ public async Task TestComment(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -249,7 +258,7 @@ public async Task TestCommented(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -257,18 +266,21 @@ public async Task TestCommented(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.EmptyTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.EmptyTestData)
+ )]
public async Task TestEmpty(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -276,18 +288,21 @@ public async Task TestEmpty(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.EscTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.EscTestData)
+ )]
public async Task TestEsc(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -295,18 +310,21 @@ public async Task TestEsc(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.FalseTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.FalseTestData)
+ )]
public async Task TestFalse(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -314,7 +332,7 @@ public async Task TestFalse(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -328,7 +346,7 @@ public async Task TestIllegal(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -336,7 +354,7 @@ public async Task TestIllegal(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -350,7 +368,7 @@ public async Task TestBareEmoji(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -358,18 +376,21 @@ public async Task TestBareEmoji(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.AllTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.AllTestData)
+ )]
public async Task TestAll(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -377,7 +398,7 @@ public async Task TestAll(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -391,7 +412,7 @@ public async Task TestAsterisk(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -399,7 +420,7 @@ public async Task TestAsterisk(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -413,7 +434,7 @@ public async Task TestBraces(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -421,7 +442,7 @@ public async Task TestBraces(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -435,7 +456,7 @@ public async Task TestChevrons(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -443,18 +464,21 @@ public async Task TestChevrons(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.CommaTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.CommaTestData)
+ )]
public async Task TestComma(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -462,18 +486,21 @@ public async Task TestComma(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.CrlfTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.CrlfTestData)
+ )]
public async Task TestCrlf(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -481,18 +508,21 @@ public async Task TestCrlf(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.DashTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.DashTestData)
+ )]
public async Task TestDash(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -500,18 +530,21 @@ public async Task TestDash(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.DotTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.DotTestData)
+ )]
public async Task TestDot(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -519,18 +552,21 @@ public async Task TestDot(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.EmojiTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.EmojiTestData)
+ )]
public async Task TestEmoji(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -538,18 +574,21 @@ public async Task TestEmoji(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.EofTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.EofTestData)
+ )]
public async Task TestEof(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -557,18 +596,21 @@ public async Task TestEof(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.ErrTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.ErrTestData)
+ )]
public async Task TestErr(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -576,7 +618,7 @@ public async Task TestErr(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -590,7 +632,7 @@ public async Task TestEscaped(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -598,18 +640,21 @@ public async Task TestEscaped(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.HexTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.HexTestData)
+ )]
public async Task TestHex(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -617,7 +662,7 @@ public async Task TestHex(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
@@ -631,7 +676,7 @@ public async Task TestInitial(ParsingTestData testData)
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -639,18 +684,21 @@ public async Task TestInitial(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.IntTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.IntTestData)
+ )]
public async Task TestInt(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -658,18 +706,21 @@ public async Task TestInt(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
[Test]
- [MethodDataSource(typeof(KuddleParsingTestDataSources), nameof(KuddleParsingTestDataSources.JustTestData))]
+ [MethodDataSource(
+ typeof(KuddleParsingTestDataSources),
+ nameof(KuddleParsingTestDataSources.JustTestData)
+ )]
public async Task TestJust(ParsingTestData testData)
{
if (File.Exists(testData.ExpectedFile))
{
var inputKdl = await File.ReadAllTextAsync(testData.InputFile);
- var doc = KuddleReader.Parse(inputKdl);
+ var doc = KdlReader.Read(inputKdl);
var expected = await File.ReadAllTextAsync(testData.ExpectedFile);
expected = expected.Replace("\r\n", "\n");
var serialized = doc.ToString(_options);
@@ -677,7 +728,7 @@ public async Task TestJust(ParsingTestData testData)
}
else
{
- Assert.Throws(() => KuddleReader.Parse(testData.InputFile));
+ Assert.Throws(() => KdlReader.Read(testData.InputFile));
}
}
}
diff --git a/src/Kuddle.Tests/Serialization/KuddleWriterTests.cs b/src/Kuddle.Tests/Serialization/KuddleWriterTests.cs
index 357858c..ed5bb09 100644
--- a/src/Kuddle.Tests/Serialization/KuddleWriterTests.cs
+++ b/src/Kuddle.Tests/Serialization/KuddleWriterTests.cs
@@ -10,9 +10,9 @@ public class KuddleWriterTests
public async Task Write_SimpleNode_FormatsCorrectly()
{
var kdl = "node 1 2 key=\"val\"";
- var doc = KuddleReader.Parse(kdl);
+ var doc = KdlReader.Read(kdl);
- var output = KuddleWriter.Write(doc, KuddleWriterOptions.Default);
+ var output = KdlWriter.Write(doc, KdlWriterOptions.Default);
await Assert.That(output.Trim()).IsEqualTo("node 1 2 key=\"val\"");
}
@@ -21,9 +21,9 @@ public async Task Write_SimpleNode_FormatsCorrectly()
public async Task Write_NestedStructure_IndentsCorrectly()
{
var kdl = "parent { child; }";
- var doc = KuddleReader.Parse(kdl);
+ var doc = KdlReader.Read(kdl);
- var output = KuddleWriter.Write(doc);
+ var output = KdlWriter.Write(doc);
var expected = @"parent {
child;
@@ -36,9 +36,9 @@ public async Task Write_NestedStructure_IndentsCorrectly()
public async Task Write_ComplexString_EscapesCorrectly()
{
var kdl = "node \"line1\\nline2\"";
- var doc = KuddleReader.Parse(kdl);
+ var doc = KdlReader.Read(kdl);
- var output = KuddleWriter.Write(doc);
+ var output = KdlWriter.Write(doc);
await Assert.That(output.Trim()).IsEqualTo("node \"line1\\nline2\"");
}
@@ -51,7 +51,7 @@ public async Task Write_BareIdentifier_QuotesIfInvalid()
Nodes = [new KdlNode(new KdlString("node name", StringKind.Quoted))],
};
- var output = KuddleWriter.Write(doc);
+ var output = KdlWriter.Write(doc);
await Assert.That(output.Trim()).IsEqualTo("\"node name\"");
}
diff --git a/src/Kuddle.Tests/Serialization/NodeToObjectTests.cs b/src/Kuddle.Tests/Serialization/NodeToObjectTests.cs
new file mode 100644
index 0000000..ab20620
--- /dev/null
+++ b/src/Kuddle.Tests/Serialization/NodeToObjectTests.cs
@@ -0,0 +1,127 @@
+using Kuddle.Exceptions;
+using Kuddle.Serialization;
+
+namespace Kuddle.Tests.Serialization;
+
+public class NodeToObjectTests
+{
+ // 1. Explicit Naming via [KdlType]
+ [KdlType("database")]
+ public class DbConfig
+ {
+ [KdlArgument(0)]
+ public string Name { get; set; } = "";
+
+ [KdlProperty("port")]
+ public int Port { get; set; }
+
+ [KdlProperty("enabled")]
+ public bool Enabled { get; set; } = true; // Default value
+ }
+
+ // 2. Implicit Naming (Class Name fallback)
+ public class Server
+ {
+ [KdlArgument(0)]
+ public string Host { get; set; } = "";
+ }
+
+ [Test]
+ public async Task Deserialize_ExplicitName_MapsArgumentsAndProperties()
+ {
+ var kdl = "database production port=5432 enabled=#false";
+
+ var result = KdlSerializer.Deserialize(kdl);
+
+ await Assert.That(result).IsNotNull();
+ await Assert.That(result.Name).IsEqualTo("production");
+ await Assert.That(result.Port).IsEqualTo(5432);
+ await Assert.That(result.Enabled).IsFalse();
+ }
+
+ [Test]
+ public async Task Deserialize_ImplicitName_UsesClassName()
+ {
+ // Class is "Server", so it expects node "server" (case-insensitive usually, or exact match)
+ // Assuming your logic defaults to exact or lowercase.
+ // Let's assume case-insensitive or exact match "Server".
+ // If your logic uses .ToLowerInvariant(), input should be "server".
+ var kdl = "Server \"localhost\"";
+
+ var result = KdlSerializer.Deserialize(kdl);
+
+ await Assert.That(result).IsNotNull();
+ await Assert.That(result.Host).IsEqualTo("localhost");
+ }
+
+ [Test]
+ public async Task Deserialize_MissingProperty_UsesDefault()
+ {
+ // "enabled" is missing, should stay true
+ var kdl = "database \"local\" port=3000";
+
+ var result = KdlSerializer.Deserialize(kdl);
+
+ await Assert.That(result.Name).IsEqualTo("local");
+ await Assert.That(result.Enabled).IsTrue();
+ }
+
+ [Test]
+ public async Task Deserialize_MismatchNodeName_Throws()
+ {
+ // Expecting "database", got "table"
+ var kdl = "table \"production\" port=5432";
+
+ // This asserts that the Serializer enforces the [KdlType] name
+ await Assert.ThrowsAsync(async () =>
+ {
+ KdlSerializer.Deserialize(kdl);
+ });
+ }
+
+ [Test]
+ public async Task Deserialize_MultipleRootNodes_Throws()
+ {
+ // Node-to-Object strategy implies the Type represents A SINGLE node.
+ // A document with two nodes is ambiguous/invalid for this mapping.
+ var kdl =
+ @"
+ database ""primary"" port=5432
+ database ""replica"" port=5433
+ ";
+
+ await Assert.ThrowsAsync(async () =>
+ {
+ KdlSerializer.Deserialize(kdl);
+ });
+ }
+
+ [Test]
+ public async Task Deserialize_TypeMismatch_Throws()
+ {
+ // Port expects int, got string identifier
+ var kdl = "database \"db\" port=\"not-a-number\"";
+
+ await Assert.ThrowsAsync(async () =>
+ {
+ KdlSerializer.Deserialize(kdl);
+ });
+ }
+
+ [Test]
+ public async Task Deserialize_DocumentRoot_RejectsProperties()
+ {
+ // If the serializer logic correctly identifies this as a Node
+ // (because it has [KdlArgument]), it handles it.
+ // But if we tried to put a property at the top level in the FILE that doesn't
+ // belong to a node (which is syntactically impossible in KDL anyway), the Parser would catch it.
+
+ // However, we can test that extraneous properties on the node are simply ignored
+ // (unless you implemented strict mode).
+ var kdl = "database \"db\" port=5432 unknown_prop=123";
+
+ // Should succeed and ignore unknown_prop
+ var result = KdlSerializer.Deserialize(kdl);
+ await Assert.That(result.Port).IsEqualTo(5432);
+ }
+}
diff --git a/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs b/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs
index 27dd4a8..915455d 100644
--- a/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs
+++ b/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs
@@ -1,11 +1,10 @@
using Kuddle.AST;
-using Kuddle.Exceptions;
using Kuddle.Serialization;
namespace Kuddle.Tests.Serialization;
///
-/// Tests for await KdlSerializer.Deserialize() and KdlSerializer.Serialize()
+/// Tests for KdlSerializer.Deserialize() and KdlSerializer.Serialize()
/// which map between KDL documents and strongly-typed C# objects.
///
public class ObjectMapperTests
@@ -21,7 +20,7 @@ public async Task DeserializeSimpleObject_WithArgument_MapsCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Name).IsEqualTo("my-lib");
@@ -36,7 +35,7 @@ public async Task DeserializeSimpleObject_WithProperties_MapsCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Name).IsEqualTo("my-lib");
@@ -53,7 +52,7 @@ public async Task DeserializeObject_WithMissingOptionalProperty_UsesDefault()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Description).IsNull();
@@ -77,7 +76,7 @@ public async Task DeserializeObject_WithChildren_MapsChildListCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Name).IsEqualTo("my-app");
@@ -101,7 +100,7 @@ public async Task DeserializeObject_WithMultipleChildTypes_MapsEachTypeToCorrect
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Dependencies).Count().IsEqualTo(2);
@@ -119,7 +118,7 @@ public async Task DeserializeObject_WithNoChildren_InitializesEmptyLists()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Dependencies).IsEmpty();
@@ -139,7 +138,7 @@ public async Task DeserializeObject_WithIntProperty_MapsCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Timeout).IsEqualTo(5000);
@@ -154,7 +153,7 @@ public async Task DeserializeObject_WithLongProperty_MapsCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Retries).IsEqualTo(long.MaxValue);
@@ -169,7 +168,7 @@ public async Task DeserializeObject_WithDoubleProperty_MapsCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Ratio).IsEqualTo(3.14159).Within(0.00001);
@@ -184,7 +183,7 @@ public async Task DeserializeObject_WithBoolProperty_MapsCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Enabled).IsTrue();
@@ -199,7 +198,7 @@ public async Task DeserializeObject_WithAllNumericTypes_MapsAllCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Timeout).IsEqualTo(500);
@@ -222,7 +221,7 @@ public async Task DeserializeObject_WithGuidProperty_ParsesUuidTypeAnnotation()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Username).IsEqualTo("alice");
@@ -239,7 +238,7 @@ public async Task DeserializeObject_WithDateTimeProperty_ParsesDateTimeTypeAnnot
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.CreatedAt).IsEqualTo(now);
@@ -309,7 +308,7 @@ public async Task RoundTrip_SimpleObject_PreservesData()
// Act
var kdl = KdlSerializer.Serialize(original);
- var deserialized = await KdlSerializer.Deserialize(kdl);
+ var deserialized = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(deserialized.Name).IsEqualTo(original.Name);
@@ -344,7 +343,7 @@ public async Task RoundTrip_NestedObject_PreservesData()
// Act
var kdl = KdlSerializer.Serialize(original);
- var deserialized = await KdlSerializer.Deserialize(kdl);
+ var deserialized = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(deserialized.Name).IsEqualTo(original.Name);
@@ -366,7 +365,7 @@ public async Task RoundTrip_NestedObject_PreservesData()
// """;
// // Act
- // var result = await KdlSerializer.Deserialize(kdl);
+ // var result = KdlSerializer.Deserialize(kdl);
// // Assert
// await Assert.That(result).IsOfType(typeof(FileResource));
@@ -383,7 +382,7 @@ public async Task RoundTrip_NestedObject_PreservesData()
// """;
// // Act
- // var result = await KdlSerializer.Deserialize(kdl);
+ // var result = KdlSerializer.Deserialize(kdl);
// // Assert
// await Assert.That(result).IsOfType(typeof(UrlResource));
@@ -404,7 +403,7 @@ public async Task DeserializeObject_WithEmptyString_MapsCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Name).IsEqualTo("");
@@ -420,7 +419,7 @@ public async Task DeserializeObject_WithSpecialCharactersInString_MapsCorrectly(
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Name).IsEqualTo("my-lib@1.0");
@@ -436,7 +435,7 @@ public async Task DeserializeObject_WithNegativeNumbers_MapsCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Timeout).IsEqualTo(-1000);
@@ -452,7 +451,7 @@ public async Task DeserializeObject_WithHexNumbers_MapsCorrectly()
""";
// Act
- var result = await KdlSerializer.Deserialize(kdl);
+ var result = KdlSerializer.Deserialize(kdl);
// Assert
await Assert.That(result.Timeout).IsEqualTo(255);
@@ -473,7 +472,7 @@ public async Task DeserializeObject_WithWrongNodeName_ThrowsException()
// Act & Assert
await Assert
- .That(async () => await KdlSerializer.Deserialize(kdl))
+ .That(async () => KdlSerializer.Deserialize(kdl))
.Throws();
}
@@ -489,7 +488,7 @@ public async Task DeserializeToScalar_WithMultipleRootNodes_ThrowsException()
// Act & Assert
await Assert
- .That(async () => await KdlSerializer.Deserialize(kdl))
+ .That(async () => KdlSerializer.Deserialize(kdl))
.Throws();
}
@@ -505,7 +504,7 @@ public async Task DeserializeToDictionary_ThrowsException()
// Act & Assert
await Assert
- .That(async () => await KdlSerializer.Deserialize>(kdl))
+ .That(async () => KdlSerializer.Deserialize>(kdl))
.Throws()
.WithMessageContaining("not supported");
}
@@ -522,7 +521,7 @@ public async Task DeserializeToEnumerable_WithDifferentNodeNames_ThrowsException
// Act & Assert
await Assert
- .That(async () => await KdlSerializer.Deserialize>(kdl))
+ .That(async () => KdlSerializer.Deserialize>(kdl))
.Throws();
}
@@ -536,7 +535,7 @@ public async Task DeserializeObject_WithInvalidNumericValue_ThrowsException()
// Act & Assert
await Assert
- .That(async () => await KdlSerializer.Deserialize(kdl))
+ .That(async () => KdlSerializer.Deserialize(kdl))
.Throws();
}
@@ -550,7 +549,7 @@ public async Task DeserializeObject_WithMissingRequiredArgument_ThrowsException(
// Act & Assert
await Assert
- .That(async () => await KdlSerializer.Deserialize(kdl))
+ .That(async () => KdlSerializer.Deserialize(kdl))
.Throws();
}
@@ -564,7 +563,7 @@ public async Task DeserializeObject_WhenTargetIsSimpleValue_ThrowsException()
// Act & Assert
await Assert
- .That(async () => await KdlSerializer.Deserialize(kdl))
+ .That(async () => KdlSerializer.Deserialize(kdl))
.Throws()
.WithMessageContaining("Cannot deserialize type");
}
diff --git a/src/Kuddle.Tests/Validation/ReservedTypeValidatorTests.cs b/src/Kuddle.Tests/Validation/ReservedTypeValidatorTests.cs
index 22deb82..6f4c310 100644
--- a/src/Kuddle.Tests/Validation/ReservedTypeValidatorTests.cs
+++ b/src/Kuddle.Tests/Validation/ReservedTypeValidatorTests.cs
@@ -1,5 +1,6 @@
using Kuddle.AST;
using Kuddle.Exceptions;
+using Kuddle.Serialization;
using Kuddle.Validation;
namespace Kuddle.Tests.Validation;
@@ -15,7 +16,7 @@ public async Task Given_ValidU8_When_Validated_Then_NoExceptionThrown()
var doc = Parse("node 255");
// Act & Assert
- await Assert.That(() => KuddleReservedTypeValidator.Validate(doc)).ThrowsNothing();
+ await Assert.That(() => KdlReservedTypeValidator.Validate(doc)).ThrowsNothing();
}
[Test]
@@ -26,7 +27,7 @@ public async Task Given_OverflowingU8_When_Validated_Then_ThrowsValidationExcept
// Act
var exception = await Assert
- .That(() => KuddleReservedTypeValidator.Validate(doc))
+ .That(() => KdlReservedTypeValidator.Validate(doc))
.Throws();
// Assert
@@ -42,7 +43,7 @@ public async Task Given_StringForIntType_When_Validated_Then_ThrowsMismatchError
// Act
var exception = Assert.Throws(() =>
- KuddleReservedTypeValidator.Validate(doc)
+ KdlReservedTypeValidator.Validate(doc)
);
// Assert
@@ -57,7 +58,7 @@ public async Task Given_InvalidUuid_When_Validated_Then_ThrowsError()
// Act
var exception = Assert.Throws(() =>
- KuddleReservedTypeValidator.Validate(doc)
+ KdlReservedTypeValidator.Validate(doc)
);
// Assert
@@ -71,14 +72,14 @@ public async Task Given_NodeNameWithReservedType_When_Validated_Then_IgnoresIt()
var doc = Parse("node (u8)123");
// Act & Assert
- await Assert.That(() => KuddleReservedTypeValidator.Validate(doc)).ThrowsNothing();
+ await Assert.That(() => KdlReservedTypeValidator.Validate(doc)).ThrowsNothing();
}
private static KdlDocument Parse(string input)
{
- return KuddleReader.Parse(
+ return KdlReader.Read(
input,
- KuddleReaderOptions.Default with
+ KdlReaderOptions.Default with
{
ValidateReservedTypes = false,
}
diff --git a/src/Kuddle/AST/KdlDocument.cs b/src/Kuddle/AST/KdlDocument.cs
index 8a11baf..f87ac17 100644
--- a/src/Kuddle/AST/KdlDocument.cs
+++ b/src/Kuddle/AST/KdlDocument.cs
@@ -7,13 +7,13 @@ public sealed record KdlDocument : KdlObject
{
public List Nodes { get; init; } = [];
- public string ToString(KuddleWriterOptions? options = null)
+ public string ToString(KdlWriterOptions? options = null)
{
- return KuddleWriter.Write(this, options);
+ return KdlWriter.Write(this, options);
}
public override string ToString()
{
- return KuddleWriter.Write(this);
+ return KdlWriter.Write(this);
}
}
diff --git a/src/Kuddle/Extensions/KuddleNodeExtensions.cs b/src/Kuddle/Extensions/KdlNodeExtensions.cs
similarity index 97%
rename from src/Kuddle/Extensions/KuddleNodeExtensions.cs
rename to src/Kuddle/Extensions/KdlNodeExtensions.cs
index 7c90031..18a3bb3 100644
--- a/src/Kuddle/Extensions/KuddleNodeExtensions.cs
+++ b/src/Kuddle/Extensions/KdlNodeExtensions.cs
@@ -2,7 +2,7 @@
namespace Kuddle.Extensions;
-public static class KuddleNodeExtensions
+public static class KdlNodeExtensions
{
extension(KdlNode node)
{
diff --git a/src/Kuddle/Extensions/KuddleValueExtensions.cs b/src/Kuddle/Extensions/KdlValueExtensions.cs
similarity index 98%
rename from src/Kuddle/Extensions/KuddleValueExtensions.cs
rename to src/Kuddle/Extensions/KdlValueExtensions.cs
index 662051c..73da3a7 100644
--- a/src/Kuddle/Extensions/KuddleValueExtensions.cs
+++ b/src/Kuddle/Extensions/KdlValueExtensions.cs
@@ -4,7 +4,7 @@
namespace Kuddle.Extensions;
-public static class KuddleValueExtensions
+public static class KdlValueExtensions
{
extension(KdlValue value)
{
diff --git a/src/Kuddle/Extensions/SpanExtensions.cs b/src/Kuddle/Extensions/SpanExtensions.cs
index d1791b7..80b6f2a 100644
--- a/src/Kuddle/Extensions/SpanExtensions.cs
+++ b/src/Kuddle/Extensions/SpanExtensions.cs
@@ -6,15 +6,15 @@ public static class SpanExtensions
{
extension(ReadOnlySpan span)
{
- public bool Any(Func predicate)
- {
- foreach (var item in span)
- {
- if (predicate(item))
- return true;
- }
- return false;
- }
+ // public bool Any(Func predicate)
+ // {
+ // foreach (var item in span)
+ // {
+ // if (predicate(item))
+ // return true;
+ // }
+ // return false;
+ // }
public int MaxConsecutive(T target)
{
diff --git a/src/Kuddle/Parser/KuddleGrammar.cs b/src/Kuddle/Parser/KdlGrammar.cs
similarity index 99%
rename from src/Kuddle/Parser/KuddleGrammar.cs
rename to src/Kuddle/Parser/KdlGrammar.cs
index 27fd64f..7d7d951 100644
--- a/src/Kuddle/Parser/KuddleGrammar.cs
+++ b/src/Kuddle/Parser/KdlGrammar.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
using System.Text.RegularExpressions;
using Kuddle.AST;
using Kuddle.Extensions;
@@ -11,7 +10,7 @@
namespace Kuddle.Parser;
-public static class KuddleGrammar
+public static class KdlGrammar
{
internal static readonly Parser Document;
@@ -68,7 +67,7 @@ public static class KuddleGrammar
IReadOnlyList
>();
- static KuddleGrammar()
+ static KdlGrammar()
{
var nodeSpace = Deferred();
diff --git a/src/Kuddle/Serialization/Attributes/KdlPropertyAttribute.cs b/src/Kuddle/Serialization/Attributes/KdlPropertyAttribute.cs
index 202d148..f1b892b 100644
--- a/src/Kuddle/Serialization/Attributes/KdlPropertyAttribute.cs
+++ b/src/Kuddle/Serialization/Attributes/KdlPropertyAttribute.cs
@@ -7,3 +7,13 @@ public sealed class KdlPropertyAttribute(string? key = null) : Attribute
{
public string? Key { get; } = key;
}
+
+[AttributeUsage(
+ AttributeTargets.Class | AttributeTargets.Property,
+ AllowMultiple = false,
+ Inherited = false
+)]
+public sealed class KdlTypeAttribute(string name) : Attribute
+{
+ public string Name { get; set; } = name;
+}
diff --git a/src/Kuddle/Serialization/KuddleReader.cs b/src/Kuddle/Serialization/KdlReader.cs
similarity index 71%
rename from src/Kuddle/Serialization/KuddleReader.cs
rename to src/Kuddle/Serialization/KdlReader.cs
index fdbb10e..a465655 100644
--- a/src/Kuddle/Serialization/KuddleReader.cs
+++ b/src/Kuddle/Serialization/KdlReader.cs
@@ -1,17 +1,15 @@
using System;
-using System.IO;
-using System.Threading.Tasks;
using Kuddle.AST;
using Kuddle.Exceptions;
using Kuddle.Parser;
using Kuddle.Validation;
using Parlot.Fluent;
-namespace Kuddle;
+namespace Kuddle.Serialization;
-public static class KuddleReader
+public static class KdlReader
{
- private static readonly Parser _parser = KuddleGrammar.Document.Compile();
+ private static readonly Parser _parser = KdlGrammar.Document.Compile();
///
/// Parses a KDL string into a KdlDocument AST.
@@ -20,10 +18,10 @@ public static class KuddleReader
///
///
///
- public static KdlDocument Parse(string text, KuddleReaderOptions? options = null)
+ public static KdlDocument Read(string text, KdlReaderOptions? options = null)
{
ArgumentNullException.ThrowIfNull(text);
- options ??= KuddleReaderOptions.Default;
+ options ??= KdlReaderOptions.Default;
if (!_parser.TryParse(text, out var doc, out var error))
{
@@ -41,7 +39,7 @@ public static KdlDocument Parse(string text, KuddleReaderOptions? options = null
if (options.ValidateReservedTypes)
{
- KuddleReservedTypeValidator.Validate(doc);
+ KdlReservedTypeValidator.Validate(doc);
}
return doc;
@@ -59,12 +57,4 @@ public static KdlDocument Parse(string text, KuddleReaderOptions? options = null
// var text = await reader.ReadToEndAsync();
// return await ReadASync(text, options);
// }
-
- public static async Task ReadAsync(
- string text,
- KuddleReaderOptions? options = null
- )
- {
- return Parse(text, options);
- }
}
diff --git a/src/Kuddle/Serialization/KdlReaderOptions.cs b/src/Kuddle/Serialization/KdlReaderOptions.cs
new file mode 100644
index 0000000..b0841d5
--- /dev/null
+++ b/src/Kuddle/Serialization/KdlReaderOptions.cs
@@ -0,0 +1,7 @@
+namespace Kuddle.Serialization;
+
+public record KdlReaderOptions
+{
+ public static KdlReaderOptions Default => new() { ValidateReservedTypes = true };
+ public bool ValidateReservedTypes { get; init; } = true;
+}
diff --git a/src/Kuddle/Serialization/Attributes/KdlSerializer.cs b/src/Kuddle/Serialization/KdlSerializer.cs
similarity index 81%
rename from src/Kuddle/Serialization/Attributes/KdlSerializer.cs
rename to src/Kuddle/Serialization/KdlSerializer.cs
index 02c37d1..9720fd4 100644
--- a/src/Kuddle/Serialization/Attributes/KdlSerializer.cs
+++ b/src/Kuddle/Serialization/KdlSerializer.cs
@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using System.Threading.Tasks;
using Kuddle.AST;
using Kuddle.Extensions;
@@ -11,70 +10,77 @@ namespace Kuddle.Serialization;
public static class KdlSerializer
{
- public static async Task Deserialize(string text, KdlSerializerOptions? options = null)
+ public static T Deserialize(string text, KdlSerializerOptions? options = null)
{
+ var document = KdlReader.Read(text);
var t = typeof(T);
- KdlDocument document;
- if (t.IsDictionary)
- {
- throw new KuddleSerializationException(
- "Deserialization to dictionaries is not supported"
- );
- }
- else if (t.IsIEnumerable)
+
+ if (t.IsNodeDefinition)
{
- var genericTypeDef = t.GetGenericTypeDefinition();
- var args = t.GetGenericArguments();
- var listType = typeof(List<>).MakeGenericType(args);
- var listInstance = (IList)Activator.CreateInstance(listType)!;
+ // T does not have any kdl args or properties, so should be a single root node doc
- document = await KuddleReader.ReadAsync(text);
+ if (t.IsDictionary)
+ {
+ throw new KuddleSerializationException(
+ "Deserialization to dictionaries is not supported"
+ );
+ }
+ else if (t.IsIEnumerable)
+ {
+ var genericTypeDef = t.GetGenericTypeDefinition();
+ var args = t.GetGenericArguments();
+ var listType = typeof(List<>).MakeGenericType(args);
+ var listInstance = (IList)Activator.CreateInstance(listType)!;
- var firstName = document.Nodes.FirstOrDefault()?.Name;
+ var firstName = document.Nodes.FirstOrDefault()?.Name;
- foreach (var node in document.Nodes)
- {
- if (node.Name != firstName)
+ foreach (var node in document.Nodes)
{
- throw new KuddleSerializationException(
- "All root nodes must have the same name."
- );
+ if (node.Name != firstName)
+ {
+ throw new KuddleSerializationException(
+ "All root nodes must have the same name."
+ );
+ }
}
+
+ return (T)listInstance;
}
- return (T)listInstance;
- }
+ if (!t.IsComplexType)
+ {
+ throw new KuddleSerializationException(
+ $"Cannot deserialize type '{t.FullName}' as a complex object. "
+ + "Ensure the type is a concrete class with a public parameterless constructor."
+ );
+ }
+ var instance = Activator.CreateInstance();
- if (!t.IsComplexType)
- {
- throw new KuddleSerializationException(
- $"Cannot deserialize type '{t.FullName}' as a complex object. "
- + "Ensure the type is a concrete class with a public parameterless constructor."
- );
- }
- var instance = Activator.CreateInstance();
- document = await KuddleReader.ReadAsync(text);
+ if (document.Nodes.Count != 1)
+ {
+ throw new KuddleSerializationException(
+ "Kdl document must have a single root node to map to an instance T"
+ );
+ }
+ if (
+ !typeof(T).Name.Equals(
+ document.Nodes[0].Name.Value,
+ StringComparison.InvariantCultureIgnoreCase
+ )
+ )
+ {
+ throw new KuddleSerializationException(
+ $"Node name '{document.Nodes[0].Name}' does not match target type name '{typeof(T).Name}'."
+ );
+ }
+ MapToInstance(document.Nodes[0], instance);
- if (document.Nodes.Count != 1)
- {
- throw new KuddleSerializationException(
- "Kdl document must have a single root node to map to an instance T"
- );
+ return instance;
}
- if (
- !typeof(T).Name.Equals(
- document.Nodes[0].Name.Value,
- StringComparison.InvariantCultureIgnoreCase
- )
- )
+ else
{
- throw new KuddleSerializationException(
- $"Node name '{document.Nodes[0].Name}' does not match target type name '{typeof(T).Name}'."
- );
+ throw new NotImplementedException();
}
- MapToInstance(document.Nodes[0], instance);
-
- return instance;
}
private static void MapToInstance(KdlNode node, T instance)
@@ -297,7 +303,7 @@ public static string Serialize(T original)
doc.Nodes.Add(node);
}
- return KuddleWriter.Write(doc);
+ return KdlWriter.Write(doc);
}
private static KdlNode SerializeNode(object? instance, string? nodeName = null)
@@ -396,7 +402,13 @@ internal static class TypeExtensions
{
extension(Type type)
{
- public bool IsComplexType =>
+ internal bool IsNodeDefinition =>
+ !type.GetProperties()
+ .Any(p =>
+ p.GetCustomAttribute() != null
+ || p.GetCustomAttribute() != null
+ );
+ internal bool IsComplexType =>
!type.IsValueType
&& !type.IsPrimitive
&& type != typeof(string)
@@ -404,7 +416,7 @@ internal static class TypeExtensions
&& !type.IsInterface
&& !type.IsAbstract;
- public bool IsDictionary =>
+ internal bool IsDictionary =>
type.IsGenericType
&& type.GetInterfaces()
.Any(i =>
@@ -415,7 +427,7 @@ internal static class TypeExtensions
)
);
- public bool IsIEnumerable =>
+ internal bool IsIEnumerable =>
type != typeof(string)
&& !type.IsDictionary
&& type.IsAssignableTo(typeof(IEnumerable));
diff --git a/src/Kuddle/Serialization/KuddleWriter.cs b/src/Kuddle/Serialization/KdlWriter.cs
similarity index 95%
rename from src/Kuddle/Serialization/KuddleWriter.cs
rename to src/Kuddle/Serialization/KdlWriter.cs
index 7ac405e..b757e52 100644
--- a/src/Kuddle/Serialization/KuddleWriter.cs
+++ b/src/Kuddle/Serialization/KdlWriter.cs
@@ -5,20 +5,20 @@
namespace Kuddle.Serialization;
-public class KuddleWriter
+public class KdlWriter
{
- private readonly KuddleWriterOptions _options;
+ private readonly KdlWriterOptions _options;
private readonly StringBuilder _sb = new();
private int _depth = 0;
- public KuddleWriter(KuddleWriterOptions? options = null)
+ public KdlWriter(KdlWriterOptions? options = null)
{
- _options ??= options ?? KuddleWriterOptions.Default;
+ _options ??= options ?? KdlWriterOptions.Default;
}
- public static string Write(KdlDocument document, KuddleWriterOptions? options = null)
+ public static string Write(KdlDocument document, KdlWriterOptions? options = null)
{
- var writer = new KuddleWriter(options);
+ var writer = new KdlWriter(options);
writer.WriteDocument(document);
return writer._sb.ToString();
}
diff --git a/src/Kuddle/Serialization/KuddleWriterOptions.cs b/src/Kuddle/Serialization/KdlWriterOptions.cs
similarity index 76%
rename from src/Kuddle/Serialization/KuddleWriterOptions.cs
rename to src/Kuddle/Serialization/KdlWriterOptions.cs
index 7a6112a..5a84e3b 100644
--- a/src/Kuddle/Serialization/KuddleWriterOptions.cs
+++ b/src/Kuddle/Serialization/KdlWriterOptions.cs
@@ -1,8 +1,8 @@
namespace Kuddle.Serialization;
-public record KuddleWriterOptions
+public record KdlWriterOptions
{
- public static KuddleWriterOptions Default => new();
+ public static KdlWriterOptions Default => new();
public string IndentChar { get; init; } = " ";
public string NewLine { get; init; } = "\n";
diff --git a/src/Kuddle/Serialization/KuddleReaderOptions.cs b/src/Kuddle/Serialization/KuddleReaderOptions.cs
deleted file mode 100644
index 95ab1bc..0000000
--- a/src/Kuddle/Serialization/KuddleReaderOptions.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Kuddle;
-
-public record KuddleReaderOptions
-{
- public static KuddleReaderOptions Default => new() { ValidateReservedTypes = true };
- public bool ValidateReservedTypes { get; init; } = true;
-}
diff --git a/src/Kuddle/Validation/KuddleReservedTypeValidator.cs b/src/Kuddle/Validation/KuddleReservedTypeValidator.cs
index c8125e9..ff05cb7 100644
--- a/src/Kuddle/Validation/KuddleReservedTypeValidator.cs
+++ b/src/Kuddle/Validation/KuddleReservedTypeValidator.cs
@@ -2,14 +2,13 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
-using System.Text.Json;
using System.Text.RegularExpressions;
using Kuddle.AST;
using Kuddle.Exceptions;
namespace Kuddle.Validation;
-public static class KuddleReservedTypeValidator
+public static class KdlReservedTypeValidator
{
private static readonly HashSet ReservedTypes =
[
@@ -174,7 +173,10 @@ private static void ValidateValue(KdlValue val, List erro
catch (Exception)
{
errors.Add(
- new KuddleValidationError($"Value '{val}' is not a valid '{val.TypeAnnotation}'.", val)
+ new KuddleValidationError(
+ $"Value '{val}' is not a valid '{val.TypeAnnotation}'.",
+ val
+ )
);
}
}
From d18ceba48839410c3c61087884b4b1e1b7cf3f88 Mon Sep 17 00:00:00 2001
From: jamesshenry <79054685+jamesshenry@users.noreply.github.com>
Date: Wed, 17 Dec 2025 01:44:10 +0000
Subject: [PATCH 03/17] wip
---
.../Serialization/DocumentToObjectTests.cs | 123 +++++++++-
.../Serialization/NodeToObjectTests.cs | 24 +-
.../Serialization/ObjectMapperTests.cs | 26 +-
src/Kuddle/Extensions/TypeExtensions.cs | 79 +++++++
src/Kuddle/Serialization/KdlSerializer.cs | 222 ++++++++----------
5 files changed, 319 insertions(+), 155 deletions(-)
create mode 100644 src/Kuddle/Extensions/TypeExtensions.cs
diff --git a/src/Kuddle.Tests/Serialization/DocumentToObjectTests.cs b/src/Kuddle.Tests/Serialization/DocumentToObjectTests.cs
index 3c62d07..b2555d2 100644
--- a/src/Kuddle.Tests/Serialization/DocumentToObjectTests.cs
+++ b/src/Kuddle.Tests/Serialization/DocumentToObjectTests.cs
@@ -1,3 +1,124 @@
+using Kuddle.Exceptions;
+using Kuddle.Serialization;
+
namespace Kuddle.Tests.Serialization;
-public class DocumentToObjectTests { }
+public class DocumentToObjectTests
+{
+ class AppConfig
+ {
+ [KdlNode("plugin")]
+ public List Plugins { get; set; } = new();
+
+ [KdlNode("logging")]
+ public LogSettings? Logging { get; set; }
+
+ [KdlNode("experiments")]
+ public Experiments? Experiments { get; set; }
+ }
+
+ class Plugin
+ {
+ [KdlArgument(0)]
+ public string Name { get; set; } = "";
+ }
+
+ class LogSettings
+ {
+ [KdlNode("level")]
+ public string LogLevel { get; set; } = "info";
+ }
+
+ class Experiments
+ {
+ [KdlProperty("enabled")]
+ public bool Enabled { get; set; }
+ }
+
+ // --- Tests ---
+
+ [Test]
+ public async Task Deserialize_Document_MapsListsAndSingleObjects()
+ {
+ var kdl = """
+ plugin "Analytics"
+ plugin "Authentication"
+
+ logging {
+ level "debug"
+ }
+ """;
+
+ var result = KdlSerializer.Deserialize(kdl);
+
+ // Assert
+ await Assert.That(result.Plugins).Count().IsEqualTo(2);
+ await Assert.That(result.Plugins[0].Name).IsEqualTo("Analytics");
+ await Assert.That(result.Plugins[1].Name).IsEqualTo("Authentication");
+
+ await Assert.That(result.Logging).IsNotNull();
+
+ await Assert.That(result.Logging!.LogLevel).IsEqualTo("debug");
+ }
+
+ [Test]
+ public async Task Deserialize_EmptyDocument_ReturnsInitializedObject()
+ {
+ var kdl = "";
+
+ var result = KdlSerializer.Deserialize(kdl);
+
+ await Assert.That(result).IsNotNull();
+ await Assert.That(result.Plugins).IsEmpty();
+ await Assert.That(result.Logging).IsNull();
+ }
+
+ [Test]
+ public async Task Deserialize_PartialMatch_IgnoresUnmappedNodes()
+ {
+ var kdl = """
+ plugin "Core"
+
+ // This node is not in AppConfig
+ garbage_data {
+ ignore me
+ }
+ """;
+
+ var result = KdlSerializer.Deserialize(kdl);
+
+ await Assert.That(result.Plugins).Count().IsEqualTo(1);
+ await Assert.That(result.Plugins[0].Name).IsEqualTo("Core");
+
+ await Assert.That(result.Logging).IsNull();
+ }
+
+ [Test]
+ public async Task Deserialize_NestedStructure_WithProperties()
+ {
+ var kdl =
+ @"
+ experiments enabled=#true
+ ";
+
+ var result = KdlSerializer.Deserialize(kdl);
+
+ await Assert.That(result.Experiments).IsNotNull();
+ await Assert.That(result.Experiments!.Enabled).IsTrue();
+ }
+
+ [Test]
+ public async Task Deserialize_AmbiguousSingleNode_ThrowsOrHandles()
+ {
+ var kdl =
+ @"
+ logging { level ""info"" }
+ logging { level ""error"" }
+ ";
+
+ await Assert.ThrowsAsync(async () =>
+ {
+ KdlSerializer.Deserialize(kdl);
+ });
+ }
+}
diff --git a/src/Kuddle.Tests/Serialization/NodeToObjectTests.cs b/src/Kuddle.Tests/Serialization/NodeToObjectTests.cs
index ab20620..62933b1 100644
--- a/src/Kuddle.Tests/Serialization/NodeToObjectTests.cs
+++ b/src/Kuddle.Tests/Serialization/NodeToObjectTests.cs
@@ -5,9 +5,8 @@ namespace Kuddle.Tests.Serialization;
public class NodeToObjectTests
{
- // 1. Explicit Naming via [KdlType]
[KdlType("database")]
- public class DbConfig
+ class DbConfig
{
[KdlArgument(0)]
public string Name { get; set; } = "";
@@ -16,10 +15,9 @@ public class DbConfig
public int Port { get; set; }
[KdlProperty("enabled")]
- public bool Enabled { get; set; } = true; // Default value
+ public bool Enabled { get; set; } = true;
}
- // 2. Implicit Naming (Class Name fallback)
public class Server
{
[KdlArgument(0)]
@@ -42,10 +40,6 @@ public async Task Deserialize_ExplicitName_MapsArgumentsAndProperties()
[Test]
public async Task Deserialize_ImplicitName_UsesClassName()
{
- // Class is "Server", so it expects node "server" (case-insensitive usually, or exact match)
- // Assuming your logic defaults to exact or lowercase.
- // Let's assume case-insensitive or exact match "Server".
- // If your logic uses .ToLowerInvariant(), input should be "server".
var kdl = "Server \"localhost\"";
var result = KdlSerializer.Deserialize(kdl);
@@ -57,7 +51,6 @@ public async Task Deserialize_ImplicitName_UsesClassName()
[Test]
public async Task Deserialize_MissingProperty_UsesDefault()
{
- // "enabled" is missing, should stay true
var kdl = "database \"local\" port=3000";
var result = KdlSerializer.Deserialize(kdl);
@@ -69,10 +62,8 @@ public async Task Deserialize_MissingProperty_UsesDefault()
[Test]
public async Task Deserialize_MismatchNodeName_Throws()
{
- // Expecting "database", got "table"
var kdl = "table \"production\" port=5432";
- // This asserts that the Serializer enforces the [KdlType] name
await Assert.ThrowsAsync(async () =>
{
KdlSerializer.Deserialize(kdl);
@@ -82,8 +73,6 @@ await Assert.ThrowsAsync(async () =>
[Test]
public async Task Deserialize_MultipleRootNodes_Throws()
{
- // Node-to-Object strategy implies the Type represents A SINGLE node.
- // A document with two nodes is ambiguous/invalid for this mapping.
var kdl =
@"
database ""primary"" port=5432
@@ -99,7 +88,6 @@ await Assert.ThrowsAsync(async () =>
[Test]
public async Task Deserialize_TypeMismatch_Throws()
{
- // Port expects int, got string identifier
var kdl = "database \"db\" port=\"not-a-number\"";
await Assert.ThrowsAsync(async () =>
@@ -111,16 +99,8 @@ await Assert.ThrowsAsync(async () =>
[Test]
public async Task Deserialize_DocumentRoot_RejectsProperties()
{
- // If the serializer logic correctly identifies this as a Node
- // (because it has [KdlArgument]), it handles it.
- // But if we tried to put a property at the top level in the FILE that doesn't
- // belong to a node (which is syntactically impossible in KDL anyway), the Parser would catch it.
-
- // However, we can test that extraneous properties on the node are simply ignored
- // (unless you implemented strict mode).
var kdl = "database \"db\" port=5432 unknown_prop=123";
- // Should succeed and ignore unknown_prop
var result = KdlSerializer.Deserialize(kdl);
await Assert.That(result.Port).IsEqualTo(5432);
}
diff --git a/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs b/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs
index 915455d..2ad93b5 100644
--- a/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs
+++ b/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs
@@ -553,20 +553,20 @@ await Assert
.Throws();
}
- [Test]
- public async Task DeserializeObject_WhenTargetIsSimpleValue_ThrowsException()
- {
- // Arrange
- var kdl = """
- package
- """;
+ // [Test]
+ // public async Task DeserializeObject_WhenTargetIsSimpleValue_ThrowsException()
+ // {
+ // // Arrange
+ // var kdl = """
+ // package
+ // """;
- // Act & Assert
- await Assert
- .That(async () => KdlSerializer.Deserialize(kdl))
- .Throws()
- .WithMessageContaining("Cannot deserialize type");
- }
+ // // Act & Assert
+ // await Assert
+ // .That(async () => KdlSerializer.Deserialize(kdl))
+ // .Throws()
+ // .WithMessageContaining("Cannot deserialize type");
+ // }
#endregion
#region Test Models
diff --git a/src/Kuddle/Extensions/TypeExtensions.cs b/src/Kuddle/Extensions/TypeExtensions.cs
new file mode 100644
index 0000000..66a5ed6
--- /dev/null
+++ b/src/Kuddle/Extensions/TypeExtensions.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Kuddle.Serialization;
+
+internal static class TypeExtensions
+{
+ extension(Type type)
+ {
+ internal bool IsNodeDefinition =>
+ type.GetProperties()
+ .Any(p =>
+ p.GetCustomAttribute() != null
+ || p.GetCustomAttribute() != null
+ );
+ internal bool IsComplexType =>
+ !type.IsValueType
+ && !type.IsPrimitive
+ && type != typeof(string)
+ && type != typeof(object)
+ && !type.IsInterface
+ && !type.IsAbstract;
+
+ internal bool IsDictionary =>
+ type.IsGenericType
+ && type.GetInterfaces()
+ .Any(i =>
+ i.IsGenericType
+ && (
+ i.GetGenericTypeDefinition() == typeof(IDictionary<,>)
+ || i.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)
+ )
+ );
+
+ internal bool IsIEnumerable =>
+ type != typeof(string)
+ && !type.IsDictionary
+ && type.IsAssignableTo(typeof(IEnumerable));
+
+ internal (PropertyInfo, KdlArgumentAttribute)[] GetKdlArgProps() =>
+ type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Select(p => new
+ {
+ Property = p,
+ ArgAttr = p.GetCustomAttribute(),
+ })
+ .Where(x => x.ArgAttr is not null)
+ .OrderBy(x => x.ArgAttr!.Index)
+ .Select(x => (x.Property, x.ArgAttr!))
+ .ToArray();
+
+ internal (PropertyInfo, KdlPropertyAttribute)[] GetKdlPropProps() =>
+ type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Select(p => new
+ {
+ Property = p,
+ PropAttr = p.GetCustomAttribute(),
+ })
+ .Where(x => x.PropAttr is not null)
+ .Select(x => (x.Property, x.PropAttr!))
+ .ToArray();
+
+ internal (PropertyInfo, KdlChildrenAttribute)[] GetKdlChildProps() =>
+ type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Select(p => new
+ {
+ Property = p,
+ ChildAttr = p.GetCustomAttribute(),
+ })
+ .Where(x => x.ChildAttr is not null)
+ .Select(x => (x.Property, x.ChildAttr!))
+ .ToArray();
+
+ public bool IsNullable() => Nullable.GetUnderlyingType(type) != null;
+ }
+}
diff --git a/src/Kuddle/Serialization/KdlSerializer.cs b/src/Kuddle/Serialization/KdlSerializer.cs
index 9720fd4..bf7b2da 100644
--- a/src/Kuddle/Serialization/KdlSerializer.cs
+++ b/src/Kuddle/Serialization/KdlSerializer.cs
@@ -10,77 +10,134 @@ namespace Kuddle.Serialization;
public static class KdlSerializer
{
- public static T Deserialize(string text, KdlSerializerOptions? options = null)
+ public static IEnumerable DeserializeMany(
+ string text,
+ KdlSerializerOptions? options = null
+ )
+ where T : new()
{
- var document = KdlReader.Read(text);
- var t = typeof(T);
+ var doc = KdlReader.Read(text);
+ var list = new List(doc.Nodes.Count);
- if (t.IsNodeDefinition)
- {
- // T does not have any kdl args or properties, so should be a single root node doc
+ var expectedName = GetExpectedNodeName(typeof(T));
- if (t.IsDictionary)
- {
+ foreach (var node in doc.Nodes)
+ {
+ if (expectedName != null && node.Name.Value != expectedName)
throw new KuddleSerializationException(
- "Deserialization to dictionaries is not supported"
+ $"Expected node '{expectedName}', found '{node.Name.Value}'"
);
- }
- else if (t.IsIEnumerable)
- {
- var genericTypeDef = t.GetGenericTypeDefinition();
- var args = t.GetGenericArguments();
- var listType = typeof(List<>).MakeGenericType(args);
- var listInstance = (IList)Activator.CreateInstance(listType)!;
- var firstName = document.Nodes.FirstOrDefault()?.Name;
+ var item = new T();
+ MapToInstance(node, item);
+ list.Add(item);
+ }
- foreach (var node in document.Nodes)
- {
- if (node.Name != firstName)
- {
- throw new KuddleSerializationException(
- "All root nodes must have the same name."
- );
- }
- }
+ return list;
+ }
- return (T)listInstance;
- }
+ private static string? GetExpectedNodeName(Type type)
+ {
+ var kdlTypeAttr = type.GetCustomAttribute();
- if (!t.IsComplexType)
- {
- throw new KuddleSerializationException(
- $"Cannot deserialize type '{t.FullName}' as a complex object. "
- + "Ensure the type is a concrete class with a public parameterless constructor."
- );
- }
- var instance = Activator.CreateInstance();
+ if (kdlTypeAttr is null)
+ return null;
+
+ return kdlTypeAttr.Name;
+ }
+
+ public static T Deserialize(string text, KdlSerializerOptions? options = null)
+ where T : new()
+ {
+ var document = KdlReader.Read(text);
+ var type = typeof(T);
+ if (type.IsNodeDefinition)
+ {
if (document.Nodes.Count != 1)
{
throw new KuddleSerializationException(
"Kdl document must have a single root node to map to an instance T"
);
}
- if (
- !typeof(T).Name.Equals(
- document.Nodes[0].Name.Value,
- StringComparison.InvariantCultureIgnoreCase
- )
- )
+
+ var rootNode = document.Nodes[0];
+ var expectedName = GetExpectedNodeName(type);
+
+ if (expectedName != null && rootNode.Name.Value != expectedName)
{
throw new KuddleSerializationException(
- $"Node name '{document.Nodes[0].Name}' does not match target type name '{typeof(T).Name}'."
+ $"Node name '{rootNode.Name.Value}' does not match target type name '{typeof(T).Name}'."
);
}
- MapToInstance(document.Nodes[0], instance);
-
+ var instance = new T();
+ MapToInstance(rootNode, instance);
return instance;
}
else
{
- throw new NotImplementedException();
+ var instance = new T();
+ MapNodesToProperties(document.Nodes, instance);
+ return instance;
+ }
+ }
+
+ private static void MapNodesToProperties(List nodes, T instance)
+ {
+ var props = instance!
+ .GetType()
+ .GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(p => p.CanWrite && p.GetCustomAttribute() == null);
+
+ foreach (var prop in props)
+ {
+ var nodeName =
+ prop.GetCustomAttribute()?.Name
+ ?? prop.GetCustomAttribute()?.ChildNodeName
+ ?? null;
+
+ if (nodeName == null)
+ continue;
+
+ var matchingNodes = nodes
+ .Where(n => n.Name.Value.Equals(nodeName, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ if (matchingNodes.Count == 0)
+ continue;
+
+ if (prop.PropertyType.IsIEnumerable)
+ {
+ SetCollectionPropValue(prop, instance, matchingNodes);
+ }
+ else
+ {
+ if (matchingNodes.Count > 1)
+ throw new KuddleSerializationException(
+ $"Property '{prop.Name}' expects a single node named '{nodeName}', but found {matchingNodes.Count}."
+ );
+
+ SetSingleComplexPropValue(prop, instance, matchingNodes[0]);
+ }
+ }
+ }
+
+ private static void SetSingleComplexPropValue(PropertyInfo prop, object instance, KdlNode node)
+ {
+ var type = prop.PropertyType;
+ if (!type.IsComplexType)
+ {
+ throw new KuddleSerializationException(
+ $"Property '{prop.Name}' matches a Node, so it must be a complex class."
+ );
}
+
+ var childInstance =
+ Activator.CreateInstance(type) ?? throw new Exception($"Could not create {type}");
+
+ MapToInstance(node, childInstance);
+
+ prop.SetValue(instance, childInstance);
}
private static void MapToInstance(KdlNode node, T instance)
@@ -192,7 +249,6 @@ private static object MapCollection(Type targetType, Type elementType, IList lis
return list;
}
- // IEnumerable, IReadOnlyList, etc.
return list;
}
@@ -397,75 +453,3 @@ private static IEnumerable SerializeCollection(IEnumerable? original)
}
public record KdlSerializerOptions { }
-
-internal static class TypeExtensions
-{
- extension(Type type)
- {
- internal bool IsNodeDefinition =>
- !type.GetProperties()
- .Any(p =>
- p.GetCustomAttribute() != null
- || p.GetCustomAttribute() != null
- );
- internal bool IsComplexType =>
- !type.IsValueType
- && !type.IsPrimitive
- && type != typeof(string)
- && type != typeof(object)
- && !type.IsInterface
- && !type.IsAbstract;
-
- internal bool IsDictionary =>
- type.IsGenericType
- && type.GetInterfaces()
- .Any(i =>
- i.IsGenericType
- && (
- i.GetGenericTypeDefinition() == typeof(IDictionary<,>)
- || i.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)
- )
- );
-
- internal bool IsIEnumerable =>
- type != typeof(string)
- && !type.IsDictionary
- && type.IsAssignableTo(typeof(IEnumerable));
-
- internal (PropertyInfo, KdlArgumentAttribute)[] GetKdlArgProps() =>
- type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
- .Select(p => new
- {
- Property = p,
- ArgAttr = p.GetCustomAttribute(),
- })
- .Where(x => x.ArgAttr is not null)
- .OrderBy(x => x.ArgAttr!.Index)
- .Select(x => (x.Property, x.ArgAttr!))
- .ToArray();
-
- internal (PropertyInfo, KdlPropertyAttribute)[] GetKdlPropProps() =>
- type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
- .Select(p => new
- {
- Property = p,
- PropAttr = p.GetCustomAttribute(),
- })
- .Where(x => x.PropAttr is not null)
- .Select(x => (x.Property, x.PropAttr!))
- .ToArray();
-
- internal (PropertyInfo, KdlChildrenAttribute)[] GetKdlChildProps() =>
- type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
- .Select(p => new
- {
- Property = p,
- ChildAttr = p.GetCustomAttribute(),
- })
- .Where(x => x.ChildAttr is not null)
- .Select(x => (x.Property, x.ChildAttr!))
- .ToArray();
-
- public bool IsNullable() => Nullable.GetUnderlyingType(type) != null;
- }
-}
From 04f4abeddfa18e46ae7589937cf64d732e08951f Mon Sep 17 00:00:00 2001
From: jamesshenry <79054685+jamesshenry@users.noreply.github.com>
Date: Wed, 17 Dec 2025 08:33:32 +0000
Subject: [PATCH 04/17] combine KdlNodeAttribute and KdlChildrenAttribute
---
.../Serialization/ObjectMapperTests.cs | 9 ++-
src/Kuddle/Extensions/TypeExtensions.cs | 4 +-
.../Attributes/KdlChildrenAttribute.cs | 24 +++---
src/Kuddle/Serialization/KdlSerializer.cs | 76 ++++++++++++++-----
4 files changed, 78 insertions(+), 35 deletions(-)
diff --git a/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs b/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs
index 2ad93b5..f208c54 100644
--- a/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs
+++ b/src/Kuddle.Tests/Serialization/ObjectMapperTests.cs
@@ -1,3 +1,4 @@
+using System.Net.WebSockets;
using Kuddle.AST;
using Kuddle.Serialization;
@@ -501,10 +502,10 @@ public async Task DeserializeToDictionary_ThrowsException()
reference "my-dep1" version="2.1.0"
node "my-dep2" version="3.2.1"
""";
-
+ var result = KdlSerializer.Deserialize>(kdl);
// Act & Assert
await Assert
- .That(async () => KdlSerializer.Deserialize>(kdl))
+ .That(() => KdlSerializer.Deserialize>(kdl))
.Throws()
.WithMessageContaining("not supported");
}
@@ -593,10 +594,10 @@ public class Project
[KdlProperty("version")]
public string Version { get; set; } = "1.0.0";
- [KdlChildren("dependency")]
+ [KdlNode("dependency")]
public List Dependencies { get; set; } = [];
- [KdlChildren("devDependency")]
+ [KdlNode("devDependency")]
public List DevDependencies { get; set; } = [];
}
diff --git a/src/Kuddle/Extensions/TypeExtensions.cs b/src/Kuddle/Extensions/TypeExtensions.cs
index 66a5ed6..5aa58a6 100644
--- a/src/Kuddle/Extensions/TypeExtensions.cs
+++ b/src/Kuddle/Extensions/TypeExtensions.cs
@@ -63,12 +63,12 @@ internal static class TypeExtensions
.Select(x => (x.Property, x.PropAttr!))
.ToArray();
- internal (PropertyInfo, KdlChildrenAttribute)[] GetKdlChildProps() =>
+ internal (PropertyInfo, KdlNodeAttribute)[] GetKdlChildProps() =>
type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(p => new
{
Property = p,
- ChildAttr = p.GetCustomAttribute(),
+ ChildAttr = p.GetCustomAttribute(),
})
.Where(x => x.ChildAttr is not null)
.Select(x => (x.Property, x.ChildAttr!))
diff --git a/src/Kuddle/Serialization/Attributes/KdlChildrenAttribute.cs b/src/Kuddle/Serialization/Attributes/KdlChildrenAttribute.cs
index fc1e300..602e12e 100644
--- a/src/Kuddle/Serialization/Attributes/KdlChildrenAttribute.cs
+++ b/src/Kuddle/Serialization/Attributes/KdlChildrenAttribute.cs
@@ -1,14 +1,14 @@
-using System;
+// using System;
-namespace Kuddle.Serialization;
+// namespace Kuddle.Serialization;
-///
-/// Marks a property to receive child nodes of a specific type from the KDL node's children block.
-///
-/// The KDL node name to match for children (e.g., "dependency").
-[AttributeUsage(AttributeTargets.Property)]
-public class KdlChildrenAttribute(string childNodeName) : Attribute
-{
- /// Gets the child node name to match.
- public string ChildNodeName { get; } = childNodeName;
-}
+// ///
+// /// Marks a property to receive child nodes of a specific type from the KDL node's children block.
+// ///
+// /// The KDL node name to match for children (e.g., "dependency").
+// [AttributeUsage(AttributeTargets.Property)]
+// public class KdlChildrenAttribute(string childNodeName) : Attribute
+// {
+// /// Gets the child node name to match.
+// public string ChildNodeName { get; } = childNodeName;
+// }
diff --git a/src/Kuddle/Serialization/KdlSerializer.cs b/src/Kuddle/Serialization/KdlSerializer.cs
index bf7b2da..9701dbb 100644
--- a/src/Kuddle/Serialization/KdlSerializer.cs
+++ b/src/Kuddle/Serialization/KdlSerializer.cs
@@ -41,9 +41,13 @@ public static IEnumerable DeserializeMany(
var kdlTypeAttr = type.GetCustomAttribute();
if (kdlTypeAttr is null)
- return null;
-
- return kdlTypeAttr.Name;
+ {
+ return type.Name.ToLowerInvariant();
+ }
+ else
+ {
+ return kdlTypeAttr.Name;
+ }
}
public static T Deserialize(string text, KdlSerializerOptions? options = null)
@@ -64,7 +68,10 @@ public static T Deserialize(string text, KdlSerializerOptions? options = null
var rootNode = document.Nodes[0];
var expectedName = GetExpectedNodeName(type);
- if (expectedName != null && rootNode.Name.Value != expectedName)
+ if (
+ expectedName != null
+ && !rootNode.Name.Value.Equals(expectedName, StringComparison.OrdinalIgnoreCase)
+ )
{
throw new KuddleSerializationException(
$"Node name '{rootNode.Name.Value}' does not match target type name '{typeof(T).Name}'."
@@ -91,10 +98,7 @@ private static void MapNodesToProperties(List nodes, T instance)
foreach (var prop in props)
{
- var nodeName =
- prop.GetCustomAttribute()?.Name
- ?? prop.GetCustomAttribute()?.ChildNodeName
- ?? null;
+ var nodeName = prop.GetCustomAttribute()?.Name;
if (nodeName == null)
continue;
@@ -178,17 +182,55 @@ private static void MapToInstance(KdlNode node, T instance)
SetPropValue(prop, instance, kdlProp);
}
- var childAttr = prop.GetCustomAttribute();
+ var childAttr = prop.GetCustomAttribute();
if (childAttr is not null)
{
- var nodeName = childAttr.ChildNodeName ?? prop.Name.ToLowerInvariant();
- var groupedChildNodes = node.Children?.Nodes.GroupBy(n => n.Name) ?? [];
- foreach (var childNode in groupedChildNodes)
+ // // Usually a collection
+ var nodeName = childAttr.Name ?? prop.Name.ToLowerInvariant();
+ // var groupedChildNodes = node.Children?.Nodes.GroupBy(n => n.Name) ?? [];
+ // foreach (var childNode in groupedChildNodes)
+ // {
+ // if (childNode.Key.Value == nodeName)
+ // {
+ // SetCollectionPropValue(prop, instance, childNode);
+ // }
+ // }
+
+ var matchingNodes = node
+ .Children?.Nodes.Where(n =>
+ n.Name.Value.Equals(nodeName, StringComparison.OrdinalIgnoreCase)
+ )
+ .ToList();
+
+ if (matchingNodes == null || matchingNodes.Count == 0)
+ continue;
+
+ if (prop.PropertyType.IsIEnumerable)
+ {
+ SetCollectionPropValue(prop, instance, matchingNodes);
+ }
+ // CASE B: Scalar (string, int, etc.) -> Map Arg(0)
+ else if (!prop.PropertyType.IsComplexType)
{
- if (childNode.Key.Value == nodeName)
- {
- SetCollectionPropValue(prop, instance, childNode);
- }
+ // Expect exactly one node
+ if (matchingNodes.Count > 1)
+ throw new KuddleSerializationException(
+ $"Expected single node '{nodeName}' for scalar property, found {matchingNodes.Count}"
+ );
+
+ // Extract Arg 0
+ var val = matchingNodes[0].Arg(0);
+ SetPropValue(prop, instance, val!);
+ }
+ // CASE C: Complex Object -> Recursion
+ else
+ {
+ if (matchingNodes.Count > 1)
+ throw new KuddleSerializationException(
+ $"Expected single node '{nodeName}' for object property, found {matchingNodes.Count}"
+ );
+
+ SetSingleComplexPropValue(prop, instance, matchingNodes[0]);
}
}
}
@@ -396,7 +438,7 @@ private static KdlNode SerializeNode(object? instance, string? nodeName = null)
var block = new KdlBlock();
foreach (var (prop, childAttr) in childProps)
{
- var childNodeName = childAttr.ChildNodeName ?? prop.Name.ToLowerInvariant();
+ var childNodeName = childAttr.Name ?? prop.Name.ToLowerInvariant();
if (prop.PropertyType.IsDictionary)
throw new NotSupportedException();
From f6536e96e8883f37dc58f42f39c89ec69553d1f2 Mon Sep 17 00:00:00 2001
From: jamesshenry <79054685+jamesshenry@users.noreply.github.com>
Date: Wed, 17 Dec 2025 13:54:56 +0000
Subject: [PATCH 05/17] add docs and kdl samples
---
docs/serialization-attributes.md | 453 ++++++++++++
samples/01-simple-node.kdl | 4 +
samples/02-multiple-arguments.kdl | 7 +
samples/03-nested-children.kdl | 11 +
samples/04-numeric-types.kdl | 31 +
samples/05-type-annotations.kdl | 30 +
samples/06-booleans-and-null.kdl | 19 +
samples/07-string-varieties.kdl | 33 +
samples/08-deeply-nested.kdl | 62 ++
samples/09-mixed-entries.kdl | 25 +
samples/10-document-vs-node.kdl | 24 +
samples/11-slashdash-comments.kdl | 31 +
samples/12-edge-cases.kdl | 53 ++
samples/13-real-world-package.kdl | 47 ++
samples/14-real-world-ci.kdl | 87 +++
spec.md | 1112 +++++++++++++++++++++++++++++
16 files changed, 2029 insertions(+)
create mode 100644 docs/serialization-attributes.md
create mode 100644 samples/01-simple-node.kdl
create mode 100644 samples/02-multiple-arguments.kdl
create mode 100644 samples/03-nested-children.kdl
create mode 100644 samples/04-numeric-types.kdl
create mode 100644 samples/05-type-annotations.kdl
create mode 100644 samples/06-booleans-and-null.kdl
create mode 100644 samples/07-string-varieties.kdl
create mode 100644 samples/08-deeply-nested.kdl
create mode 100644 samples/09-mixed-entries.kdl
create mode 100644 samples/10-document-vs-node.kdl
create mode 100644 samples/11-slashdash-comments.kdl
create mode 100644 samples/12-edge-cases.kdl
create mode 100644 samples/13-real-world-package.kdl
create mode 100644 samples/14-real-world-ci.kdl
create mode 100644 spec.md
diff --git a/docs/serialization-attributes.md b/docs/serialization-attributes.md
new file mode 100644
index 0000000..44fe741
--- /dev/null
+++ b/docs/serialization-attributes.md
@@ -0,0 +1,453 @@
+# KDL Serialization Attribute Guide
+
+This document describes how to use Kuddle's serialization attributes to map between KDL documents and C# types.
+
+## Quick Reference
+
+| Attribute | Target | Purpose |
+|-----------|--------|---------|
+| `[KdlType]` | Class | Override the expected node name |
+| `[KdlArgument(index)]` | Property | Map to a positional argument |
+| `[KdlProperty(key?)]` | Property | Map to a named property |
+| `[KdlNode(name?)]` | Property | Map to child nodes |
+| `[KdlIgnore]` | Property | Exclude from serialization |
+
+---
+
+## Node Name Matching
+
+### Default Convention
+
+By default, the class name (lowercased) must match the KDL node name:
+
+```csharp
+// Matches: package "my-lib"
+public class Package { ... }
+```
+
+### Custom Node Name with `[KdlType]`
+
+Override the expected node name:
+
+```csharp
+// Matches: db "primary" (not "database")
+[KdlType("db")]
+public class Database { ... }
+```
+
+---
+
+## `[KdlArgument(index)]` — Positional Arguments
+
+Maps a property to a **positional argument** by zero-based index.
+
+### KDL
+
+```kdl
+point 10 20 30 label="origin"
+```
+
+### C# Model
+
+```csharp
+public class Point
+{
+ [KdlArgument(0)]
+ public int X { get; set; }
+
+ [KdlArgument(1)]
+ public int Y { get; set; }
+
+ [KdlArgument(2)]
+ public int Z { get; set; }
+
+ [KdlProperty("label")]
+ public string? Label { get; set; }
+}
+```
+
+### Rules
+
+1. **Index is required** — Each `[KdlArgument]` must specify its position
+2. **Indices should be contiguous** — Gaps (0, 2 without 1) may cause errors
+3. **Order matters** — During serialization, arguments are written in index order
+4. **Scalar types only** — Arguments cannot be complex objects
+
+### Supported Types
+
+- `string`
+- `int`, `long`, `double`, `decimal`
+- `bool`
+- `Guid` (with `(uuid)` type annotation)
+- `DateTimeOffset` (with `(date-time)` type annotation)
+- Nullable variants of all above
+
+---
+
+## `[KdlProperty(key?)]` — Named Properties
+
+Maps a property to a **KDL property** (key=value pair).
+
+### KDL
+
+```kdl
+dependency lodash version="4.17.21" optional=#false
+```
+
+### C# Model
+
+```csharp
+public class Dependency
+{
+ [KdlArgument(0)]
+ public string Package { get; set; } = "";
+
+ [KdlProperty("version")]
+ public string Version { get; set; } = "*";
+
+ [KdlProperty("optional")]
+ public bool Optional { get; set; }
+}
+```
+
+### Key Name Resolution
+
+1. **Explicit key**: `[KdlProperty("my-key")]` → matches `my-key=...`
+2. **Implicit key**: `[KdlProperty]` → uses property name lowercased
+
+```csharp
+[KdlProperty] // Matches "timeout=..."
+public int Timeout { get; set; }
+
+[KdlProperty("max-retries")] // Matches "max-retries=..."
+public int MaxRetries { get; set; }
+```
+
+### Rules
+
+1. **Last value wins** — Per KDL spec, if `key=1 key=2`, the value is `2`
+2. **Missing properties use defaults** — No error if property absent in KDL
+3. **Scalar types only** — Properties cannot be complex objects
+
+---
+
+## `[KdlNode(name?)]` — Child Nodes
+
+Maps a property to **child nodes** within the parent's `{ }` block.
+
+### Basic Usage — Collection of Child Nodes
+
+#### KDL
+
+```kdl
+project web-app version="1.0.0" {
+ dependency lodash version="4.17.21"
+ dependency react version="18.2.0"
+ devDependency jest version="29.0.0"
+}
+```
+
+#### C# Model
+
+```csharp
+public class Project
+{
+ [KdlArgument(0)]
+ public string Name { get; set; } = "";
+
+ [KdlProperty("version")]
+ public string Version { get; set; } = "1.0.0";
+
+ [KdlNode("dependency")]
+ public List Dependencies { get; set; } = [];
+
+ [KdlNode("devDependency")]
+ public List DevDependencies { get; set; } = [];
+}
+```
+
+### Single Complex Child
+
+When the property type is a **non-collection complex type**, it maps to a single child node:
+
+#### KDL
+
+```kdl
+application myapp {
+ database {
+ host "localhost"
+ port 5432
+ }
+}
+```
+
+#### C# Model
+
+```csharp
+public class Application
+{
+ [KdlArgument(0)]
+ public string Name { get; set; } = "";
+
+ [KdlNode("database")]
+ public DatabaseConfig? Database { get; set; }
+}
+
+public class DatabaseConfig
+{
+ [KdlNode("host")] // Maps child node's Arg(0)
+ public string Host { get; set; } = "";
+
+ [KdlNode("port")] // Maps child node's Arg(0)
+ public int Port { get; set; }
+}
+```
+
+### Scalar Child Node
+
+When the property type is a **scalar type**, it extracts `Arg(0)` from the child node:
+
+#### KDL
+
+```kdl
+config {
+ timeout 5000
+ enabled #true
+}
+```
+
+#### C# Model
+
+```csharp
+public class Config
+{
+ [KdlNode("timeout")]
+ public int Timeout { get; set; }
+
+ [KdlNode("enabled")]
+ public bool Enabled { get; set; }
+}
+```
+
+### Node Name Resolution
+
+1. **Explicit name**: `[KdlNode("my-items")]` → matches child nodes named `my-items`
+2. **Implicit name**: `[KdlNode]` → uses property name lowercased
+
+### Rules
+
+1. **Collection types** → Collects all matching child nodes into the list/array
+2. **Complex types** → Expects exactly one matching child node (throws if multiple)
+3. **Scalar types** → Extracts `Arg(0)` from the single matching child node
+4. **Supported collection types**: `List`, `T[]`, `IEnumerable`, `IList`
+
+---
+
+## `[KdlIgnore]` — Exclude Properties
+
+Excludes a property from both serialization and deserialization:
+
+```csharp
+public class User
+{
+ [KdlArgument(0)]
+ public string Username { get; set; } = "";
+
+ [KdlIgnore]
+ public string ComputedDisplayName => Username.ToUpperInvariant();
+
+ [KdlIgnore]
+ public DateTime LoadedAt { get; set; } = DateTime.UtcNow;
+}
+```
+
+---
+
+## Type Conversion
+
+### Automatic Type Mapping
+
+| C# Type | KDL Representation |
+|---------|-------------------|
+| `string` | `"value"` or `bare-string` |
+| `int`, `long` | `123`, `0xFF`, `0o77`, `0b1010` |
+| `double`, `decimal` | `3.14`, `1.5e-10` |
+| `bool` | `#true`, `#false` |
+| `Guid` | `(uuid)"550e8400-..."` |
+| `DateTimeOffset` | `(date-time)"2024-01-15T10:30:00Z"` |
+| Nullable `T?` | Value or `#null` |
+
+### Type Annotations
+
+Type annotations in KDL provide parsing hints. The serializer recognizes:
+
+- `(uuid)` → `Guid`
+- `(date-time)` → `DateTimeOffset`
+
+```kdl
+user alice {
+ id (uuid)"550e8400-e29b-41d4-a716-446655440000"
+ createdAt (date-time)"2024-01-15T10:30:00Z"
+}
+```
+
+---
+
+## Document-Level vs Node-Level Deserialization
+
+### Single Node → Object
+
+When your type has `[KdlArgument]` or `[KdlProperty]` attributes, the deserializer expects **exactly one root node**:
+
+```csharp
+var kdl = "package my-lib version=\"1.0\"";
+var pkg = KdlSerializer.Deserialize(kdl);
+```
+
+### Multiple Nodes → Collection
+
+Use `DeserializeMany` for documents with multiple top-level nodes:
+
+```kdl
+server web-1 host="10.0.0.1"
+server web-2 host="10.0.0.2"
+server api-1 host="10.0.1.1"
+```
+
+```csharp
+var servers = KdlSerializer.DeserializeMany(kdl);
+// Returns IEnumerable with 3 items
+```
+
+### Document as Container
+
+For a document where each node type maps to a different property:
+
+```kdl
+name "my-project"
+version "1.0.0"
+author "Alice"
+```
+
+```csharp
+public class Manifest
+{
+ [KdlNode("name")]
+ public string Name { get; set; } = "";
+
+ [KdlNode("version")]
+ public string Version { get; set; } = "";
+
+ [KdlNode("author")]
+ public string Author { get; set; } = "";
+}
+```
+
+---
+
+## Complete Example
+
+### KDL Document
+
+```kdl
+project web-app version="2.0.0" {
+ dependency lodash version="4.17.21" optional=#false
+ dependency react version="18.2.0" optional=#false
+
+ devDependency jest version="29.0.0"
+ devDependency typescript version="5.0.0"
+
+ author "Alice" {
+ email "alice@example.com"
+ url "https://github.com/alice"
+ }
+
+ repository type="git" url="https://github.com/alice/web-app"
+}
+```
+
+### C# Models
+
+```csharp
+public class Project
+{
+ [KdlArgument(0)]
+ public string Name { get; set; } = "";
+
+ [KdlProperty("version")]
+ public string Version { get; set; } = "1.0.0";
+
+ [KdlNode("dependency")]
+ public List Dependencies { get; set; } = [];
+
+ [KdlNode("devDependency")]
+ public List DevDependencies { get; set; } = [];
+
+ [KdlNode("author")]
+ public Author? Author { get; set; }
+
+ [KdlNode("repository")]
+ public Repository? Repository { get; set; }
+}
+
+public class Dependency
+{
+ [KdlArgument(0)]
+ public string Package { get; set; } = "";
+
+ [KdlProperty("version")]
+ public string Version { get; set; } = "*";
+
+ [KdlProperty("optional")]
+ public bool Optional { get; set; }
+}
+
+public class Author
+{
+ [KdlArgument(0)]
+ public string Name { get; set; } = "";
+
+ [KdlNode("email")]
+ public string? Email { get; set; }
+
+ [KdlNode("url")]
+ public string? Url { get; set; }
+}
+
+public class Repository
+{
+ [KdlProperty("type")]
+ public string Type { get; set; } = "";
+
+ [KdlProperty("url")]
+ public string Url { get; set; } = "";
+}
+```
+
+---
+
+## Decision Tree: Which Attribute to Use?
+
+```
+Is the data a positional value after the node name?
+├─ YES → [KdlArgument(index)]
+└─ NO
+ │
+ Is the data a key=value pair on the same line?
+ ├─ YES → [KdlProperty("key")]
+ └─ NO
+ │
+ Is the data inside a { } children block?
+ ├─ YES → [KdlNode("childNodeName")]
+ └─ NO → Not mappable with current attributes
+```
+
+---
+
+## Limitations & Notes
+
+1. **No dictionary support** — `Dictionary` types are not currently supported
+2. **No polymorphism** — Cannot deserialize to derived types based on discriminator
+3. **Case sensitivity** — Node/property name matching is case-insensitive by default
+4. **Argument gaps** — Missing argument indices will throw; ensure contiguous indices
+5. **Round-trip fidelity** — Comments, formatting, and slashdash elements are not preserved
diff --git a/samples/01-simple-node.kdl b/samples/01-simple-node.kdl
new file mode 100644
index 0000000..9ee78ec
--- /dev/null
+++ b/samples/01-simple-node.kdl
@@ -0,0 +1,4 @@
+// Simple node with arguments and properties
+// Maps to a single object with scalar values
+
+package "my-awesome-lib" version="2.1.0" description="A really cool library"
diff --git a/samples/02-multiple-arguments.kdl b/samples/02-multiple-arguments.kdl
new file mode 100644
index 0000000..99e7d19
--- /dev/null
+++ b/samples/02-multiple-arguments.kdl
@@ -0,0 +1,7 @@
+// Node with multiple positional arguments
+// Arguments are ordered and indexed (0, 1, 2...)
+
+command git commit -m Initial commit
+
+// Another example: coordinates
+point 10 20 30 label="origin"
diff --git a/samples/03-nested-children.kdl b/samples/03-nested-children.kdl
new file mode 100644
index 0000000..ddce116
--- /dev/null
+++ b/samples/03-nested-children.kdl
@@ -0,0 +1,11 @@
+// Node with child nodes (children block)
+// Children are nodes inside { }
+
+project web-app version="1.0.0" {
+ dependency lodash version="4.17.21" optional=#false
+ dependency react version="18.2.0" optional=#false
+ dependency typescript version="5.0.0" optional=#true
+
+ devDependency jest version="29.0.0"
+ devDependency eslint version="8.0.0"
+}
diff --git a/samples/04-numeric-types.kdl b/samples/04-numeric-types.kdl
new file mode 100644
index 0000000..81b14c4
--- /dev/null
+++ b/samples/04-numeric-types.kdl
@@ -0,0 +1,31 @@
+// Various numeric types and formats
+// KDL supports integers, floats, hex, octal, binary
+
+config {
+ // Integer properties
+ timeout 5000
+ maxRetries 3
+ port 8080
+
+ // Large numbers (long)
+ maxFileSize 9223372036854775807
+
+ // Floating point
+ ratio 0.75
+ pi 3.14159265358979
+
+ // Scientific notation
+ precision 1.5e-10
+
+ // Different bases (all represent the same value: 255)
+ hexValue 0xFF
+ octalValue 0o377
+ binaryValue 0b11111111
+
+ // Negative numbers
+ offset -100
+ temperature -273.15
+
+ // Underscores for readability
+ population 7_900_000_000
+}
diff --git a/samples/05-type-annotations.kdl b/samples/05-type-annotations.kdl
new file mode 100644
index 0000000..3cdf365
--- /dev/null
+++ b/samples/05-type-annotations.kdl
@@ -0,0 +1,30 @@
+// Type annotations provide hints about value interpretation
+// Format: (type)value
+
+user alice {
+ id (uuid)"550e8400-e29b-41d4-a716-446655440000"
+ createdAt (date-time)"2024-01-15T10:30:00Z"
+ lastLogin (date-time)"2024-12-17T14:22:33.123Z"
+
+ // Numeric type annotations
+ age (u8)28
+ balance (decimal)"1234567.89"
+ score (f64)98.5
+}
+
+// Type annotation on node name (context/role annotation)
+(admin)user "superuser" {
+ permissions "all"
+}
+
+// Various string type annotations from the spec
+metadata {
+ email (email)"user@example.com"
+ website (url)"https://example.com"
+ ip (ipv4)"192.168.1.1"
+ ipv6 (ipv6)"2001:0db8:85a3:0000:0000:8a2e:0370:7334"
+ country (country-2)"US"
+ duration (duration)"P1Y2M3DT4H5M6S"
+ pattern (regex)"^[a-z]+$"
+ binary (base64)"SGVsbG8gV29ybGQh"
+}
diff --git a/samples/06-booleans-and-null.kdl b/samples/06-booleans-and-null.kdl
new file mode 100644
index 0000000..5b5ca71
--- /dev/null
+++ b/samples/06-booleans-and-null.kdl
@@ -0,0 +1,19 @@
+// Boolean and null values use # prefix in KDL 2.0
+
+feature dark-mode {
+ enabled #true
+ beta #false
+ deprecatedAt #null
+}
+
+settings {
+ // All the keyword values
+ debug #true
+ production #false
+ overrideConfig #null
+
+ // Special float keywords
+ infiniteTimeout #inf
+ negativeInfinity #-inf
+ undefinedValue #nan
+}
diff --git a/samples/07-string-varieties.kdl b/samples/07-string-varieties.kdl
new file mode 100644
index 0000000..c671b6b
--- /dev/null
+++ b/samples/07-string-varieties.kdl
@@ -0,0 +1,33 @@
+// KDL supports multiple string syntaxes
+
+strings {
+ // Bare/identifier strings (no quotes needed for simple values)
+ simple hello-world
+ kebab my-kebab-case
+
+ // Quoted strings (required for whitespace, special chars)
+ withSpaces "hello world"
+ withQuote "say \"hello\""
+ withNewline "line1\nline2"
+ withTab "col1\tcol2"
+ unicode "emoji: \u{1F600}"
+
+ // Raw strings (no escape processing)
+ rawPath #"C:\Users\name\file.txt"#
+ rawRegex #"^\d+\.\d+\.\d+$"#
+ rawWithQuotes ##"She said "hello""##
+
+ // Multi-line strings
+ multiLine """
+ This is a multi-line string.
+ The indentation of the closing quotes
+ determines the base indentation.
+ All lines are dedented accordingly.
+ """
+
+ // Raw multi-line
+ rawMultiLine #"""
+ No escapes here: \n \t \u{1234}
+ All literal backslashes.
+ """#
+}
diff --git a/samples/08-deeply-nested.kdl b/samples/08-deeply-nested.kdl
new file mode 100644
index 0000000..99f2bf7
--- /dev/null
+++ b/samples/08-deeply-nested.kdl
@@ -0,0 +1,62 @@
+// Deeply nested structure - common in config files
+
+application "enterprise-app" version="3.0.0" {
+ database "primary" {
+ connection {
+ host "db.example.com"
+ port 5432
+ ssl #true
+
+ pool {
+ minSize 5
+ maxSize 50
+ idleTimeout 30000
+ }
+
+ credentials {
+ username "app_user"
+ // In real config, this would be a reference
+ passwordEnvVar "DB_PASSWORD"
+ }
+ }
+
+ replica "read-replica-1" {
+ host "replica1.example.com"
+ port 5432
+ readOnly #true
+ }
+
+ replica "read-replica-2" {
+ host "replica2.example.com"
+ port 5432
+ readOnly #true
+ }
+ }
+
+ cache "redis" {
+ host "cache.example.com"
+ port 6379
+ ttl 3600
+
+ cluster {
+ enabled #true
+ nodes 3
+ }
+ }
+
+ logging {
+ level "info"
+ format "json"
+
+ output "console" {
+ colorize #true
+ }
+
+ output "file" {
+ path "/var/log/app.log"
+ maxSize 10485760
+ maxFiles 5
+ compress #true
+ }
+ }
+}
diff --git a/samples/09-mixed-entries.kdl b/samples/09-mixed-entries.kdl
new file mode 100644
index 0000000..94b07da
--- /dev/null
+++ b/samples/09-mixed-entries.kdl
@@ -0,0 +1,25 @@
+// Arguments and properties can be freely intermixed
+// This is valid KDL - order of args is preserved, props can appear anywhere
+
+// Properties between arguments
+node 1 key=value 2 another=prop 3
+
+// Real-world example: CLI-like syntax
+run "npm" "install" --save=#true "lodash" --dev=#false
+
+// Build configuration with mixed entries
+task "build" priority=high "src/**/*.ts" output="dist" "!src/**/*.test.ts" {
+ compiler "tsc"
+ watch #false
+}
+
+// HTTP request definition
+request "POST" "/api/users" content-type="application/json" {
+ header "Authorization" "Bearer token123"
+ header "X-Request-Id" (uuid)"550e8400-e29b-41d4-a716-446655440000"
+
+ body {
+ name "John Doe"
+ email "john@example.com"
+ }
+}
diff --git a/samples/10-document-vs-node.kdl b/samples/10-document-vs-node.kdl
new file mode 100644
index 0000000..7d77f6e
--- /dev/null
+++ b/samples/10-document-vs-node.kdl
@@ -0,0 +1,24 @@
+// Document with multiple top-level nodes
+// This tests DeserializeMany and document-level mapping
+
+// When deserializing as a document, each top-level node maps to a list item
+// When deserializing as a single node, you'd wrap in a container
+
+server "web-1" host="10.0.0.1" port=80 {
+ role "frontend"
+ role "load-balancer"
+}
+
+server "web-2" host="10.0.0.2" port=80 {
+ role "frontend"
+}
+
+server "api-1" host="10.0.1.1" port=8080 {
+ role "backend"
+ role "api"
+}
+
+server "db-1" host="10.0.2.1" port=5432 {
+ role "database"
+ role "primary"
+}
diff --git a/samples/11-slashdash-comments.kdl b/samples/11-slashdash-comments.kdl
new file mode 100644
index 0000000..de7c4a5
--- /dev/null
+++ b/samples/11-slashdash-comments.kdl
@@ -0,0 +1,31 @@
+// Slashdash comments out entire elements
+// The serializer should NOT see these in the parsed AST
+
+config {
+ // Regular comment - ignored
+ enabled #true
+
+ /- disabled #true // This entire node is commented out
+
+ database {
+ host "localhost"
+ /- port 5432 // Commented out argument
+ port 3306 // This is the actual port
+
+ credentials user="admin" /- password="secret" // password prop commented out
+ }
+
+ /- slashdashed-node {
+ // Entire children block commented out
+ this "is" ignored=#true
+ }
+
+ // Slashdash can span multiple lines
+ /-
+ experimental {
+ feature1 #true
+ feature2 #true
+ }
+
+ production #true
+}
diff --git a/samples/12-edge-cases.kdl b/samples/12-edge-cases.kdl
new file mode 100644
index 0000000..cb0669b
--- /dev/null
+++ b/samples/12-edge-cases.kdl
@@ -0,0 +1,53 @@
+// Edge cases and tricky scenarios
+
+// Empty nodes (valid - just a name)
+empty-node
+
+// Node with only children, no args/props
+container {
+ child1
+ child2
+}
+
+// Same property multiple times - rightmost wins (per spec)
+settings priority=1 priority=2 priority=3 // priority should be 3
+
+// Unicode identifiers (bare strings)
+日本語ノード value="Japanese node name"
+émoji-🎉 value="Emoji in identifier"
+
+// Very long numbers
+bignum 999999999999999999999999999999
+
+// Numbers with many underscores
+formatted 1_000_000_000
+
+// Semicolon-separated nodes on one line
+a; b; c
+
+// Children on same line
+parent { child1; child2; child3 }
+
+// Line continuation
+very-long-node \
+ arg1 \
+ arg2 \
+ prop1="value1" \
+ prop2="value2" \
+ {
+ child
+ }
+
+// Property with same name as argument value (no conflict)
+node "key" key="different-value"
+
+// Nested type annotations
+typed-node (container)data {
+ item (u8)255
+ item (i8)-128
+}
+
+// Null in various positions
+nullable arg1=#null prop=#null {
+ child #null
+}
diff --git a/samples/13-real-world-package.kdl b/samples/13-real-world-package.kdl
new file mode 100644
index 0000000..dd1a440
--- /dev/null
+++ b/samples/13-real-world-package.kdl
@@ -0,0 +1,47 @@
+// Real-world example: Package manifest (like package.json in KDL)
+
+package "kuddle" version="1.0.0" {
+ description "A KDL parser and serializer for .NET"
+ license "MIT"
+
+ author "Henry" email="henry@example.com" {
+ url "https://github.com/henry"
+ }
+
+ repository type="git" url="https://github.com/henry/kuddle"
+
+ keywords "kdl" "parser" "serializer" "configuration" "dotnet"
+
+ engines {
+ dotnet ">=8.0.0"
+ }
+
+ scripts {
+ build "dotnet build"
+ test "dotnet test"
+ pack "dotnet pack"
+ publish "dotnet nuget push"
+ }
+
+ dependency "Parlot" version="^1.0.0" {
+ optional #false
+ }
+
+ devDependency "TUnit" version="^1.0.0"
+ devDependency "BenchmarkDotNet" version="^0.14.0"
+
+ // Conditional dependencies
+ dependency "System.Text.Json" version=">=8.0.0" {
+ condition "net8.0"
+ }
+
+ files "src/**/*.cs" "!src/**/*.Tests.cs" {
+ include "LICENSE"
+ include "README.md"
+ }
+
+ exports {
+ main "./src/Kuddle/Kuddle.csproj"
+ types "./src/Kuddle/Kuddle.csproj"
+ }
+}
diff --git a/samples/14-real-world-ci.kdl b/samples/14-real-world-ci.kdl
new file mode 100644
index 0000000..e8bc03e
--- /dev/null
+++ b/samples/14-real-world-ci.kdl
@@ -0,0 +1,87 @@
+// Real-world example: CI/CD pipeline configuration
+
+pipeline "main" {
+ trigger {
+ branch "main" "develop" "release/*"
+ path "src/**" "tests/**" ".github/**"
+
+ // Exclude paths
+ exclude "**/*.md" "docs/**"
+ }
+
+ variables {
+ variable "DOTNET_VERSION" "8.0.x"
+ variable "BUILD_CONFIGURATION" "Release"
+ variable "NUGET_FEED" "https://api.nuget.org/v3/index.json" secret=#false
+ }
+
+ stage "build" {
+ displayName "Build and Test"
+
+ job "build-windows" {
+ pool "windows-latest"
+
+ step "checkout" {
+ uses "actions/checkout@v4"
+ with {
+ fetch-depth 0
+ }
+ }
+
+ step "setup-dotnet" {
+ uses "actions/setup-dotnet@v4"
+ with {
+ dotnet-version "${{ variables.DOTNET_VERSION }}"
+ }
+ }
+
+ step "restore" {
+ run "dotnet restore"
+ }
+
+ step "build" {
+ run "dotnet build --configuration ${{ variables.BUILD_CONFIGURATION }} --no-restore"
+ }
+
+ step "test" {
+ run "dotnet test --configuration ${{ variables.BUILD_CONFIGURATION }} --no-build --logger trx"
+ continueOnError #false
+ }
+ }
+
+ job "build-linux" {
+ pool "ubuntu-latest"
+ dependsOn #null // Runs in parallel
+
+ step "checkout" uses="actions/checkout@v4"
+ step "setup-dotnet" uses="actions/setup-dotnet@v4"
+ step "restore" run="dotnet restore"
+ step "build" run="dotnet build -c Release"
+ step "test" run="dotnet test -c Release"
+ }
+ }
+
+ stage "publish" {
+ displayName "Publish Package"
+ dependsOn "build"
+ condition "eq(variables['Build.SourceBranch'], 'refs/heads/main')"
+
+ job "publish-nuget" {
+ pool "ubuntu-latest"
+
+ step "download-artifact" {
+ uses "actions/download-artifact@v4"
+ with {
+ name "nuget-package"
+ }
+ }
+
+ step "push-nuget" {
+ run "dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source ${{ variables.NUGET_FEED }}"
+ env {
+ NUGET_API_KEY "${{ secrets.NUGET_API_KEY }}"
+ }
+ }
+ }
+ }
+}
diff --git a/spec.md b/spec.md
new file mode 100644
index 0000000..c34e33f
--- /dev/null
+++ b/spec.md
@@ -0,0 +1,1112 @@
+---
+title: "The KDL Document Language"
+abbrev: "KDL"
+docname: draft-marchan-kdl2-latest
+submissionType: independent
+category: exp
+
+ipr: none
+area: General
+venue:
+ github: kdl-org/kdl
+ home: