Skip to content

Commit

Permalink
Use System.Text.Json and Scriban
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikSchierboom committed Feb 2, 2025
1 parent f2c700c commit 7dc6a88
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 77 deletions.
10 changes: 5 additions & 5 deletions exercises/practice/leap/.meta/Generator.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ using Xunit;

public class LeapTests
{
{{#test_cases}}
[Fact{{#unless @first}}(Skip = "Remove this Skip property to run this test"){{/unless}}]
public void {{test_method_name}}()
{{for testCase in testCases}}
[Fact{{if !for.first}}(Skip = "Remove this Skip property to run this test"){{end}}]
public void {{testCase.testMethodName}}()
{
Assert.{{expected}}(Leap.IsLeapYear({{input.year}}));
Assert.{{testCase.expected ? "True" : "False"}}(Leap.IsLeapYear({{testCase.input.year}}));
}
{{/test_cases}}
{{end}}
}
32 changes: 16 additions & 16 deletions generators/CanonicalData.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
using System.Collections.Immutable;
using System.Text.Json;
using System.Text.Json.Nodes;

using LibGit2Sharp;

using Newtonsoft.Json.Linq;

namespace Generators;

internal record CanonicalData(Exercise Exercise, JObject[] TestCases);
internal record CanonicalData(Exercise Exercise, JsonNode[] TestCases);

internal static class CanonicalDataParser
{
static CanonicalDataParser() => ProbSpecs.Sync();

internal static CanonicalData Parse(Exercise exercise) => new(exercise, ParseTestCases(exercise));

private static JObject[] ParseTestCases(Exercise exercise)
{
var jsonObject = JObject.Parse(File.ReadAllText(Paths.CanonicalDataFile(exercise)));
return ParseTestCases(jsonObject, ImmutableQueue<string>.Empty).ToArray();
}
private static JsonNode[] ParseTestCases(Exercise exercise) =>
ParseTestCases(ParseCanonicalData(exercise), ImmutableQueue<string>.Empty).ToArray();

private static JsonNode ParseCanonicalData(Exercise exercise) =>
JsonNode.Parse(File.ReadAllText(Paths.CanonicalDataFile(exercise)))!;

private static IEnumerable<JObject> ParseTestCases(JObject jsonObject, ImmutableQueue<string> path)
private static IEnumerable<JsonNode> ParseTestCases(JsonNode jsonNode, ImmutableQueue<string> path)
{
var updatedPath = jsonObject.TryGetValue("description", out var description)
? path.Enqueue(description.Value<string>()!)
var updatedPath = jsonNode["description"] is {} description
? path.Enqueue(description.GetValue<string>())
: path;

return jsonObject.TryGetValue("cases", out var cases)
? ((JArray)cases).Cast<JObject>().SelectMany(child => ParseTestCases(child, updatedPath))
: [ToTestCase(jsonObject, updatedPath)];
return jsonNode["cases"] is {} cases
? cases.AsArray().SelectMany(child => ParseTestCases(child!, updatedPath))
: [ToTestCase(jsonNode, updatedPath)];
}

private static JObject ToTestCase(JObject testCaseJson, IEnumerable<string> path)
private static JsonNode ToTestCase(JsonNode testCaseJson, IEnumerable<string> path)
{
testCaseJson["path"] = JArray.FromObject(path);
testCaseJson["path"] = JsonSerializer.SerializeToNode(path);
return testCaseJson;
}

Expand Down
5 changes: 1 addition & 4 deletions generators/Generators.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Handlebars.Net" Version="2.1.6" />
<PackageReference Include="Handlebars.Net.Extension.NewtonsoftJson" Version="1.0.3" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.4.10" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="LibGit2Sharp" Version="0.31.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Scriban" Version="5.12.1" />
<PackageReference Include="Tomlyn" Version="0.18.0" />
</ItemGroup>

Expand Down
5 changes: 2 additions & 3 deletions generators/Naming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ namespace Generators;

internal static class Naming
{
internal static string ToMethodName(params object[] path) =>
path.Cast<string>()
.Unwords()
internal static string ToMethodName(params string[] path) =>
path.Unwords()
.Words()
.Select(Transform)
.Unwords()
Expand Down
66 changes: 18 additions & 48 deletions generators/Templates.cs
Original file line number Diff line number Diff line change
@@ -1,72 +1,42 @@
using System.Dynamic;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;

using HandlebarsDotNet;
using HandlebarsDotNet.Helpers;
using Microsoft.CodeAnalysis.CSharp;

using Newtonsoft.Json.Linq;
using Scriban;
using Scriban.Runtime;

namespace Generators;

internal static class Templates
{
private static readonly IHandlebars HandlebarsContext = Handlebars.Create();

static Templates()
public static string RenderTestsCode(CanonicalData canonicalData)
{
HandlebarsHelpers.Register(HandlebarsContext, options => { options.UseCategoryPrefix = false; });
HandlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture;

HandlebarsContext.RegisterHelper("lit", (writer, context, parameters) =>
writer.WriteSafeString(Formatting.FormatLiteral(parameters.First())));

HandlebarsContext.RegisterHelper("equals", (output, options, context, arguments) =>
{
if (arguments.Length != 2) throw new HandlebarsException("{{#equals}} helper must have exactly two arguments");

if (arguments[0]!.Equals(arguments[1]!))
options.Template(output, context);
else
options.Inverse(output, context);
});
var template = Template.Parse(File.ReadAllText(Paths.TemplateFile(canonicalData.Exercise)));
return template.Render(TemplateData.ForCanonicalData(canonicalData));
}

public static string RenderTestsCode(CanonicalData canonicalData) =>
CompileTemplate(canonicalData.Exercise)(TemplateData.ForCanonicalData(canonicalData));

private static HandlebarsTemplate<object, object> CompileTemplate(Exercise exercise) =>
HandlebarsContext.Compile(File.ReadAllText(Paths.TemplateFile(exercise)));

private static class TemplateData
{
internal static Dictionary<string, object> ForCanonicalData(CanonicalData canonicalData)
internal static JsonElement ForCanonicalData(CanonicalData canonicalData)
{
var testCases = canonicalData.TestCases.Select(Create).ToArray();
var testCasesByProperty = GroupTestCasesByProperty(testCases);

return new()
{
["test_cases"] = testCases.ToArray(),
["test_cases_by_property"] = GroupTestCasesByProperty(testCases)
};
return JsonSerializer.SerializeToElement(new { testCases, testCasesByProperty });
}

private static ExpandoObject Create(JToken testCase)
private static JsonElement Create(JsonNode testCase)
{
dynamic testData = testCase.ToObject<ExpandoObject>()!;
testData.test_method_name = Naming.ToMethodName(testData.path.ToArray());
testData.short_test_method_name = Naming.ToMethodName(testData.description);

if (testCase["expected"] is JArray expected)
{
testData.expected = expected.Select(e => e.ToString()).ToArray();
}
testCase["testMethodName"] = Naming.ToMethodName(testCase["path"]!.AsArray().GetValues<string>().ToArray());
testCase["shortMethodName"] = Naming.ToMethodName(testCase["description"]!.GetValue<string>());

return testData;
return JsonSerializer.SerializeToElement(testCase);
}

private static Dictionary<string, dynamic[]> GroupTestCasesByProperty(IEnumerable<dynamic> testCases) =>
private static Dictionary<string, JsonElement[]> GroupTestCasesByProperty(IEnumerable<JsonElement> testCases) =>
testCases
.GroupBy(testCase => (string)testCase.property)
.GroupBy(testCase => testCase.GetProperty("property").GetString()!)
.ToDictionary(kv => kv.Key, kv => kv.ToArray());
}
}
}
2 changes: 1 addition & 1 deletion generators/TestCasesConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal static CanonicalData RemoveExcludedTestCases(CanonicalData canonicalDat
{
var excludedTestCaseIds = ExcludedTestCaseIds(canonicalData.Exercise);
var includedTestCases = canonicalData.TestCases
.Where(testCase => !excludedTestCaseIds.Contains(testCase["uuid"]!.ToObject<string>()!))
.Where(testCase => !excludedTestCaseIds.Contains(testCase["uuid"]!.GetValue<string>()))
.ToArray();

return canonicalData with { TestCases = includedTestCases };
Expand Down

0 comments on commit 7dc6a88

Please sign in to comment.