From f8943f679e0c0c2d43c8c0ffc5e0ff3385236ee7 Mon Sep 17 00:00:00 2001 From: Anna Sas Date: Fri, 10 Jan 2025 17:37:25 +0100 Subject: [PATCH] Feat: Foundation for string builder approach --- CodeOfChaos.Ansi.sln | 7 + README.md | 1 + .../AnsiStringBuilderGenerator.cs | 148 ++++++++++++++++++ src/CodeOfChaos.Ansi.Generators/Class1.cs | 4 - .../CodeOfChaos.Ansi.Generators.csproj | 2 +- .../Properties/launchSettings.json | 9 ++ .../Xml/ColorEntryContainer.cs | 60 +++++++ src/CodeOfChaos.Ansi/AnsiStringBuilder.cs | 29 ---- src/CodeOfChaos.Ansi/Assets/ASB.CssColors.xml | 6 + .../Builders/AnsiBackgroundBuilder.cs | 14 ++ .../Builders/AnsiForegroundBuilder.cs | 14 ++ .../Builders/AnsiStringBuilder.cs | 46 ++++++ src/CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj | 8 +- .../CodeOfChaos.Ansi.csproj.DotSettings | 2 + .../Example.CodeOfChaos.Ansi.csproj | 14 ++ src/Example.CodeOfChaos.Ansi/Program.cs | 28 ++++ .../Tests.CodeOfChaos.Ansi.csproj | 2 +- 17 files changed, 358 insertions(+), 36 deletions(-) create mode 100644 src/CodeOfChaos.Ansi.Generators/AnsiStringBuilderGenerator.cs delete mode 100644 src/CodeOfChaos.Ansi.Generators/Class1.cs create mode 100644 src/CodeOfChaos.Ansi.Generators/Properties/launchSettings.json create mode 100644 src/CodeOfChaos.Ansi.Generators/Xml/ColorEntryContainer.cs delete mode 100644 src/CodeOfChaos.Ansi/AnsiStringBuilder.cs create mode 100644 src/CodeOfChaos.Ansi/Assets/ASB.CssColors.xml create mode 100644 src/CodeOfChaos.Ansi/Builders/AnsiBackgroundBuilder.cs create mode 100644 src/CodeOfChaos.Ansi/Builders/AnsiForegroundBuilder.cs create mode 100644 src/CodeOfChaos.Ansi/Builders/AnsiStringBuilder.cs create mode 100644 src/CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj.DotSettings create mode 100644 src/Example.CodeOfChaos.Ansi/Example.CodeOfChaos.Ansi.csproj create mode 100644 src/Example.CodeOfChaos.Ansi/Program.cs diff --git a/CodeOfChaos.Ansi.sln b/CodeOfChaos.Ansi.sln index a63ffde..a8e3f69 100644 --- a/CodeOfChaos.Ansi.sln +++ b/CodeOfChaos.Ansi.sln @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "example", "example", "{A5F3 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeOfChaos.Ansi.Generators", "src\CodeOfChaos.Ansi.Generators\CodeOfChaos.Ansi.Generators.csproj", "{82941FB2-B148-4A12-8DFB-71769B4F4DD8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.CodeOfChaos.Ansi", "src\Example.CodeOfChaos.Ansi\Example.CodeOfChaos.Ansi.csproj", "{F8867201-2A48-4DDE-BB19-5267F09D7DE2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,11 +40,16 @@ Global {82941FB2-B148-4A12-8DFB-71769B4F4DD8}.Debug|Any CPU.Build.0 = Debug|Any CPU {82941FB2-B148-4A12-8DFB-71769B4F4DD8}.Release|Any CPU.ActiveCfg = Release|Any CPU {82941FB2-B148-4A12-8DFB-71769B4F4DD8}.Release|Any CPU.Build.0 = Release|Any CPU + {F8867201-2A48-4DDE-BB19-5267F09D7DE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8867201-2A48-4DDE-BB19-5267F09D7DE2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8867201-2A48-4DDE-BB19-5267F09D7DE2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8867201-2A48-4DDE-BB19-5267F09D7DE2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {26284571-0E09-4BAF-8C2B-DF87DCC1BA0B} = {8DD280D4-1E14-4D5E-AFE6-58DD8F079DCC} {64B26DED-68C3-47FF-B409-1C8FAD4F9176} = {197E72AD-DEAB-4350-AFC3-A3BB38720BF5} {ADEADD97-0AFA-4D9E-970B-9FFB932949B3} = {AF1A203C-6EF1-440E-BB3C-55B1DBFE9C19} {82941FB2-B148-4A12-8DFB-71769B4F4DD8} = {197E72AD-DEAB-4350-AFC3-A3BB38720BF5} + {F8867201-2A48-4DDE-BB19-5267F09D7DE2} = {A5F3433A-9F39-4372-B994-E262F597F1D2} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index e69de29..f972ad6 100644 --- a/README.md +++ b/README.md @@ -0,0 +1 @@ +# CodeOfChaos.Ansi \ No newline at end of file diff --git a/src/CodeOfChaos.Ansi.Generators/AnsiStringBuilderGenerator.cs b/src/CodeOfChaos.Ansi.Generators/AnsiStringBuilderGenerator.cs new file mode 100644 index 0000000..b893cb2 --- /dev/null +++ b/src/CodeOfChaos.Ansi.Generators/AnsiStringBuilderGenerator.cs @@ -0,0 +1,148 @@ +// --------------------------------------------------------------------------------------------------------------------- +// Imports +// --------------------------------------------------------------------------------------------------------------------- +using CodeOfChaos.Ansi.Generators.Xml; +using CodeOfChaos.GeneratorTools; +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Serialization; + +namespace CodeOfChaos.Ansi.Generators; +// --------------------------------------------------------------------------------------------------------------------- +// Code +// --------------------------------------------------------------------------------------------------------------------- +[Generator] +public class AnsiStringBuilderGenerator : IIncrementalGenerator { + private static Regex FindFirstColorName { get; } = new(@"ASB\.[^\\\/]*\.xml"); + + // ----------------------------------------------------------------------------------------------------------------- + // Methods + // ----------------------------------------------------------------------------------------------------------------- + public void Initialize(IncrementalGeneratorInitializationContext context) { + IncrementalValueProvider> provider = context.AdditionalTextsProvider + .Where(IsColorFile) + .Collect(); + + context.RegisterSourceOutput(provider, GenerateCode); + } + + private static bool IsColorFile(AdditionalText f) => FindFirstColorName.IsMatch(f.Path); + + private static void GenerateCode(SourceProductionContext context, ImmutableArray files) { + IEnumerable colors = files.SelectMany(file => ParseColorFile(context, file)).ToArray(); + var builder = new GeneratorStringBuilder(); + + #region Fore & Background + foreach (string section in new[] {"Foreground", "Background"}) { + context.AddSource($"Ansi{section}Builder.g.cs", builder + .AppendUsings("System") + .AppendAutoGenerated() + .AppendNamespace("CodeOfChaos.Ansi") + .AppendLine($"public partial class Ansi{section}Builder {{") + .ForEach(colors, (stringBuilder, entry) => stringBuilder + .AppendBodyIndented($$""" + #region {{entry.Name}} + private static CodeOfChaos.Ansi.ByteVector3 _{{entry.Name}} = new({{entry.Colors}}); + + public string {{entry.Name}}(string text) => $"{CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}})}{text}{CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes}"; + + public Ansi{{section}}Builder Append{{entry.Name}}(string text) => BuilderAction(() => { + Builder + .Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}})) + .Append(text) + .Append(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes); + }); + + public Ansi{{section}}Builder Append{{entry.Name}}(Func action) => BuilderAction(() => { + Builder + .Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}})) + .Append(action()) + .Append(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes); + }); + + public Ansi{{section}}Builder Append{{entry.Name}}(Action action) => BuilderAction(() => { + Builder.Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}})); + action(this); + Builder.Append(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes); + }); + + public Ansi{{section}}Builder Append{{entry.Name}}Line(string text) => BuilderAction(() => { + Builder + .Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}})) + .Append(text) + .AppendLine(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes); + }); + + public Ansi{{section}}Builder Append{{entry.Name}}Line(Func action) => BuilderAction(() => { + Builder + .Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}})) + .Append(action()) + .AppendLine(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes); + }); + + public Ansi{{section}}Builder Append{{entry.Name}}Line(Action action) => BuilderAction(() => { + Builder.Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}})); + action(this); + Builder.AppendLine(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes); + }); + #endregion + + """) + .AppendLine() + ) + .AppendLine("}") + .ToStringAndClear() + ); + } + #endregion + } + + private static ColorEntry[] ParseColorFile(SourceProductionContext context, AdditionalText file) { + try { + string? fileContent = file.GetText(context.CancellationToken)?.ToString(); + if (fileContent is null || string.IsNullOrWhiteSpace(fileContent)) return []; + + // Use XmlSerializer to deserialize XML + var serializer = new XmlSerializer(typeof(ColorEntryContainer)); + using var reader = new StringReader(fileContent); + // Deserialize the XML into the container object + var container = (ColorEntryContainer?)serializer.Deserialize(reader); + + // Map the Color attribute (comma-separated values) to the ColorCode int array + return container?.Entries + .Select(entry => entry.ToColorEntry()) + .ToArray() ?? []; + } + catch (InvalidOperationException ex) { + // Report a diagnostic if XML parsing fails + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor( + "ASB01", + "Invalid XML Format", + $"Failed to parse the XML file: {file.Path}. Error: {ex.Message}", + "CodeGeneration", + DiagnosticSeverity.Warning, + true), + Location.None)); + return []; + } + catch (FormatException ex) { + // Handle errors when parsing color values + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor( + "ASB02", + "Invalid Color Formatting", + $"Error while parsing RGB values in file: {file.Path}. Error: {ex.Message}", + "CodeGeneration", + DiagnosticSeverity.Warning, + true), + Location.None)); + return []; + } + } +} diff --git a/src/CodeOfChaos.Ansi.Generators/Class1.cs b/src/CodeOfChaos.Ansi.Generators/Class1.cs deleted file mode 100644 index 630fbb3..0000000 --- a/src/CodeOfChaos.Ansi.Generators/Class1.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace CodeOfChaos.Ansi.Generators; -public class Class1 { - -} diff --git a/src/CodeOfChaos.Ansi.Generators/CodeOfChaos.Ansi.Generators.csproj b/src/CodeOfChaos.Ansi.Generators/CodeOfChaos.Ansi.Generators.csproj index 7b46178..67bad7d 100644 --- a/src/CodeOfChaos.Ansi.Generators/CodeOfChaos.Ansi.Generators.csproj +++ b/src/CodeOfChaos.Ansi.Generators/CodeOfChaos.Ansi.Generators.csproj @@ -41,7 +41,7 @@ - + diff --git a/src/CodeOfChaos.Ansi.Generators/Properties/launchSettings.json b/src/CodeOfChaos.Ansi.Generators/Properties/launchSettings.json new file mode 100644 index 0000000..7c79e51 --- /dev/null +++ b/src/CodeOfChaos.Ansi.Generators/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "DebugRoslynSourceGenerator": { + "commandName": "DebugRoslynComponent", + "targetProject": "../CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj" + } + } +} \ No newline at end of file diff --git a/src/CodeOfChaos.Ansi.Generators/Xml/ColorEntryContainer.cs b/src/CodeOfChaos.Ansi.Generators/Xml/ColorEntryContainer.cs new file mode 100644 index 0000000..8c2127e --- /dev/null +++ b/src/CodeOfChaos.Ansi.Generators/Xml/ColorEntryContainer.cs @@ -0,0 +1,60 @@ +// --------------------------------------------------------------------------------------------------------------------- +// Imports +// --------------------------------------------------------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Serialization; + +namespace CodeOfChaos.Ansi.Generators.Xml; +// --------------------------------------------------------------------------------------------------------------------- +// Code +// --------------------------------------------------------------------------------------------------------------------- +[XmlRoot("ColorEntries")] +public class ColorEntryContainer { + [XmlElement("ColorEntry")] + public List Entries { get; set; } = [];// Default to an empty list +} + +public class XmlColorEntry { + [XmlAttribute("Name")] + public string Name { get; set; } = string.Empty;// Name attribute for the color + + [XmlAttribute("Color")] + public string Color { get; set; } = string.Empty;// RGB values as a comma-separated string + + private readonly Regex _regexCommaSeparated = new(@"^(\d+)[,.;:](\d+)[,.;:](\d+)$", RegexOptions.Compiled); + private readonly Regex _regexHexFormat = new(@"^#?([A-Fa-f0-9]{6})$", RegexOptions.Compiled); + + public ColorEntry ToColorEntry() { + if (_regexCommaSeparated.Match(Color) is {Success : true } match) return new ColorEntry { + Name = Name, + Codes = [ + int.Parse(match.Groups[1].Value), + int.Parse(match.Groups[2].Value), + int.Parse(match.Groups[3].Value) + ] + }; + + if (_regexHexFormat.IsMatch(Color)) { + string hex = Color.TrimStart('#'); + return new ColorEntry { + Name = Name, + Codes = Enumerable.Range(0, 3) + .Select(i => Convert.ToInt32(hex.Substring(i * 2, 2), 16)) + .ToArray() + }; + } + + throw new FormatException("Invalid Color format."); + } +} + +public class ColorEntry { + public string Name { get; set; } = string.Empty;// Mapped Name + + public int[] Codes { get; set; } = [];// Mapped and split RGB values + + public string Colors => string.Join(",", Codes); +} diff --git a/src/CodeOfChaos.Ansi/AnsiStringBuilder.cs b/src/CodeOfChaos.Ansi/AnsiStringBuilder.cs deleted file mode 100644 index c8cf379..0000000 --- a/src/CodeOfChaos.Ansi/AnsiStringBuilder.cs +++ /dev/null @@ -1,29 +0,0 @@ -// --------------------------------------------------------------------------------------------------------------------- -// Imports -// --------------------------------------------------------------------------------------------------------------------- -using System.Text; - -namespace CodeOfChaos.Ansi; - -// --------------------------------------------------------------------------------------------------------------------- -// Code -// --------------------------------------------------------------------------------------------------------------------- -public partial class AnsiStringBuilder { - private StringBuilder _builder = new(); - - // ----------------------------------------------------------------------------------------------------------------- - // Methods - // ----------------------------------------------------------------------------------------------------------------- - private AnsiStringBuilder BuilderAction(Action action) { - action(); - return this; - } - - public void Append(string value) => _builder.Append(value); - public void AppendLine(string value) => _builder.AppendLine(value); - public string ToStringAndClear() { - string result = _builder.ToString(); - _builder.Clear(); - return result; - } -} diff --git a/src/CodeOfChaos.Ansi/Assets/ASB.CssColors.xml b/src/CodeOfChaos.Ansi/Assets/ASB.CssColors.xml new file mode 100644 index 0000000..4cc9f48 --- /dev/null +++ b/src/CodeOfChaos.Ansi/Assets/ASB.CssColors.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/CodeOfChaos.Ansi/Builders/AnsiBackgroundBuilder.cs b/src/CodeOfChaos.Ansi/Builders/AnsiBackgroundBuilder.cs new file mode 100644 index 0000000..1cfc7a0 --- /dev/null +++ b/src/CodeOfChaos.Ansi/Builders/AnsiBackgroundBuilder.cs @@ -0,0 +1,14 @@ +// --------------------------------------------------------------------------------------------------------------------- +// Imports +// --------------------------------------------------------------------------------------------------------------------- +namespace CodeOfChaos.Ansi; + +// --------------------------------------------------------------------------------------------------------------------- +// Code +// --------------------------------------------------------------------------------------------------------------------- +public partial class AnsiBackgroundBuilder : AnsiStringBuilder { + private AnsiBackgroundBuilder BuilderAction(Action action) { + action(); + return this; + } +} diff --git a/src/CodeOfChaos.Ansi/Builders/AnsiForegroundBuilder.cs b/src/CodeOfChaos.Ansi/Builders/AnsiForegroundBuilder.cs new file mode 100644 index 0000000..212f195 --- /dev/null +++ b/src/CodeOfChaos.Ansi/Builders/AnsiForegroundBuilder.cs @@ -0,0 +1,14 @@ +// --------------------------------------------------------------------------------------------------------------------- +// Imports +// --------------------------------------------------------------------------------------------------------------------- +namespace CodeOfChaos.Ansi; + +// --------------------------------------------------------------------------------------------------------------------- +// Code +// --------------------------------------------------------------------------------------------------------------------- +public partial class AnsiForegroundBuilder : AnsiStringBuilder { + private AnsiForegroundBuilder BuilderAction(Action action) { + action(); + return this; + } +} diff --git a/src/CodeOfChaos.Ansi/Builders/AnsiStringBuilder.cs b/src/CodeOfChaos.Ansi/Builders/AnsiStringBuilder.cs new file mode 100644 index 0000000..ccdad65 --- /dev/null +++ b/src/CodeOfChaos.Ansi/Builders/AnsiStringBuilder.cs @@ -0,0 +1,46 @@ +// --------------------------------------------------------------------------------------------------------------------- +// Imports +// --------------------------------------------------------------------------------------------------------------------- +using System.Text; + +namespace CodeOfChaos.Ansi; + +// --------------------------------------------------------------------------------------------------------------------- +// Code +// --------------------------------------------------------------------------------------------------------------------- +public partial class AnsiStringBuilder { + internal StringBuilder Builder { get; init; } = new(); + public int Length => Builder.Length; + + public AnsiForegroundBuilder Foreground => new() { Builder = Builder }; + public AnsiForegroundBuilder Fore => Foreground; + + public AnsiBackgroundBuilder Background => new() { Builder = Builder }; + public AnsiBackgroundBuilder Back => Background; + + // ----------------------------------------------------------------------------------------------------------------- + // Methods + // ----------------------------------------------------------------------------------------------------------------- + private AnsiStringBuilder BuilderAction(Action action) { + action(); + return this; + } + + public AnsiStringBuilder WithForeground(Action action) => BuilderAction(() => action(Foreground)); + public AnsiStringBuilder WithFore(Action action) => BuilderAction(() => action(Foreground)); + + public AnsiStringBuilder WithBackground(Action action) => BuilderAction(() => action(Foreground)); + public AnsiStringBuilder WithBack(Action action) => BuilderAction(() => action(Foreground)); + + public AnsiStringBuilder Append(string value) => BuilderAction(() => Builder.Append(value)); + public AnsiStringBuilder AppendLine(string value) => BuilderAction(() => Builder.AppendLine(value)); + + public string ToStringAndClear() { + string result = Builder.ToString(); + Builder.Clear(); + return result; + } + + public override string ToString() => Builder.ToString(); + public void Clear() => Builder.Clear(); +} diff --git a/src/CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj b/src/CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj index 25139c3..968f5b2 100644 --- a/src/CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj +++ b/src/CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj @@ -28,11 +28,17 @@ + - + + + + + + diff --git a/src/CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj.DotSettings b/src/CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj.DotSettings new file mode 100644 index 0000000..fe13687 --- /dev/null +++ b/src/CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/Example.CodeOfChaos.Ansi/Example.CodeOfChaos.Ansi.csproj b/src/Example.CodeOfChaos.Ansi/Example.CodeOfChaos.Ansi.csproj new file mode 100644 index 0000000..bcda176 --- /dev/null +++ b/src/Example.CodeOfChaos.Ansi/Example.CodeOfChaos.Ansi.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/src/Example.CodeOfChaos.Ansi/Program.cs b/src/Example.CodeOfChaos.Ansi/Program.cs new file mode 100644 index 0000000..faed05e --- /dev/null +++ b/src/Example.CodeOfChaos.Ansi/Program.cs @@ -0,0 +1,28 @@ +// --------------------------------------------------------------------------------------------------------------------- +// Imports +// --------------------------------------------------------------------------------------------------------------------- +using CodeOfChaos.Ansi; + +namespace Example.CodeOfChaos.Ansi; + +// --------------------------------------------------------------------------------------------------------------------- +// Code +// --------------------------------------------------------------------------------------------------------------------- +public static class Program { + public static void Main() { + var builder = new AnsiStringBuilder(); + + builder.AppendLine("Hello World"); + + builder.Foreground.AppendRedLine("something here"); + builder.Foreground.AppendGreenLine("something green here"); + + + builder.WithForeground(b => b + .AppendRedLine("something red here") + .AppendLine($"something {b.Fore.Green(b.Back.Yellow("green on yellow"))} here") + ); + + Console.WriteLine(builder.ToString()); + } +} diff --git a/tests/Tests.CodeOfChaos.Ansi/Tests.CodeOfChaos.Ansi.csproj b/tests/Tests.CodeOfChaos.Ansi/Tests.CodeOfChaos.Ansi.csproj index 79e3f5e..6447555 100644 --- a/tests/Tests.CodeOfChaos.Ansi/Tests.CodeOfChaos.Ansi.csproj +++ b/tests/Tests.CodeOfChaos.Ansi/Tests.CodeOfChaos.Ansi.csproj @@ -13,7 +13,7 @@ - +