From 9515e85777906690d15af09208e20575b1101fa2 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 3 Feb 2025 14:49:34 +0100 Subject: [PATCH 01/15] Allow specifying commands --- generators/Options.cs | 17 +++++++++++++++++ generators/Program.cs | 42 ++++++++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 generators/Options.cs diff --git a/generators/Options.cs b/generators/Options.cs new file mode 100644 index 0000000000..3e0362ab2c --- /dev/null +++ b/generators/Options.cs @@ -0,0 +1,17 @@ +using CommandLine; + +namespace Generators; + +[Verb("generate", isDefault: true, HelpText = "Generate the test file's contents using the exercise's generator template file.")] +internal class GenerateOptions +{ + [Option('e', "exercise", Required = false, HelpText = "The exercise (slug) which tests file to generate.")] + public string? Exercise { get; set; } +} + +[Verb("new", HelpText = "Create a new exercise generator template file.")] +internal class NewOptions +{ + [Option('e', "exercise", Required = true, HelpText = "The exercise (slug) for which to generate a generator file.")] + public string Exercise { get; set; } +} diff --git a/generators/Program.cs b/generators/Program.cs index 6da9d090d0..f15dde9c52 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -2,24 +2,34 @@ namespace Generators; -public static class Program +public static class Program { - private class Options + static void Main(string[] args) => + Parser.Default.ParseArguments(args) + .WithParsed(HandleGenerateCommand) + .WithParsed(HandleNewCommand) + .WithNotParsed(HandleErrors); + + private static void HandleGenerateCommand(GenerateOptions options) { - [Option('e', "exercise", Required = false, HelpText = "The exercise (slug) to generate the tests file for.")] - public string? Exercise { get; set; } + if (options.Exercise is not null) + { + TestsGenerator.Generate(Exercises.TemplatedExercise(options.Exercise)); + return; + } + + foreach (var exercise in Exercises.TemplatedExercises()) + TestsGenerator.Generate(exercise); + } + + private static void HandleNewCommand(NewOptions options) + { + throw new NotImplementedException(); } - - static void Main(string[] args) => - Parser.Default.ParseArguments(args) - .WithParsed(options => - { - foreach (var exercise in Exercises(options)) - TestsGenerator.Generate(exercise); - }); - private static Exercise[] Exercises(Options options) => - options.Exercise is null - ? Generators.Exercises.TemplatedExercises() - : [Generators.Exercises.TemplatedExercise(options.Exercise)]; + private static void HandleErrors(IEnumerable errors) + { + foreach (var error in errors) + Console.Error.WriteLine(error.ToString()); + } } \ No newline at end of file From 4a99426d5fda2b9fbd8c3ca3850b7d627d109ba9 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 3 Feb 2025 14:53:38 +0100 Subject: [PATCH 02/15] Add basic command structure --- generators/Options.cs | 17 ----------------- generators/Program.cs | 20 ++++++++++++++++---- generators/TemplateGenerator.cs | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 21 deletions(-) delete mode 100644 generators/Options.cs create mode 100644 generators/TemplateGenerator.cs diff --git a/generators/Options.cs b/generators/Options.cs deleted file mode 100644 index 3e0362ab2c..0000000000 --- a/generators/Options.cs +++ /dev/null @@ -1,17 +0,0 @@ -using CommandLine; - -namespace Generators; - -[Verb("generate", isDefault: true, HelpText = "Generate the test file's contents using the exercise's generator template file.")] -internal class GenerateOptions -{ - [Option('e', "exercise", Required = false, HelpText = "The exercise (slug) which tests file to generate.")] - public string? Exercise { get; set; } -} - -[Verb("new", HelpText = "Create a new exercise generator template file.")] -internal class NewOptions -{ - [Option('e', "exercise", Required = true, HelpText = "The exercise (slug) for which to generate a generator file.")] - public string Exercise { get; set; } -} diff --git a/generators/Program.cs b/generators/Program.cs index f15dde9c52..97a60a4961 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -22,14 +22,26 @@ private static void HandleGenerateCommand(GenerateOptions options) TestsGenerator.Generate(exercise); } - private static void HandleNewCommand(NewOptions options) - { - throw new NotImplementedException(); - } + private static void HandleNewCommand(NewOptions options) => + TemplateGenerator.Generate(Exercises.TemplatedExercise(options.Exercise)); private static void HandleErrors(IEnumerable errors) { foreach (var error in errors) Console.Error.WriteLine(error.ToString()); } + + [Verb("generate", isDefault: true, HelpText = "Generate the test file's contents using the exercise's generator template file.")] + private class GenerateOptions + { + [Option('e', "exercise", Required = false, HelpText = "The exercise (slug) which tests file to generate.")] + public string? Exercise { get; set; } + } + + [Verb("new", HelpText = "Create a new exercise generator template file.")] + private class NewOptions + { + [Option('e', "exercise", Required = true, HelpText = "The exercise (slug) for which to generate a generator file.")] + public required string Exercise { get; set; } + } } \ No newline at end of file diff --git a/generators/TemplateGenerator.cs b/generators/TemplateGenerator.cs new file mode 100644 index 0000000000..092af5559a --- /dev/null +++ b/generators/TemplateGenerator.cs @@ -0,0 +1,18 @@ +namespace Generators; + +internal static class TemplateGenerator +{ + internal static void Generate(Exercise exercise) + { + Console.WriteLine($"{exercise.Slug}: generating template..."); + + var canonicalData = CanonicalDataParser.Parse(exercise); + var filteredCanonicalData = TestCasesConfiguration.RemoveExcludedTestCases(canonicalData); + + var template = RenderTemplate(filteredCanonicalData); + File.WriteAllText(Paths.TemplateFile(exercise), template); + } + + private static string RenderTemplate(CanonicalData canonicalData) => + "test"; +} \ No newline at end of file From 9cfec17a1bf0c28c503646d929912df97df175ea Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 3 Feb 2025 15:19:41 +0100 Subject: [PATCH 03/15] More generator work --- exercises/practice/leap/.meta/Generator.tpl | 3 +- generators/TemplateGenerator.cs | 69 ++++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/exercises/practice/leap/.meta/Generator.tpl b/exercises/practice/leap/.meta/Generator.tpl index 63d0f7e149..658e6f4c22 100644 --- a/exercises/practice/leap/.meta/Generator.tpl +++ b/exercises/practice/leap/.meta/Generator.tpl @@ -6,7 +6,6 @@ public class {{testClass}} [Fact{{if !for.first}}(Skip = "Remove this Skip property to run this test"){{end}}] public void {{test.testMethod}}() { - Assert.{{test.expected ? "True" : "False"}}({{testedClass}}.Is{{test.testedMethod}}({{test.input.year}})); + Assert.{{test.expected ? "True" : "False"}}({{testedClass}}.{{test.testedMethod}}({{test.input}}); } - {{end}} } diff --git a/generators/TemplateGenerator.cs b/generators/TemplateGenerator.cs index 092af5559a..4acc8eff05 100644 --- a/generators/TemplateGenerator.cs +++ b/generators/TemplateGenerator.cs @@ -1,3 +1,8 @@ +using System.Text.Json; +using System.Text.Json.Nodes; + +using Scriban; + namespace Generators; internal static class TemplateGenerator @@ -13,6 +18,66 @@ internal static void Generate(Exercise exercise) File.WriteAllText(Paths.TemplateFile(exercise), template); } - private static string RenderTemplate(CanonicalData canonicalData) => - "test"; + private static string RenderTemplate(CanonicalData canonicalData) + { + var error = canonicalData.TestCases.Where(ExpectsError).Any(); + var testCase = canonicalData.TestCases.First(testCase => !ExpectsError(testCase)); + + HashSet namespaces = ["Xunit"]; + if (error) namespaces.Add("System"); + + var model = new { namespaces, error, assertion = Assertion(testCase), throwsAssertion = AssertThrows(testCase) }; + + var template = Template.Parse(GeneratorTemplate); + return template.Render(model).Trim() + Environment.NewLine; + } + + private static string Expected(JsonNode testCase) => + testCase.GetValueKind() == JsonValueKind.String + ? "{{test.expected | string.literal}}" + : "{{test.expected}}"; + + private static string Assertion(JsonNode testCase) => + testCase["expected"]!.GetValueKind() switch + { + JsonValueKind.False or JsonValueKind.True => AssertBool(testCase), + _ => AssertEqual(testCase) + }; + + private static string TestedMethodCall(JsonNode testCase) => + "{{testedClass}}.{{test.testedMethod}}({{test.input}}"; + + private static string AssertBool(JsonNode testCase) => + $"Assert.{{{{test.expected ? \"True\" : \"False\"}}}}({TestedMethodCall(testCase)});"; + + private static string AssertEqual(JsonNode testCase) => + $"Assert.Equal({Expected(testCase)}, {TestedMethodCall(testCase)}));"; + + private static string AssertThrows(JsonNode testCase) => + $"Assert.Throws(() => {TestedMethodCall(testCase)}));"; + + private static bool ExpectsError(this JsonNode testCase) => + testCase["expected"] is JsonObject jsonObject && jsonObject.ContainsKey("error"); + + private const string GeneratorTemplate = @" +{{for namespace in namespaces}} +using {{namespace}}; +{{end}} +public class {%{{{testClass}}}%} +{ + {%{{{for test in tests}}}%} + [Fact{%{{{if !for.first}}}%}(Skip = ""Remove this Skip property to run this test""){%{{{end}}}%}] + public void {%{{{test.testMethod}}}%}() + { + {{-if error}} + {%{{{if test.expected.error}}}%} + {{throwsAssertion}} + {%{{{else}}}%} + {{assertion}} + {%{{{end}}}%} + {{else}} + {{assertion}} + {{-end}} + } +}"; } \ No newline at end of file From 56f7ae2371618709d5dbf8cfeb56d59844eacedc Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 3 Feb 2025 20:02:51 +0100 Subject: [PATCH 04/15] More work --- bin/add-practice-exercise.ps1 | 20 +++----------------- exercises/practice/leap/.meta/Generator.tpl | 2 +- generators/TemplateGenerator.cs | 13 +++++++++---- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/bin/add-practice-exercise.ps1 b/bin/add-practice-exercise.ps1 index 8fbfc780fc..0ea300ec9f 100644 --- a/bin/add-practice-exercise.ps1 +++ b/bin/add-practice-exercise.ps1 @@ -46,23 +46,9 @@ $project = "${exerciseDir}/${ExerciseName}.csproj" Remove-Item -Path "${exerciseDir}/UnitTest1.cs" (Get-Content -Path ".editorconfig") -Replace "\[\*\.cs\]", "[${exerciseName}.cs]" | Set-Content -Path "${exerciseDir}/.editorconfig" -# Add and run generator (this will update the tests file) -$generator = "${exerciseDir}/.meta/Generator.tpl" -Add-Content -Path $generator -Value @" -using Xunit; - -public class {{testClass}} -{ - {{for test in tests}} - [Fact{{if !for.first}}(Skip = "Remove this Skip property to run this test"){{end}}] - public void {{test.testMethod}}() - { - // TODO: implement the test - } - {{end}} -} -"@ -& dotnet run --project generators --exercise $Exercise +# Create new generator template and run generator (this will update the tests file) +& dotnet run --project generators new --exercise $Exercise +& dotnet run --project generators generate --exercise $Exercise # Output the next steps $files = Get-Content "exercises/practice/${Exercise}/.meta/config.json" | ConvertFrom-Json | Select-Object -ExpandProperty files diff --git a/exercises/practice/leap/.meta/Generator.tpl b/exercises/practice/leap/.meta/Generator.tpl index 658e6f4c22..3bf4de4831 100644 --- a/exercises/practice/leap/.meta/Generator.tpl +++ b/exercises/practice/leap/.meta/Generator.tpl @@ -6,6 +6,6 @@ public class {{testClass}} [Fact{{if !for.first}}(Skip = "Remove this Skip property to run this test"){{end}}] public void {{test.testMethod}}() { - Assert.{{test.expected ? "True" : "False"}}({{testedClass}}.{{test.testedMethod}}({{test.input}}); + Assert.{{test.expected ? "True" : "False"}}({{testedClass}}.{{test.testedMethod}}({{test.input.year}})); } } diff --git a/generators/TemplateGenerator.cs b/generators/TemplateGenerator.cs index 4acc8eff05..5fa697279f 100644 --- a/generators/TemplateGenerator.cs +++ b/generators/TemplateGenerator.cs @@ -32,10 +32,12 @@ private static string RenderTemplate(CanonicalData canonicalData) return template.Render(model).Trim() + Environment.NewLine; } - private static string Expected(JsonNode testCase) => + private static string Value(string field, JsonNode testCase) => testCase.GetValueKind() == JsonValueKind.String - ? "{{test.expected | string.literal}}" - : "{{test.expected}}"; + ? $"{{{{{field} | string.literal}}}}" + : $"{{{{{field}}}}}"; + + private static string Expected(JsonNode testCase) => Value("test.expected", testCase); private static string Assertion(JsonNode testCase) => testCase["expected"]!.GetValueKind() switch @@ -43,9 +45,12 @@ private static string Assertion(JsonNode testCase) => JsonValueKind.False or JsonValueKind.True => AssertBool(testCase), _ => AssertEqual(testCase) }; + + private static string TestedMethodArguments(JsonNode testCase) => + string.Join(", ", testCase["input"]!.AsObject().Select(kv => Value($"test.input.{kv.Key}", kv.Value!))); private static string TestedMethodCall(JsonNode testCase) => - "{{testedClass}}.{{test.testedMethod}}({{test.input}}"; + $"{{{{testedClass}}}}.{{{{test.testedMethod}}}}({TestedMethodArguments(testCase)})"; private static string AssertBool(JsonNode testCase) => $"Assert.{{{{test.expected ? \"True\" : \"False\"}}}}({TestedMethodCall(testCase)});"; From c02723f804fb7005489ba77019963790ae5b74d9 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 3 Feb 2025 20:07:23 +0100 Subject: [PATCH 05/15] Refactor --- generators/Exercises.cs | 4 ++-- generators/Program.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/generators/Exercises.cs b/generators/Exercises.cs index 6b4452d408..34b91167e4 100644 --- a/generators/Exercises.cs +++ b/generators/Exercises.cs @@ -6,12 +6,12 @@ internal record Exercise(string Slug, string Name); internal static class Exercises { - internal static Exercise[] TemplatedExercises() => + internal static List TemplatedExercises() => Directory.EnumerateFiles(Paths.PracticeExercisesDir, "Generator.tpl", SearchOption.AllDirectories) .Select(templateFile => Directory.GetParent(templateFile)!.Parent!.Name) .Select(ToExercise) .OrderBy(exercise => exercise.Slug) - .ToArray(); + .ToList(); internal static Exercise TemplatedExercise(string slug) { diff --git a/generators/Program.cs b/generators/Program.cs index 97a60a4961..d2fe785104 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -42,6 +42,6 @@ private class GenerateOptions private class NewOptions { [Option('e', "exercise", Required = true, HelpText = "The exercise (slug) for which to generate a generator file.")] - public required string Exercise { get; set; } + public string? Exercise { get; set; } } } \ No newline at end of file From 005e10d91ae84044d78db0441fbe7a0e3ea4002c Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 3 Feb 2025 21:26:21 +0100 Subject: [PATCH 06/15] Start parsing --- generators/Exercises.cs | 16 ++++++++++++++-- generators/Paths.cs | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/generators/Exercises.cs b/generators/Exercises.cs index 34b91167e4..bc123f88b5 100644 --- a/generators/Exercises.cs +++ b/generators/Exercises.cs @@ -1,8 +1,10 @@ +using System.Text.Json; + using Humanizer; namespace Generators; -internal record Exercise(string Slug, string Name); +internal record Exercise(string Slug, string Name, bool HasTemplate); internal static class Exercises { @@ -26,5 +28,15 @@ internal static Exercise TemplatedExercise(string slug) return exercise; } - private static Exercise ToExercise(string slug) => new(slug, slug.Dehumanize()); + private static Exercise ToExercise(string slug) => new(slug, slug.Dehumanize(), true); + + private static IEnumerable ParseExerciseSlugs() => + ParseConfig() + .GetProperty("exercises") + .GetProperty("practice") + .EnumerateArray() + .Select(exercise => exercise.GetProperty("slug").ToString()); + + private static JsonElement ParseConfig() => + JsonSerializer.Deserialize(File.ReadAllText(Paths.TrackConfigFile)); } \ No newline at end of file diff --git a/generators/Paths.cs b/generators/Paths.cs index e2cbbe7df8..be6933d207 100644 --- a/generators/Paths.cs +++ b/generators/Paths.cs @@ -6,7 +6,8 @@ internal static class Paths internal static readonly string ProbSpecsDir = Path.Join(RootDir, ".problem-specifications"); private static readonly string ProbSpecsExercisesDir = Path.Join(ProbSpecsDir, "exercises"); internal static readonly string PracticeExercisesDir = Path.Join(RootDir, "exercises", "practice"); - + internal static readonly string TrackConfigFile = Path.Join(RootDir, "config.json"); + internal static string ExerciseDir(Exercise exercise) => Path.Join(PracticeExercisesDir, exercise.Slug); internal static string TestsFile(Exercise exercise) => Path.Join(ExerciseDir(exercise), $"{exercise.Name}Tests.cs"); internal static string TestsTomlFile(Exercise exercise) => Path.Join(ExerciseDir(exercise), ".meta", "tests.toml"); From 4fc1bb4eda5b9a9e3cad79f52fc609fdba77146a Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Feb 2025 08:22:31 +0100 Subject: [PATCH 07/15] More work --- generators/Exercises.cs | 40 +++++++++++++++------------------------- generators/Program.cs | 22 +++++----------------- 2 files changed, 20 insertions(+), 42 deletions(-) diff --git a/generators/Exercises.cs b/generators/Exercises.cs index bc123f88b5..bc20b8c47e 100644 --- a/generators/Exercises.cs +++ b/generators/Exercises.cs @@ -4,39 +4,29 @@ namespace Generators; -internal record Exercise(string Slug, string Name, bool HasTemplate); +internal record Exercise(string Slug, string Name); internal static class Exercises { - internal static List TemplatedExercises() => - Directory.EnumerateFiles(Paths.PracticeExercisesDir, "Generator.tpl", SearchOption.AllDirectories) - .Select(templateFile => Directory.GetParent(templateFile)!.Parent!.Name) - .Select(ToExercise) - .OrderBy(exercise => exercise.Slug) - .ToList(); - - internal static Exercise TemplatedExercise(string slug) - { - var exercise = ToExercise(slug); + internal static List Templated(string? slug = null) => Find(slug, hasTemplate: true); - if (!Directory.Exists(Paths.ExerciseDir(exercise))) - throw new ArgumentException($"Could not find exercise '{slug}'."); - - if (!File.Exists(Paths.TemplateFile(exercise))) - throw new ArgumentException($"Could not find template file for exercise '{slug}'."); + internal static List Untemplated(string? slug = null) => Find(slug, hasTemplate: false); - return exercise; - } + private static List Find(string? slug, bool hasTemplate) => + Parse() + .Where(exercise => slug is null || exercise.Slug == slug) + .Where(exercise => hasTemplate == HasTemplate(exercise)) + .ToList(); - private static Exercise ToExercise(string slug) => new(slug, slug.Dehumanize(), true); - - private static IEnumerable ParseExerciseSlugs() => - ParseConfig() + private static IEnumerable Parse() => + JsonSerializer.Deserialize(File.ReadAllText(Paths.TrackConfigFile)) .GetProperty("exercises") .GetProperty("practice") .EnumerateArray() - .Select(exercise => exercise.GetProperty("slug").ToString()); + .Select(exercise => exercise.GetProperty("slug").ToString()) + .Select(ToExercise); + + private static Exercise ToExercise(string slug) => new(slug, slug.Dehumanize()); - private static JsonElement ParseConfig() => - JsonSerializer.Deserialize(File.ReadAllText(Paths.TrackConfigFile)); + private static bool HasTemplate(Exercise exercise) => File.Exists(Paths.TemplateFile(exercise)); } \ No newline at end of file diff --git a/generators/Program.cs b/generators/Program.cs index d2fe785104..4239c59096 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -10,26 +10,14 @@ static void Main(string[] args) => .WithParsed(HandleNewCommand) .WithNotParsed(HandleErrors); - private static void HandleGenerateCommand(GenerateOptions options) - { - if (options.Exercise is not null) - { - TestsGenerator.Generate(Exercises.TemplatedExercise(options.Exercise)); - return; - } - - foreach (var exercise in Exercises.TemplatedExercises()) - TestsGenerator.Generate(exercise); - } + private static void HandleGenerateCommand(GenerateOptions options) => + Exercises.Templated().ForEach(TestsGenerator.Generate); private static void HandleNewCommand(NewOptions options) => - TemplateGenerator.Generate(Exercises.TemplatedExercise(options.Exercise)); + Exercises.Templated().ForEach(TemplateGenerator.Generate); - private static void HandleErrors(IEnumerable errors) - { - foreach (var error in errors) - Console.Error.WriteLine(error.ToString()); - } + private static void HandleErrors(IEnumerable errors) => + errors.ToList().ForEach(Console.Error.WriteLine); [Verb("generate", isDefault: true, HelpText = "Generate the test file's contents using the exercise's generator template file.")] private class GenerateOptions From a4ee94fdd49988b146a00b2ccda92d6b85a3d9b9 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Feb 2025 09:34:36 +0100 Subject: [PATCH 08/15] Not required --- generators/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/Program.cs b/generators/Program.cs index 4239c59096..2bb1346049 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -29,7 +29,7 @@ private class GenerateOptions [Verb("new", HelpText = "Create a new exercise generator template file.")] private class NewOptions { - [Option('e', "exercise", Required = true, HelpText = "The exercise (slug) for which to generate a generator file.")] + [Option('e', "exercise", Required = false, HelpText = "The exercise (slug) for which to generate a generator file.")] public string? Exercise { get; set; } } } \ No newline at end of file From 4173c1212ef703936a4d42d6bd0f1b52428243de Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Feb 2025 09:37:11 +0100 Subject: [PATCH 09/15] Fix null --- generators/TemplateGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generators/TemplateGenerator.cs b/generators/TemplateGenerator.cs index 5fa697279f..1dcccc822c 100644 --- a/generators/TemplateGenerator.cs +++ b/generators/TemplateGenerator.cs @@ -32,8 +32,8 @@ private static string RenderTemplate(CanonicalData canonicalData) return template.Render(model).Trim() + Environment.NewLine; } - private static string Value(string field, JsonNode testCase) => - testCase.GetValueKind() == JsonValueKind.String + private static string Value(string field, JsonNode? testCase) => + testCase is not null && testCase.GetValueKind() == JsonValueKind.String ? $"{{{{{field} | string.literal}}}}" : $"{{{{{field}}}}}"; From 165b40ea1ab98d9e0da85ad63843d3e38a4b970d Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Feb 2025 09:37:57 +0100 Subject: [PATCH 10/15] Sort exercises --- generators/Exercises.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/generators/Exercises.cs b/generators/Exercises.cs index bc20b8c47e..09525b5615 100644 --- a/generators/Exercises.cs +++ b/generators/Exercises.cs @@ -24,6 +24,7 @@ private static IEnumerable Parse() => .GetProperty("practice") .EnumerateArray() .Select(exercise => exercise.GetProperty("slug").ToString()) + .Order() .Select(ToExercise); private static Exercise ToExercise(string slug) => new(slug, slug.Dehumanize()); From 2ee37165187bc53b6a7c30ed594cab20219d0503 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Feb 2025 09:38:23 +0100 Subject: [PATCH 11/15] Use untemplated --- generators/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/Program.cs b/generators/Program.cs index 2bb1346049..a367ff9f30 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -14,7 +14,7 @@ private static void HandleGenerateCommand(GenerateOptions options) => Exercises.Templated().ForEach(TestsGenerator.Generate); private static void HandleNewCommand(NewOptions options) => - Exercises.Templated().ForEach(TemplateGenerator.Generate); + Exercises.Untemplated().ForEach(TemplateGenerator.Generate); private static void HandleErrors(IEnumerable errors) => errors.ToList().ForEach(Console.Error.WriteLine); From ee3d58e1f5e6913811dd5f92a8c3080347c44516 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Feb 2025 09:49:52 +0100 Subject: [PATCH 12/15] Only work on exercises with canonical data --- generators/Exercises.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/generators/Exercises.cs b/generators/Exercises.cs index 09525b5615..7d14b43bb3 100644 --- a/generators/Exercises.cs +++ b/generators/Exercises.cs @@ -15,6 +15,7 @@ internal static class Exercises private static List Find(string? slug, bool hasTemplate) => Parse() .Where(exercise => slug is null || exercise.Slug == slug) + .Where(HasCanonicalData) .Where(exercise => hasTemplate == HasTemplate(exercise)) .ToList(); @@ -29,5 +30,6 @@ private static IEnumerable Parse() => private static Exercise ToExercise(string slug) => new(slug, slug.Dehumanize()); + private static bool HasCanonicalData(Exercise exercise) => File.Exists(Paths.CanonicalDataFile(exercise)); private static bool HasTemplate(Exercise exercise) => File.Exists(Paths.TemplateFile(exercise)); } \ No newline at end of file From b9161c2121138614f8bbf365e5a70b78f2a662dc Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Feb 2025 10:20:00 +0100 Subject: [PATCH 13/15] Fix generating single exercise --- generators/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generators/Program.cs b/generators/Program.cs index a367ff9f30..44f4634a46 100644 --- a/generators/Program.cs +++ b/generators/Program.cs @@ -11,10 +11,10 @@ static void Main(string[] args) => .WithNotParsed(HandleErrors); private static void HandleGenerateCommand(GenerateOptions options) => - Exercises.Templated().ForEach(TestsGenerator.Generate); + Exercises.Templated(options.Exercise).ForEach(TestsGenerator.Generate); private static void HandleNewCommand(NewOptions options) => - Exercises.Untemplated().ForEach(TemplateGenerator.Generate); + Exercises.Untemplated(options.Exercise).ForEach(TemplateGenerator.Generate); private static void HandleErrors(IEnumerable errors) => errors.ToList().ForEach(Console.Error.WriteLine); From 96a64dbd890e66536ce16db7bfc2bf3d70952b5b Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Feb 2025 10:33:41 +0100 Subject: [PATCH 14/15] Some fixes --- generators/TemplateGenerator.cs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/generators/TemplateGenerator.cs b/generators/TemplateGenerator.cs index 1dcccc822c..3ca8fb78ba 100644 --- a/generators/TemplateGenerator.cs +++ b/generators/TemplateGenerator.cs @@ -22,11 +22,7 @@ private static string RenderTemplate(CanonicalData canonicalData) { var error = canonicalData.TestCases.Where(ExpectsError).Any(); var testCase = canonicalData.TestCases.First(testCase => !ExpectsError(testCase)); - - HashSet namespaces = ["Xunit"]; - if (error) namespaces.Add("System"); - - var model = new { namespaces, error, assertion = Assertion(testCase), throwsAssertion = AssertThrows(testCase) }; + var model = new { error, assert = Assertion(testCase), throws = AssertThrows(testCase) }; var template = Template.Parse(GeneratorTemplate); return template.Render(model).Trim() + Environment.NewLine; @@ -37,7 +33,7 @@ private static string Value(string field, JsonNode? testCase) => ? $"{{{{{field} | string.literal}}}}" : $"{{{{{field}}}}}"; - private static string Expected(JsonNode testCase) => Value("test.expected", testCase); + private static string Expected(JsonNode testCase) => Value("test.expected", testCase["expected"]); private static string Assertion(JsonNode testCase) => testCase["expected"]!.GetValueKind() switch @@ -56,18 +52,18 @@ private static string AssertBool(JsonNode testCase) => $"Assert.{{{{test.expected ? \"True\" : \"False\"}}}}({TestedMethodCall(testCase)});"; private static string AssertEqual(JsonNode testCase) => - $"Assert.Equal({Expected(testCase)}, {TestedMethodCall(testCase)}));"; + $"Assert.Equal({Expected(testCase)}, {TestedMethodCall(testCase)});"; private static string AssertThrows(JsonNode testCase) => - $"Assert.Throws(() => {TestedMethodCall(testCase)}));"; + $"Assert.Throws(() => {TestedMethodCall(testCase)});"; private static bool ExpectsError(this JsonNode testCase) => testCase["expected"] is JsonObject jsonObject && jsonObject.ContainsKey("error"); private const string GeneratorTemplate = @" -{{for namespace in namespaces}} -using {{namespace}}; -{{end}} +{{if error}}using System;{{end}} +using Xunit; + public class {%{{{testClass}}}%} { {%{{{for test in tests}}}%} @@ -76,13 +72,14 @@ public class {%{{{testClass}}}%} { {{-if error}} {%{{{if test.expected.error}}}%} - {{throwsAssertion}} + {{throws}} {%{{{else}}}%} - {{assertion}} + {{assert}} {%{{{end}}}%} - {{else}} - {{assertion}} + {{-else}} + {{assert}} {{-end}} } + {%{{{end}}}%} }"; } \ No newline at end of file From 04c3f9c25de3f29d3a816fb7808fc677ebde8d68 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Feb 2025 10:38:02 +0100 Subject: [PATCH 15/15] Fix leap template --- exercises/practice/leap/.meta/Generator.tpl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/leap/.meta/Generator.tpl b/exercises/practice/leap/.meta/Generator.tpl index 3bf4de4831..63d0f7e149 100644 --- a/exercises/practice/leap/.meta/Generator.tpl +++ b/exercises/practice/leap/.meta/Generator.tpl @@ -6,6 +6,7 @@ public class {{testClass}} [Fact{{if !for.first}}(Skip = "Remove this Skip property to run this test"){{end}}] public void {{test.testMethod}}() { - Assert.{{test.expected ? "True" : "False"}}({{testedClass}}.{{test.testedMethod}}({{test.input.year}})); + Assert.{{test.expected ? "True" : "False"}}({{testedClass}}.Is{{test.testedMethod}}({{test.input.year}})); } + {{end}} }