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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,694 changes: 0 additions & 4,694 deletions .ai/outputs/codebase.txt

This file was deleted.

13 changes: 12 additions & 1 deletion .build/targets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
"The release version.",
CommandOptionType.SingleValue
);
var packProjectOption = app.Option<string>(
"--pack-project <path>",
"The project file to pack (relative path). If omitted the default project will be used.",
CommandOptionType.SingleValue
);

app.Argument(
"targets",
Expand All @@ -37,7 +42,9 @@
{
const string configuration = "Release";
const string solution = "Kuddle.slnx";
const string packProject = "src/Kuddle.Net/Kuddle.Net.csproj";
// Default project path (relative to repo root) used when `--pack-project` isn't supplied.
// Example: "src/Kuddle.Net/Kuddle.Net.csproj"
const string defaultPackProject = "src/Kuddle.Net/Kuddle.Net.csproj";

var root = Directory.GetCurrentDirectory();

Expand Down Expand Up @@ -91,6 +98,10 @@ await RunAsync(
dependsOn: ["build"],
async () =>
{
var packProject = packProjectOption.Value();
if (string.IsNullOrWhiteSpace(packProject))
packProject = defaultPackProject;

ArgumentException.ThrowIfNullOrWhiteSpace(packProject);

var nugetOutputDir = Path.Combine(root, "dist", "nuget");
Expand Down
33 changes: 30 additions & 3 deletions .github/workflows/prerelease-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ name: Publish Preview Nuget
on:
push:
tags:
- "[0-9]*.[0-9]*.[0-9]*-*" # Matches semver tags with a suffix, e.g., 1.2.3-preview, 1.2.3-beta1
- "*-[0-9]*.[0-9]*.[0-9]-*" # Matches semver tags with a suffix, e.g., 1.2.3-preview, 1.2.3-beta1
workflow_dispatch:
inputs:
project:
description: "Relative path to the .csproj to release (optional, e.g. src/Kuddle.Net/Kuddle.Net.csproj)"
required: false
type: string

permissions:
contents: write
Expand All @@ -16,13 +21,35 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Run Tests
run: dotnet run .build/targets.cs test
- name: Pack NuGet Package
run: dotnet run .build/targets.cs pack
- name: Select project to pack
id: select_project
run: |
if [ -n "${{ github.event.inputs.project }}" ]; then
echo "project=${{ github.event.inputs.project }}" >> $GITHUB_OUTPUT
else
TAG_NAME="${GITHUB_REF##*/}"
if [[ "$TAG_NAME" == kuddle-conf-* ]]; then
echo "project=src/Kuddle.Net.Extensions.Configuration/Kuddle.Net.Extensions.Configuration.csproj" >> $GITHUB_OUTPUT
else
echo "project=src/Kuddle.Net/Kuddle.Net.csproj" >> $GITHUB_OUTPUT
fi
fi

- name: Pack NuGet Package (selected project)
run: |
set -e
mkdir -p dist/nuget
echo "Packing ${{ steps.select_project.outputs.project }}"
dotnet run .build/targets.cs pack --pack-project "${{ steps.select_project.outputs.project }}"
echo "All packages in dist/nuget:"
ls dist/nuget/*.nupkg
- name: Push NuGet Package
run: dotnet nuget push "dist/nuget/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source "https://api.nuget.org/v3/index.json" --skip-duplicate
22 changes: 20 additions & 2 deletions .github/workflows/release-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,31 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Run Tests
run: dotnet run .build/targets.cs test
- name: Pack NuGet Package
run: dotnet run .build/targets.cs pack
- name: Select project to pack based on tag prefix
id: select_project
run: |
TAG_NAME="${GITHUB_REF##*/}"
if [[ "$TAG_NAME" == kuddle-conf-* ]]; then
echo "project=src/Kuddle.Net.Extensions.Configuration/Kuddle.Net.Extensions.Configuration.csproj" >> $GITHUB_OUTPUT
else
echo "project=src/Kuddle.Net/Kuddle.Net.csproj" >> $GITHUB_OUTPUT
fi

- name: Pack NuGet Package (selected project)
run: |
set -e
mkdir -p dist/nuget
echo "Packing ${{ steps.select_project.outputs.project }}"
dotnet run .build/targets.cs pack --pack-project "${{ steps.select_project.outputs.project }}"
echo "All packages in dist/nuget:"
ls dist/nuget/*.nupkg
- name: Push NuGet Package
run: dotnet nuget push "dist/nuget/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source "https://api.nuget.org/v3/index.json" --skip-duplicate
4 changes: 3 additions & 1 deletion Kuddle.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
</Folder>
<Folder Name="/src/">
<Project Path="src/Kuddle.Net.Benchmarks/Kuddle.Net.Benchmarks.csproj" />
<Project Path="src/Kuddle.Net.Tests/Kuddle.Net.Tests.csproj" />
<Project Path="src/Kuddle.Net/Kuddle.Net.csproj" />
<Project Path="src/Kuddle.Net.Tests/Kuddle.Net.Tests.csproj" />
<Project Path="src/Kuddle.Net.Extensions.Configuration/Kuddle.Net.Extensions.Configuration.csproj" />
<Project Path="src/Kuddle.Net.Extensions.Configuration.Tests/Kuddle.Net.Extensions.Configuration.Tests.csproj" />
</Folder>
</Solution>
56 changes: 53 additions & 3 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,19 +1,69 @@
<Project>
<PropertyGroup>
<ArtifactsPath>$(MSBuildThisFileDirectory)..\.artifacts</ArtifactsPath>
<!-- Build Properties -->
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- NuGet Package Properties -->
<Authors>jamesshenry</Authors>
<Company>jamesshenry</Company>
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
<PackageProjectUrl>https://github.com/jamesshenry/Kuddle.Net</PackageProjectUrl>
<RepositoryUrl>https://github.com/jamesshenry/Kuddle.Net</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>readme.md</PackageReadmeFile>
<MinVerDefaultPreReleaseIdentifiers>preview</MinVerDefaultPreReleaseIdentifiers>
<Deterministic>true</Deterministic>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'"
>true</ContinuousIntegrationBuild
>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Roslynator.Analyzers" Version="4.14.1">
<PackageReference Include="MinVer" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" Version="4.14.1">
<PackageReference Include="Roslynator.Analyzers" Version="4.15.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="4.14.1">
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" Version="4.15.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="4.15.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
<!-- NuGet Package Files -->
<ItemGroup>
<None
Include="$(MSBuildThisFileDirectory)..\readme.md"
Pack="true"
PackagePath="\"
Condition="Exists('$(MSBuildThisFileDirectory)..\readme.md')"
/>
<None
Include="$(MSBuildThisFileDirectory)..\LICENSE.md"
Pack="true"
PackagePath="\"
Condition="Exists('$(MSBuildThisFileDirectory)..\LICENSE.md')"
/>
<None
Include="$(MSBuildThisFileDirectory)..\.build\assets\icon.png"
Pack="true"
PackagePath="\"
Condition="Exists('$(MSBuildThisFileDirectory)..\.build\assets\icon.png')"
/>
</ItemGroup>
</Project>
6 changes: 1 addition & 5 deletions src/Kuddle.Net.Benchmarks/Kuddle.Net.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@
<PropertyGroup>
<RootNamespace>Kuddle.Benchmarks</RootNamespace>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
Expand Down
161 changes: 161 additions & 0 deletions src/Kuddle.Net.Extensions.Configuration.Tests/ConfigurationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System.Diagnostics;
using Kuddle.Serialization;
using Microsoft.Extensions.Configuration;

namespace Kuddle.Extensions.Configuration.Tests;

public class ConfigurationTests
{
[Test]
public async Task AddKdlFile_ShouldNotThrow()
{
var serialized = KdlSerializer.Serialize(new Sample());
File.WriteAllText("sample.kdl", serialized);

await Assert
.That(() => new ConfigurationBuilder().AddKdlFile("sample.kdl").Build())
.ThrowsNothing();
}

[Test]
public async Task ReflectionBased_ConfigurationBind_ShouldBindToDeserializedKdl()
{
var result = new Sample();
var expected = new Sample
{
Title = "kdl example",
Owner = new Owner { Name = "Tom Preston-Werner", DoB = new DateTime(1979, 05, 27) },
Database = new Database
{
Enabled = true,
Ports = [8000, 8001, 8002],
Temp_Targets = new Dictionary<string, decimal> { ["cpu"] = 79.5m, ["case"] = 72m },
},
Servers = new Dictionary<string, Server>
{
["alpha"] = new() { Ip = "10.0.0.1", Role = Role.Frontend },
["beta"] = new() { Ip = "10.0.0.2", Role = Role.Backend },
},
};

// 1. Serialize the object
var serialized = KdlSerializer.Serialize(
expected,
new KdlSerializerOptions { UnwrapRoot = true }
);

// 2. YOU MUST WRITE THE FILE TO DISK
File.WriteAllText("sample.kdl", serialized);

var configuration = new ConfigurationBuilder().AddKdlFile("sample.kdl").Build();
configuration.Bind(result);

await Assert.That(result.Title).IsEqualTo(expected.Title);
await Assert.That(result.Database.Ports).Count().IsEqualTo(3);
await Assert.That(result.Database.Ports[0]).IsEqualTo((ushort)8000);
await Assert.That(result.Servers["alpha"].Ip).IsEqualTo("10.0.0.1");
}

[Test]
public async Task Configuration_ShouldFlattenHierarchy()
{
var kdl = """
server {
host "localhost"
port 8080
ssl #true
}
""";
File.WriteAllText("appsettings.kdl", kdl);

var config = new ConfigurationBuilder().AddKdlFile("appsettings.kdl").Build();

await Assert.That(config["server:host"]).IsEqualTo("localhost");
await Assert.That(config["server:port"]).IsEqualTo("8080");
await Assert.That(config["server:ssl"]).IsEqualTo("true");
}

[Test]
public async Task Configuration_ShouldHandleDuplicateNodesAsArrays()
{
var kdl = """
user name="Alice"
user name="Bob"
""";
File.WriteAllText("users.kdl", kdl);

var config = new ConfigurationBuilder().AddKdlFile("users.kdl").Build();

await Assert.That(config["user:0:name"]).IsEqualTo("Alice");
await Assert.That(config["user:1:name"]).IsEqualTo("Bob");
}

[Test]
public async Task Configuration_ShouldHandlePositionalArguments()
{
var kdl = """
endpoints "10.0.0.1" "10.0.0.2" "10.0.0.3"
""";
File.WriteAllText("network.kdl", kdl);

var config = new ConfigurationBuilder().AddKdlFile("network.kdl").Build();

await Assert.That(config["endpoints:0"]).IsEqualTo("10.0.0.1");
await Assert.That(config["endpoints:1"]).IsEqualTo("10.0.0.2");
}

[Test]
public async Task Configuration_ShouldOverrideValuesFromSubsequentFiles()
{
File.WriteAllText("base.kdl", "logging level=\"info\"");
File.WriteAllText("override.kdl", "logging level=\"debug\"");

var config = new ConfigurationBuilder()
.AddKdlFile("base.kdl")
.AddKdlFile("override.kdl")
.Build();

await Assert.That(config["logging:level"]).IsEqualTo("debug");
}

[Test]
public async Task OptionalFile_Missing_ShouldNotThrow()
{
await Assert
.That(() =>
new ConfigurationBuilder().AddKdlFile("missing.kdl", optional: true).Build()
)
.ThrowsNothing();
}

[Test]
public async Task RequiredFile_Missing_ShouldThrow()
{
// The base Microsoft.Extensions.Configuration.FileExtensions handles this,
// but it's good to ensure your provider doesn't swallow the error.
await Assert
.That(() =>
new ConfigurationBuilder().AddKdlFile("missing.kdl", optional: false).Build()
)
.Throws<FileNotFoundException>();
}

[Test]
public async Task Configuration_ShouldConvertKdlTypesToStrings()
{
var kdl = """
flags {
enabled #true
disabled #false
missing #null
}
""";
File.WriteAllText("flags.kdl", kdl);

var config = new ConfigurationBuilder().AddKdlFile("flags.kdl").Build();

await Assert.That(config["flags:enabled"]).IsEqualTo("true");
await Assert.That(config["flags:disabled"]).IsEqualTo("false");
await Assert.That(config["flags:missing"]).IsNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Kuddle.Extensions.Configuration.Tests</RootNamespace>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.1" />
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.1.0" />
<PackageReference Include="TUnit" Version="1.7.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Kuddle.Net.Extensions.Configuration\Kuddle.Net.Extensions.Configuration.csproj" />
</ItemGroup>
</Project>
Loading