Skip to content

Commit

Permalink
Feat: Foundation for string builder approach
Browse files Browse the repository at this point in the history
  • Loading branch information
AnnaSasDev committed Jan 10, 2025
1 parent 277412e commit f8943f6
Show file tree
Hide file tree
Showing 17 changed files with 358 additions and 36 deletions.
7 changes: 7 additions & 0 deletions CodeOfChaos.Ansi.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# CodeOfChaos.Ansi
148 changes: 148 additions & 0 deletions src/CodeOfChaos.Ansi.Generators/AnsiStringBuilderGenerator.cs
Original file line number Diff line number Diff line change
@@ -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<ImmutableArray<AdditionalText>> 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<AdditionalText> files) {
IEnumerable<ColorEntry> 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<string> 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<Ansi{{section}}Builder> 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<string> 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<Ansi{{section}}Builder> 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 [];
}
}
}
4 changes: 0 additions & 4 deletions src/CodeOfChaos.Ansi.Generators/Class1.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.12.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all" />
<PackageReference Include="CodeOfChaos.GeneratorTools" Version="1.1.2" Pack="true" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="CodeOfChaos.GeneratorTools" Version="1.2.0" Pack="true" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"DebugRoslynSourceGenerator": {
"commandName": "DebugRoslynComponent",
"targetProject": "../CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj"
}
}
}
60 changes: 60 additions & 0 deletions src/CodeOfChaos.Ansi.Generators/Xml/ColorEntryContainer.cs
Original file line number Diff line number Diff line change
@@ -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<XmlColorEntry> 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);
}
29 changes: 0 additions & 29 deletions src/CodeOfChaos.Ansi/AnsiStringBuilder.cs

This file was deleted.

6 changes: 6 additions & 0 deletions src/CodeOfChaos.Ansi/Assets/ASB.CssColors.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<ColorEntries>
<ColorEntry Name="Red" Color="FF0000"/>
<ColorEntry Name="Green" Color="0,255,0"/>
<ColorEntry Name="Blue" Color="0,0,255"/>
<ColorEntry Name="Yellow" Color="255,255,0"/>
</ColorEntries>
14 changes: 14 additions & 0 deletions src/CodeOfChaos.Ansi/Builders/AnsiBackgroundBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
namespace CodeOfChaos.Ansi;

// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
public partial class AnsiBackgroundBuilder : AnsiStringBuilder {
private AnsiBackgroundBuilder BuilderAction(Action action) {
action();
return this;
}
}
14 changes: 14 additions & 0 deletions src/CodeOfChaos.Ansi/Builders/AnsiForegroundBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
namespace CodeOfChaos.Ansi;

// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
public partial class AnsiForegroundBuilder : AnsiStringBuilder {
private AnsiForegroundBuilder BuilderAction(Action action) {
action();
return this;
}
}
46 changes: 46 additions & 0 deletions src/CodeOfChaos.Ansi/Builders/AnsiStringBuilder.cs
Original file line number Diff line number Diff line change
@@ -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<AnsiForegroundBuilder> action) => BuilderAction(() => action(Foreground));
public AnsiStringBuilder WithFore(Action<AnsiForegroundBuilder> action) => BuilderAction(() => action(Foreground));

public AnsiStringBuilder WithBackground(Action<AnsiForegroundBuilder> action) => BuilderAction(() => action(Foreground));
public AnsiStringBuilder WithBack(Action<AnsiForegroundBuilder> 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();
}
8 changes: 7 additions & 1 deletion src/CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@
</ItemGroup>

<ItemGroup>
<!-- ProjectReference for the generator as an analyzer -->
<ProjectReference Include="..\CodeOfChaos.Ansi.Generators\CodeOfChaos.Ansi.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

<!-- Ugly, but this is because of some weirdness with how analyzers are imported through a project reference -->
<PackageReference Include="CodeOfChaos.GeneratorTools" Version="1.1.2" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="CodeOfChaos.GeneratorTools" Version="1.2.0" GeneratePathProperty="true" PrivateAssets="all" />
<Analyzer Include="$(PkgCodeOfChaos_GeneratorTools)\lib\netstandard2.0\*.dll" />
</ItemGroup>

<ItemGroup>
<None Include="Assets\ASB.CssColors.xml" />
<AdditionalFiles Include="Assets\ASB.CssColors.xml"/>
</ItemGroup>

</Project>
Loading

0 comments on commit f8943f6

Please sign in to comment.