From 6e293408ce2f4a13e03ff46e3a102fbd464a5b6c Mon Sep 17 00:00:00 2001 From: Emotion Date: Tue, 11 Jul 2023 20:54:17 +1200 Subject: [PATCH] Added Ellie.Bot.Generators.Strings --- Ellie.sln | 8 + .../Ellie.Bot.Generators.Strings.csproj | 30 ++++ .../LocalizedStringsGenerator.cs | 140 ++++++++++++++++++ src/Ellie.Bot.Generators.Strings/README.md | 24 +++ 4 files changed, 202 insertions(+) create mode 100644 src/Ellie.Bot.Generators.Strings/Ellie.Bot.Generators.Strings.csproj create mode 100644 src/Ellie.Bot.Generators.Strings/LocalizedStringsGenerator.cs create mode 100644 src/Ellie.Bot.Generators.Strings/README.md diff --git a/Ellie.sln b/Ellie.sln index 5c73b79..88d2626 100644 --- a/Ellie.sln +++ b/Ellie.sln @@ -5,6 +5,14 @@ VisualStudioVersion = 17.6.33815.320 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0B2F1537-4BF0-422B-A0DD-8F9CCEFB340F}" +ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md + LICENSE.md = LICENSE.md + README.md = README.md + Dockerfile = Dockerfile + NuGet.Config = NuGet.Config +EndProjectSection Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie", "src\Ellie\Ellie.csproj", "{2BAF005E-781D-45FF-B218-E6361F5E8CD4}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ayu", "ayu", "{5284415D-A43F-4539-9483-410124199743}" diff --git a/src/Ellie.Bot.Generators.Strings/Ellie.Bot.Generators.Strings.csproj b/src/Ellie.Bot.Generators.Strings/Ellie.Bot.Generators.Strings.csproj new file mode 100644 index 0000000..a1d67da --- /dev/null +++ b/src/Ellie.Bot.Generators.Strings/Ellie.Bot.Generators.Strings.csproj @@ -0,0 +1,30 @@ + + + + netstandard2.0 + latest + false + true + true + enable + enable + Ellie.Generators + + + + + + + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + diff --git a/src/Ellie.Bot.Generators.Strings/LocalizedStringsGenerator.cs b/src/Ellie.Bot.Generators.Strings/LocalizedStringsGenerator.cs new file mode 100644 index 0000000..0bf2875 --- /dev/null +++ b/src/Ellie.Bot.Generators.Strings/LocalizedStringsGenerator.cs @@ -0,0 +1,140 @@ +#nullable enable +using System.CodeDom.Compiler; +using System.Diagnostics; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Newtonsoft.Json; + +namespace Ellie.Generators +{ + internal readonly struct TranslationPair + { + public string Name { get; } + public string Value { get; } + + public TranslationPair(string name, string value) + { + Name = name; + Value = value; + } + } + + [Generator] + public class LocalizedStringsGenerator : ISourceGenerator + { + // private const string LOC_STR_SOURCE = @"namespace Ellie + // { + // public readonly struct LocStr + // { + // public readonly string Key; + // public readonly object[] Params; + // + // public LocStr(string key, params object[] data) + // { + // Key = key; + // Params = data; + // } + // } + // }"; + + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + var file = context.AdditionalFiles.First(x => x.Path.EndsWith("responses.en-US.json")); + + var fields = GetFields(file.GetText()?.ToString()); + + using (var stringWriter = new StringWriter()) + using (var sw = new IndentedTextWriter(stringWriter)) + { + sw.WriteLine("#pragma warning disable CS8981"); + sw.WriteLine("namespace Ellie;"); + sw.WriteLine(); + + sw.WriteLine("public static class strs"); + sw.WriteLine("{"); + sw.Indent++; + + var typedParamStrings = new List(10); + foreach (var field in fields) + { + var matches = Regex.Matches(field.Value, @"{(?\d)[}:]"); + var max = 0; + foreach (Match match in matches) + { + max = Math.Max(max, int.Parse(match.Groups["num"].Value) + 1); + } + + typedParamStrings.Clear(); + var typeParams = new string[max]; + var passedParamString = string.Empty; + for (var i = 0; i < max; i++) + { + typedParamStrings.Add($"in T{i} p{i}"); + passedParamString += $", p{i}"; + typeParams[i] = $"T{i}"; + } + + var sig = string.Empty; + var typeParamStr = string.Empty; + if (max > 0) + { + sig = $"({string.Join(", ", typedParamStrings)})"; + typeParamStr = $"<{string.Join(", ", typeParams)}>"; + } + + sw.WriteLine("public static LocStr {0}{1}{2} => new LocStr(\"{3}\"{4});", + field.Name, + typeParamStr, + sig, + field.Name, + passedParamString); + } + + sw.Indent--; + sw.WriteLine("}"); + + + sw.Flush(); + context.AddSource("strs.g.cs", stringWriter.ToString()); + } + + // context.AddSource("LocStr.g.cs", LOC_STR_SOURCE); + } + + private List GetFields(string? dataText) + { + if (string.IsNullOrWhiteSpace(dataText)) + return new(); + + Dictionary data; + try + { + var output = JsonConvert.DeserializeObject>(dataText!); + if (output is null) + return new(); + + data = output; + } + catch + { + Debug.WriteLine("Failed parsing responses file."); + return new(); + } + + var list = new List(); + foreach (var entry in data) + { + list.Add(new( + entry.Key, + entry.Value + )); + } + + return list; + } + } +} diff --git a/src/Ellie.Bot.Generators.Strings/README.md b/src/Ellie.Bot.Generators.Strings/README.md new file mode 100644 index 0000000..b17dd47 --- /dev/null +++ b/src/Ellie.Bot.Generators.Strings/README.md @@ -0,0 +1,24 @@ +## Generators + +Project which contains source generators required for Ellie project + +--- +### 1) Localized Strings Generator + + -- Why -- + Type safe response strings access, and enforces correct usage of response strings. + + -- How it works -- + Creates a file "strs.cs" containing a class called "strs" in "Ellie" namespace. + + Loads "data/strings/responses.en-US.json" and creates a property or a function for each key in the responses json file based on whether the value has string format placeholders or not. + + - If a value has no placeholders, it creates a property in the strs class which returns an instance of a LocStr struct containing only the key and no replacement parameters + + - If a value has placeholders, it creates a function with the same number of arguments as the number of placeholders, and passes those arguments to the LocStr instance + + -- How to use -- + 1. Add a new key to responses.en-US.json "greet_me": "Hello, {0}" + 2. You now have access to a function strs.greet_me(obj p1) + 3. Using "GetText(strs.greet_me("Me"))" will return "Hello, Me" +