From 34d0f664660a5c4b5e3338da7ca03420c44923aa Mon Sep 17 00:00:00 2001 From: Kwoth Date: Fri, 23 Jul 2021 19:01:26 +0200 Subject: [PATCH] - Added NadekoBot.Generators projects which will contain source generators - Implemented initial version of the response strings source generator - Creates a class with property names equivalent to key names in responses.en-US.json - Each Property has struct type (with generic type parameters matching the number of string format placeholders) for type safe GetText implementation - Struct types are readonly refs as they should be ephermal, and only used to pass string keys to GetText --- NadekoBot.sln | 9 + .../LocalizedStringsGenerator.cs | 159 ++++++++++++++++++ .../NadekoBot.Generators.csproj | 23 +++ src/NadekoBot/Common/NadekoModule.cs | 3 + src/NadekoBot/Modules/Games/Games.cs | 2 +- src/NadekoBot/Modules/Searches/Searches.cs | 2 +- .../Modules/Utility/RepeatCommands.cs | 8 +- src/NadekoBot/Modules/Utility/Utility.cs | 2 +- src/NadekoBot/NadekoBot.csproj | 4 + .../strings/responses/responses.en-US.json | 12 +- 10 files changed, 211 insertions(+), 13 deletions(-) create mode 100644 src/NadekoBot.Generators/LocalizedStringsGenerator.cs create mode 100644 src/NadekoBot.Generators/NadekoBot.Generators.csproj diff --git a/NadekoBot.sln b/NadekoBot.sln index 5ef1e8922..301a5e351 100644 --- a/NadekoBot.sln +++ b/NadekoBot.sln @@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Tests", "src\Nade EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Coordinator", "src\NadekoBot.Coordinator\NadekoBot.Coordinator.csproj", "{AE9B7F8C-81D7-4401-83A3-643B38258374}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Generators", "src\NadekoBot.Generators\NadekoBot.Generators.csproj", "{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -54,6 +56,12 @@ Global {AE9B7F8C-81D7-4401-83A3-643B38258374}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU {AE9B7F8C-81D7-4401-83A3-643B38258374}.Release|Any CPU.ActiveCfg = Release|Any CPU {AE9B7F8C-81D7-4401-83A3-643B38258374}.Release|Any CPU.Build.0 = Release|Any CPU + {3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU + {3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU + {3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -64,6 +72,7 @@ Global {2F4CF6D6-0C2F-4944-B204-9508CDA53195} = {6058FEDF-A318-4CD4-8F04-A7E8E7EC8874} {DB448DD4-C97F-40E9-8BD3-F605FF1FF833} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {AE9B7F8C-81D7-4401-83A3-643B38258374} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} + {3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4} diff --git a/src/NadekoBot.Generators/LocalizedStringsGenerator.cs b/src/NadekoBot.Generators/LocalizedStringsGenerator.cs new file mode 100644 index 000000000..117642d90 --- /dev/null +++ b/src/NadekoBot.Generators/LocalizedStringsGenerator.cs @@ -0,0 +1,159 @@ +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Newtonsoft.Json; + +namespace NadekoBot.Generators +{ + internal class FieldData + { + public string Type { get; set; } + public string Name { get; set; } + } + + [Generator] + public class LocalizedStringsGenerator : ISourceGenerator + { + private const string LocStrSource = @"namespace NadekoBot +{ + public readonly ref struct LocStr + { + public readonly string Key; + + public LocStr(string key) + { + Key = key; + } + + public static implicit operator LocStr(string data) + => new LocStr(data); + } + + public readonly ref struct LocStr + { + public readonly string Key; + + public LocStr(string key) + { + Key = key; + } + + public static implicit operator LocStr(string data) + => new LocStr(data); + } + + public readonly ref struct LocStr + { + public readonly string Key; + + public LocStr(string key) + { + Key = key; + } + + public static implicit operator LocStr(string data) + => new LocStr(data); + } + + public readonly ref struct LocStr + { + public readonly string Key; + + public LocStr(string key) + { + Key = key; + } + + public static implicit operator LocStr(string data) + => new LocStr(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("namespace NadekoBot"); + sw.WriteLine("{"); + sw.Indent++; + + sw.WriteLine("public static class Strs"); + sw.WriteLine("{"); + sw.Indent++; + + foreach (var field in fields) + { + sw.WriteLine($"public static {field.Type} {field.Name} => \"{field.Name}\";"); + } + + sw.Indent--; + sw.WriteLine("}"); + sw.Indent--; + sw.WriteLine("}"); + + + sw.Flush(); + context.AddSource("Strs.cs", stringWriter.ToString()); + } + + context.AddSource("LocStr.cs", LocStrSource); + } + + private List GetFields(string dataText) + { + if (string.IsNullOrWhiteSpace(dataText)) + throw new ArgumentNullException(nameof(dataText)); + + var data = JsonConvert.DeserializeObject>(dataText); + + var list = new List(); + foreach (var entry in data) + { + list.Add(new FieldData() + { + Type = GetFieldType(entry.Value), + Name = entry.Key, + }); + } + + return list; + } + + private string GetFieldType(string value) + { + var matches = Regex.Matches(value, @"{(?\d)}"); + int max = 0; + foreach (Match match in matches) + { + max = Math.Max(max, int.Parse(match.Groups["num"].Value)); + } + + if (max == 0) + return "LocStr"; + if (max == 1) + return "LocStr"; + if (max == 2) + return "LocStr"; + if (max == 3) + return "LocStr"; + + return "!Error"; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot.Generators/NadekoBot.Generators.csproj b/src/NadekoBot.Generators/NadekoBot.Generators.csproj new file mode 100644 index 000000000..9e89c01b8 --- /dev/null +++ b/src/NadekoBot.Generators/NadekoBot.Generators.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0 + false + + + + + + + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + diff --git a/src/NadekoBot/Common/NadekoModule.cs b/src/NadekoBot/Common/NadekoModule.cs index 585104f1a..c4b9b2297 100644 --- a/src/NadekoBot/Common/NadekoModule.cs +++ b/src/NadekoBot/Common/NadekoModule.cs @@ -31,6 +31,9 @@ namespace NadekoBot.Modules protected string GetText(string key) => Strings.GetText(key, _cultureInfo); + + protected string GetText(in LocStr key) => + Strings.GetText(key.Key, _cultureInfo); protected string GetText(string key, params object[] args) => Strings.GetText(key, _cultureInfo, args); diff --git a/src/NadekoBot/Modules/Games/Games.cs b/src/NadekoBot/Modules/Games/Games.cs index c46985056..09d72ad3f 100644 --- a/src/NadekoBot/Modules/Games/Games.cs +++ b/src/NadekoBot/Modules/Games/Games.cs @@ -51,7 +51,7 @@ namespace NadekoBot.Modules.Games await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor() .WithDescription(ctx.User.ToString()) .AddField("❓ " + GetText("question"), question, false) - .AddField("🎱 " + GetText("8ball"), res, false)); + .AddField("🎱 " + GetText("_8ball"), res, false)); } [NadekoCommand, Aliases] diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 3b552f505..4481076f3 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -532,7 +532,7 @@ namespace NadekoBot.Modules.Searches var embed = _eb.Create() .WithDescription(ctx.User.Mention) .AddField(GetText("word"), data.Word, true) - .AddField(GetText("class"), data.WordType, true) + .AddField(GetText("_class"), data.WordType, true) .AddField(GetText("definition"), data.Definition) .WithOkColor(); diff --git a/src/NadekoBot/Modules/Utility/RepeatCommands.cs b/src/NadekoBot/Modules/Utility/RepeatCommands.cs index c8e8057d7..521047f73 100644 --- a/src/NadekoBot/Modules/Utility/RepeatCommands.cs +++ b/src/NadekoBot/Modules/Utility/RepeatCommands.cs @@ -190,13 +190,13 @@ namespace NadekoBot.Modules.Utility string description = ""; if (_service.IsNoRedundant(runner.Repeater.Id)) { - description = Format.Underline(Format.Bold(GetText("no_redundant:"))) + "\n\n"; + description = Format.Underline(Format.Bold(GetText("no_redundant"))) + "\n\n"; } description += $"<#{runner.Repeater.ChannelId}>\n" + - $"`{GetText("interval:")}` {intervalString}\n" + - $"`{GetText("executes_in:")}` {executesInString}\n" + - $"`{GetText("message:")}` {message}"; + $"`{GetText("Comment")}` {intervalString}\n" + + $"`{GetText("executes_in_colon")}` {executesInString}\n" + + $"`{GetText("message_colon")}` {message}"; return description; } diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index 1cd79e1e3..7f50687d7 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -249,7 +249,7 @@ namespace NadekoBot.Modules.Utility .WithAuthor($"NadekoBot v{StatsService.BotVersion}", "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/avatar.png", "https://nadekobot.readthedocs.io/en/latest/") - .AddField(GetText("author"), _stats.Author, true) + .AddField(GetText(Strs.author), _stats.Author, true) .AddField(GetText("botid"), _client.CurrentUser.Id.ToString(), true) .AddField(GetText("shard"), $"#{_client.ShardId} / {_creds.TotalShards}", true) .AddField(GetText("commands_ran"), _stats.CommandsRan.ToString(), true) diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index 2b67004a1..caa89c61d 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -58,8 +58,12 @@ + + + + Protos\coordinator.proto diff --git a/src/NadekoBot/data/strings/responses/responses.en-US.json b/src/NadekoBot/data/strings/responses/responses.en-US.json index 10903879a..5208772a1 100644 --- a/src/NadekoBot/data/strings/responses/responses.en-US.json +++ b/src/NadekoBot/data/strings/responses/responses.en-US.json @@ -323,7 +323,7 @@ "waifu_reset_fail": "Failed resetting waifu stats. Make sure you have enough currency.", "waifu_reset_confirm": "This will reset your waifu stats", "waifu_reset_price": "Price: {0}", - "8ball": "8ball", + "_8ball": "8ball", "acrophobia": "Acrophobia", "acro_ended_no_sub": "Game ended with no submissions.", "acro_no_votes_cast": "No votes cast. Game ended with no winner.", @@ -508,7 +508,7 @@ "cost": "Cost", "date": "Date", "word": "Word", - "class": "Class", + "_class": "Class", "definition": "Definition", "example": "Example", "dropped": "Dropped", @@ -629,10 +629,10 @@ "repeater_removed": "Repeater #{0} Removed", "repeater_exceed_limit": "You cannot have more than {0} repeaters per server.", "repeater_remove_fail": "Failed removing repeater on that index. Either you've specified invalid index, or repeater was in executing state at that time, in which case, try again in a few seconds.", - "interval:": "Interval:", - "executes_in:": "Executes in:", - "message:": "Message:", - "no_redundant:": "Won't post duplicate message.", + "interval_colon": "Interval:", + "executes_in_colon": "Executes in:", + "message_colon": "Message:", + "no_redundant": "Won't post duplicate message.", "name": "Name", "nickname": "Nickname", "nobody_playing_game": "Nobody is playing that game.",