Implemented the .NET generator

- Added .NET generator setting for the namespace to use
- Added .NET generator setting to choose the default culture
- Added get child section method to section processor
- Added util to convert any string to a verbatim string literal
- Removed redundant variables on exceptions
This commit is contained in:
tsommer committed 2022-10-30 15:49:22 +01:00
1 parent 547a22bfd2
commit 630f014c1a
13 files changed
+454 -5

No files matched your search

+148
View File
@@ -685,6 +685,154 @@ public static class AppSettings
#endregion
#region .NET Generator Namespace
private static string CACHE_GENERATOR_DOTNET_NAMESPACE = string.Empty;
private static bool CACHE_GENERATOR_DOTNET_NAMESPACE_IS_LOADED = false;
public static async Task<string> GetGeneratorDotnetNamespace()
{
// When possible, use the cache:
if (CACHE_GENERATOR_DOTNET_NAMESPACE_IS_LOADED)
return CACHE_GENERATOR_DOTNET_NAMESPACE;
var generatorDotnetNamespace = "I18N";
try
{
// Get the database:
await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
// Check, if the setting is already set:
if (await db.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.GENERATOR_DOTNET_NAMESPACE) is { } existingSetting)
{
generatorDotnetNamespace = existingSetting.TextValue;
return generatorDotnetNamespace;
}
// Does not exist, so create it:
var setting = new Setting
{
Code = SettingNames.GENERATOR_DOTNET_NAMESPACE,
TextValue = generatorDotnetNamespace,
};
await db.Settings.AddAsync(setting);
await db.SaveChangesAsync();
return generatorDotnetNamespace;
}
finally
{
CACHE_GENERATOR_DOTNET_NAMESPACE_IS_LOADED = true;
CACHE_GENERATOR_DOTNET_NAMESPACE = generatorDotnetNamespace;
}
}
public static async Task SetGeneratorDotnetNamespace(string updatedNamespace)
{
// Update the cache:
CACHE_GENERATOR_DOTNET_NAMESPACE = updatedNamespace;
CACHE_GENERATOR_DOTNET_NAMESPACE_IS_LOADED = true;
// Get the database:
await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
// Check, if the setting is already set:
if (await db.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.GENERATOR_DOTNET_NAMESPACE) is { } existingSetting)
{
existingSetting.TextValue = updatedNamespace;
await db.SaveChangesAsync();
}
// Does not exist, so create it:
else
{
var setting = new Setting
{
Code = SettingNames.GENERATOR_DOTNET_NAMESPACE,
TextValue = updatedNamespace,
};
await db.Settings.AddAsync(setting);
await db.SaveChangesAsync();
}
}
#endregion
#region .NET Generator Default Culture
private static int CACHE_GENERATOR_DOTNET_DEFAULT_CULTURE = -1;
private static bool CACHE_GENERATOR_DOTNET_DEFAULT_CULTURE_IS_LOADED = false;
public static async Task<int> GetGeneratorDotnetDefaultCultureIndex()
{
// When possible, use the cache:
if (CACHE_GENERATOR_DOTNET_DEFAULT_CULTURE_IS_LOADED)
return CACHE_GENERATOR_DOTNET_DEFAULT_CULTURE;
var generatorDotnetDefaultCulture = 0;
try
{
// Get the database:
await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
// Check, if the setting is already set:
if (await db.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.GENERATOR_DOTNET_DEFAULT_CULTURE) is { } existingSetting)
{
generatorDotnetDefaultCulture = existingSetting.IntegerValue;
return generatorDotnetDefaultCulture;
}
// Does not exist, so create it:
var setting = new Setting
{
Code = SettingNames.GENERATOR_DOTNET_DEFAULT_CULTURE,
IntegerValue = generatorDotnetDefaultCulture,
};
await db.Settings.AddAsync(setting);
await db.SaveChangesAsync();
return generatorDotnetDefaultCulture;
}
finally
{
CACHE_GENERATOR_DOTNET_DEFAULT_CULTURE_IS_LOADED = true;
CACHE_GENERATOR_DOTNET_DEFAULT_CULTURE = generatorDotnetDefaultCulture;
}
}
public static async Task SetGeneratorDotnetDefaultCultureIndex(int updatedCulture)
{
// Update the cache:
CACHE_GENERATOR_DOTNET_DEFAULT_CULTURE = updatedCulture;
CACHE_GENERATOR_DOTNET_DEFAULT_CULTURE_IS_LOADED = true;
// Get the database:
await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
// Check, if the setting is already set:
if (await db.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.GENERATOR_DOTNET_DEFAULT_CULTURE) is { } existingSetting)
{
existingSetting.IntegerValue = updatedCulture;
await db.SaveChangesAsync();
}
// Does not exist, so create it:
else
{
var setting = new Setting
{
Code = SettingNames.GENERATOR_DOTNET_DEFAULT_CULTURE,
IntegerValue = updatedCulture,
};
await db.Settings.AddAsync(setting);
await db.SaveChangesAsync();
}
}
#endregion
#region Godot Generator Enabled/Disabled
private static bool CACHE_GENERATOR_GODOT_ENABLED = true;
+2 -2
View File
@@ -28,7 +28,7 @@ public static class DeepL
return new DeepLUsage(true, usage.Character!.Count, usage.Character.Limit);
}
catch (AuthorizationException e)
catch (AuthorizationException)
{
DEEPL_NOT_AVAILABLE = true;
return new DeepLUsage(false, 0, 1, true);
@@ -63,7 +63,7 @@ public static class DeepL
return translation.Text;
}
catch (AuthorizationException e)
catch (AuthorizationException)
{
DEEPL_NOT_AVAILABLE = true;
return string.Empty;
@@ -0,0 +1,141 @@
using System.Text;
using DataModel.Database;
namespace Processor.Generators;
public class DotnetBigFile : IGenerator
{
private static readonly List<string> CULTURE_CODES = new();
private static int DEFAULT_CULTURE_INDEX = -1;
public async Task GenerateAsync()
{
const string filename = "I18N.cs";
var destPath = await AppSettings.GetGeneratorDotnetDestinationPath();
destPath = Environment.ExpandEnvironmentVariables(destPath);
var pathFinal = Path.Join(destPath, filename);
var pathTemp = Path.Join(destPath, filename + ".gen");
if(File.Exists(pathTemp))
File.Delete(pathTemp);
CULTURE_CODES.Clear();
var cultures = await AppSettings.GetCultureInfos();
foreach (var (code, _) in cultures)
CULTURE_CODES.Add(code);
DEFAULT_CULTURE_INDEX = await AppSettings.GetGeneratorDotnetDefaultCultureIndex();
DEFAULT_CULTURE_INDEX -= 1; // 1-based to 0-based
try
{
await using var fileStream = new FileStream(pathTemp, FileMode.CreateNew, FileAccess.Write, FileShare.None);
await using var writer = new StreamWriter(fileStream, Encoding.UTF8);
await writer.WriteLineAsync($"namespace {await AppSettings.GetGeneratorDotnetNamespace()};");
await this.CreateStaticClass(writer, "I18N", 0, async (streamWriter, indention) =>
{
var indentionString = this.AddIndention(indention);
var buildTime = DateTime.UtcNow;
await writer.WriteLineAsync($"{indentionString}public static readonly string BUILD_TIME = \"{buildTime:yyyy.MM.dd HH:mm:ss}\";");
await writer.WriteLineAsync($"{indentionString}public static readonly long BUILD_TIME_TICKS = {buildTime.Ticks};");
await writer.WriteLineAsync();
await writer.WriteLineAsync($"{indentionString}private static int PREVIOUS_CULTURE = -1;");
// Go through the first layer of sections:
var sections = await SectionProcessor.LoadLayer(0);
foreach (var section in sections)
await this.TransformSection(writer, indention, section);
});
}
finally
{
if(new FileInfo(pathTemp).Length > 0)
{
if(File.Exists(pathFinal))
File.Delete(pathFinal);
File.Move(pathTemp, pathFinal);
}
}
}
private string AddIndention(int indention) => new string(' ', indention * 3);
private async Task TransformSection(TextWriter writer, int indention, Section section)
{
await this.CreateStaticClass(writer, section.DataKey, indention, async (_, innerIndention) =>
{
var textElements = section.TextElements;
foreach (var textElement in textElements)
await this.TransformTextElement(writer, innerIndention, textElement);
var childSections = await SectionProcessor.GetChildSections(section.DataKey);
foreach (var childSection in childSections)
await this.TransformSection(writer, innerIndention, childSection);
});
}
private async Task TransformTextElement(TextWriter writer, int indention, TextElement textElement)
{
var indentionString = this.AddIndention(indention);
var indentionPropString = this.AddIndention(indention + 1);
var indentionPropInner1String = this.AddIndention(indention + 2);
var indentionPropInner2String = this.AddIndention(indention + 3);
var indentionPropInner3String = this.AddIndention(indention + 4);
await writer.WriteLineAsync($"{indentionString}private static string E_{textElement.Code}_CACHE = \"\";");
await writer.WriteLineAsync($"{indentionString}public static string E_{textElement.Code}");
await writer.WriteLineAsync($"{indentionString}{{");
await writer.WriteLineAsync($"{indentionPropString}get");
await writer.WriteLineAsync($"{indentionPropString}{{");
await writer.WriteLineAsync($"{indentionPropInner1String}var currentCulture = CultureInfo.CurrentCulture.Name;");
await writer.WriteLineAsync($"{indentionPropInner1String}if(PREVIOUS_CULTURE == currentCulture.GetHashCode())");
await writer.WriteLineAsync($"{indentionPropInner2String}return E_{textElement.Code}_CACHE;");
await writer.WriteLineAsync($"{indentionPropInner1String}else");
await writer.WriteLineAsync($"{indentionPropInner1String}{{");
await writer.WriteLineAsync($"{indentionPropInner2String}PREVIOUS_CULTURE = currentCulture.GetHashCode();");
for (var cultureIndex = 0; cultureIndex < CULTURE_CODES.Count; cultureIndex++)
{
if(cultureIndex == 0)
await writer.WriteLineAsync($"{indentionPropInner2String}if (currentCulture.StartsWith(\"{CULTURE_CODES[cultureIndex]}\", StringComparison.InvariantCultureIgnoreCase))");
else
await writer.WriteLineAsync($"{indentionPropInner2String}else if (currentCulture.StartsWith(\"{CULTURE_CODES[cultureIndex]}\", StringComparison.InvariantCultureIgnoreCase))");
await writer.WriteLineAsync($"{indentionPropInner2String}{{");
var cultureTranslation = textElement.Translations.FirstOrDefault(x => x.Culture == CULTURE_CODES[cultureIndex]);
var cultureText = cultureTranslation?.Text ?? string.Empty;
await writer.WriteLineAsync($"{indentionPropInner3String}var text = @\"{Utils.MadeVerbatimStringLiteral(cultureText)}\";");
await writer.WriteLineAsync($"{indentionPropInner3String}E_{textElement.Code}_CACHE = text;");
await writer.WriteLineAsync($"{indentionPropInner3String}return text;");
await writer.WriteLineAsync($"{indentionPropInner2String}}}");
}
// Add the default case:
await writer.WriteLineAsync($"{indentionPropInner2String}else");
await writer.WriteLineAsync($"{indentionPropInner2String}{{");
var defaultCultureTranslation = textElement.Translations.FirstOrDefault(x => x.Culture == CULTURE_CODES[DEFAULT_CULTURE_INDEX]);
var defaultCultureText = defaultCultureTranslation?.Text ?? string.Empty;
await writer.WriteLineAsync($"{indentionPropInner3String}var text = @\"{Utils.MadeVerbatimStringLiteral(defaultCultureText)}\";");
await writer.WriteLineAsync($"{indentionPropInner3String}E_{textElement.Code}_CACHE = text;");
await writer.WriteLineAsync($"{indentionPropInner3String}return text;");
await writer.WriteLineAsync($"{indentionPropInner2String}}}");
await writer.WriteLineAsync($"{indentionPropInner1String}}}");
await writer.WriteLineAsync($"{indentionPropString}}}");
await writer.WriteLineAsync($"{indentionString}}}");
await writer.WriteLineAsync();
}
private async Task CreateStaticClass(TextWriter writer, string name, int indention, Func<TextWriter, int, Task> content)
{
var indentionString = this.AddIndention(indention);
await writer.WriteLineAsync(indentionString);
await writer.WriteLineAsync($"{indentionString}public static class {name}");
await writer.WriteLineAsync($"{indentionString}{{");
await content(writer, indention + 1);
await writer.WriteLineAsync($"{indentionString}}}");
}
}
@@ -0,0 +1,26 @@
namespace Processor.Generators;
public static class Generator
{
private static readonly Dictionary<Type, IGenerator> GENERATORS = new();
private static readonly IGenerator VOID_GENERATOR = new VoidGenerator();
public static IGenerator Get(Type genType) => genType switch
{
Type.DOTNET => GENERATORS.ContainsKey(genType) ? GENERATORS[genType] : GENERATORS[genType] = new DotnetBigFile(),
_ => VOID_GENERATOR,
};
public static async Task TriggerAllAsync()
{
var dotnetEnabled = await AppSettings.GetGeneratorDotnetEnabled();
var godotEnabled = await AppSettings.GetGeneratorGodotEnabled();
if (dotnetEnabled)
await Generator.Get(Type.DOTNET).GenerateAsync();
if(godotEnabled)
await Generator.Get(Type.GODOT).GenerateAsync();
}
}
@@ -0,0 +1,6 @@
namespace Processor.Generators;
public interface IGenerator
{
public Task GenerateAsync();
}
@@ -0,0 +1,9 @@
namespace Processor.Generators;
public enum Type
{
NONE,
DOTNET,
GODOT,
}
@@ -0,0 +1,6 @@
namespace Processor.Generators;
public class VoidGenerator : IGenerator
{
public Task GenerateAsync() => Task.CompletedTask;
}
@@ -198,4 +198,12 @@ public static class SectionProcessor
return $"Section's path: {path}";
}
public static async Task<List<Section>> GetChildSections(string sectionKey)
{
await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
var section = await db.Sections.FirstAsync(n => n.DataKey == sectionKey);
return await db.Sections.Where(n => n.Parent == section).ToListAsync();
}
}
@@ -124,7 +124,7 @@ public static class TextElementProcessor
// Save the changes:
await db.SaveChangesAsync();
}
catch (DbUpdateException updateException)
catch (DbUpdateException)
{
}
}
+22
View File
@@ -1,4 +1,5 @@
using System.Linq.Expressions;
using System.Text;
using Microsoft.EntityFrameworkCore;
namespace Processor;
@@ -46,4 +47,25 @@ internal static class Utils
return code;
}
public static string MadeVerbatimStringLiteral(string text)
{
IEnumerable<char> ConvertAll(string source)
{
foreach(var c in source)
if(c == '"')
{
yield return '"';
yield return '"';
}
else
yield return c;
}
var sb = new StringBuilder();
foreach(var c in ConvertAll(text))
sb.Append(c);
return sb.ToString();
}
}