Skip to content

Commit f8943f6

Browse files
committed
Feat: Foundation for string builder approach
1 parent 277412e commit f8943f6

17 files changed

+358
-36
lines changed

CodeOfChaos.Ansi.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "example", "example", "{A5F3
1616
EndProject
1717
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeOfChaos.Ansi.Generators", "src\CodeOfChaos.Ansi.Generators\CodeOfChaos.Ansi.Generators.csproj", "{82941FB2-B148-4A12-8DFB-71769B4F4DD8}"
1818
EndProject
19+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.CodeOfChaos.Ansi", "src\Example.CodeOfChaos.Ansi\Example.CodeOfChaos.Ansi.csproj", "{F8867201-2A48-4DDE-BB19-5267F09D7DE2}"
20+
EndProject
1921
Global
2022
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2123
Debug|Any CPU = Debug|Any CPU
@@ -38,11 +40,16 @@ Global
3840
{82941FB2-B148-4A12-8DFB-71769B4F4DD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
3941
{82941FB2-B148-4A12-8DFB-71769B4F4DD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
4042
{82941FB2-B148-4A12-8DFB-71769B4F4DD8}.Release|Any CPU.Build.0 = Release|Any CPU
43+
{F8867201-2A48-4DDE-BB19-5267F09D7DE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44+
{F8867201-2A48-4DDE-BB19-5267F09D7DE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
45+
{F8867201-2A48-4DDE-BB19-5267F09D7DE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
46+
{F8867201-2A48-4DDE-BB19-5267F09D7DE2}.Release|Any CPU.Build.0 = Release|Any CPU
4147
EndGlobalSection
4248
GlobalSection(NestedProjects) = preSolution
4349
{26284571-0E09-4BAF-8C2B-DF87DCC1BA0B} = {8DD280D4-1E14-4D5E-AFE6-58DD8F079DCC}
4450
{64B26DED-68C3-47FF-B409-1C8FAD4F9176} = {197E72AD-DEAB-4350-AFC3-A3BB38720BF5}
4551
{ADEADD97-0AFA-4D9E-970B-9FFB932949B3} = {AF1A203C-6EF1-440E-BB3C-55B1DBFE9C19}
4652
{82941FB2-B148-4A12-8DFB-71769B4F4DD8} = {197E72AD-DEAB-4350-AFC3-A3BB38720BF5}
53+
{F8867201-2A48-4DDE-BB19-5267F09D7DE2} = {A5F3433A-9F39-4372-B994-E262F597F1D2}
4754
EndGlobalSection
4855
EndGlobal

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# CodeOfChaos.Ansi
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using CodeOfChaos.Ansi.Generators.Xml;
5+
using CodeOfChaos.GeneratorTools;
6+
using Microsoft.CodeAnalysis;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Collections.Immutable;
10+
using System.IO;
11+
using System.Linq;
12+
using System.Text.RegularExpressions;
13+
using System.Xml.Serialization;
14+
15+
namespace CodeOfChaos.Ansi.Generators;
16+
// ---------------------------------------------------------------------------------------------------------------------
17+
// Code
18+
// ---------------------------------------------------------------------------------------------------------------------
19+
[Generator]
20+
public class AnsiStringBuilderGenerator : IIncrementalGenerator {
21+
private static Regex FindFirstColorName { get; } = new(@"ASB\.[^\\\/]*\.xml");
22+
23+
// -----------------------------------------------------------------------------------------------------------------
24+
// Methods
25+
// -----------------------------------------------------------------------------------------------------------------
26+
public void Initialize(IncrementalGeneratorInitializationContext context) {
27+
IncrementalValueProvider<ImmutableArray<AdditionalText>> provider = context.AdditionalTextsProvider
28+
.Where(IsColorFile)
29+
.Collect();
30+
31+
context.RegisterSourceOutput(provider, GenerateCode);
32+
}
33+
34+
private static bool IsColorFile(AdditionalText f) => FindFirstColorName.IsMatch(f.Path);
35+
36+
private static void GenerateCode(SourceProductionContext context, ImmutableArray<AdditionalText> files) {
37+
IEnumerable<ColorEntry> colors = files.SelectMany(file => ParseColorFile(context, file)).ToArray();
38+
var builder = new GeneratorStringBuilder();
39+
40+
#region Fore & Background
41+
foreach (string section in new[] {"Foreground", "Background"}) {
42+
context.AddSource($"Ansi{section}Builder.g.cs", builder
43+
.AppendUsings("System")
44+
.AppendAutoGenerated()
45+
.AppendNamespace("CodeOfChaos.Ansi")
46+
.AppendLine($"public partial class Ansi{section}Builder {{")
47+
.ForEach(colors, (stringBuilder, entry) => stringBuilder
48+
.AppendBodyIndented($$"""
49+
#region {{entry.Name}}
50+
private static CodeOfChaos.Ansi.ByteVector3 _{{entry.Name}} = new({{entry.Colors}});
51+
52+
public string {{entry.Name}}(string text) => $"{CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}})}{text}{CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes}";
53+
54+
public Ansi{{section}}Builder Append{{entry.Name}}(string text) => BuilderAction(() => {
55+
Builder
56+
.Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}}))
57+
.Append(text)
58+
.Append(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes);
59+
});
60+
61+
public Ansi{{section}}Builder Append{{entry.Name}}(Func<string> action) => BuilderAction(() => {
62+
Builder
63+
.Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}}))
64+
.Append(action())
65+
.Append(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes);
66+
});
67+
68+
public Ansi{{section}}Builder Append{{entry.Name}}(Action<Ansi{{section}}Builder> action) => BuilderAction(() => {
69+
Builder.Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}}));
70+
action(this);
71+
Builder.Append(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes);
72+
});
73+
74+
public Ansi{{section}}Builder Append{{entry.Name}}Line(string text) => BuilderAction(() => {
75+
Builder
76+
.Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}}))
77+
.Append(text)
78+
.AppendLine(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes);
79+
});
80+
81+
public Ansi{{section}}Builder Append{{entry.Name}}Line(Func<string> action) => BuilderAction(() => {
82+
Builder
83+
.Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}}))
84+
.Append(action())
85+
.AppendLine(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes);
86+
});
87+
88+
public Ansi{{section}}Builder Append{{entry.Name}}Line(Action<Ansi{{section}}Builder> action) => BuilderAction(() => {
89+
Builder.Append(CodeOfChaos.Ansi.AnsiCodes.Rgb{{section}}Color(_{{entry.Name}}));
90+
action(this);
91+
Builder.AppendLine(CodeOfChaos.Ansi.AnsiCodes.ResetGraphicsModes);
92+
});
93+
#endregion
94+
95+
""")
96+
.AppendLine()
97+
)
98+
.AppendLine("}")
99+
.ToStringAndClear()
100+
);
101+
}
102+
#endregion
103+
}
104+
105+
private static ColorEntry[] ParseColorFile(SourceProductionContext context, AdditionalText file) {
106+
try {
107+
string? fileContent = file.GetText(context.CancellationToken)?.ToString();
108+
if (fileContent is null || string.IsNullOrWhiteSpace(fileContent)) return [];
109+
110+
// Use XmlSerializer to deserialize XML
111+
var serializer = new XmlSerializer(typeof(ColorEntryContainer));
112+
using var reader = new StringReader(fileContent);
113+
// Deserialize the XML into the container object
114+
var container = (ColorEntryContainer?)serializer.Deserialize(reader);
115+
116+
// Map the Color attribute (comma-separated values) to the ColorCode int array
117+
return container?.Entries
118+
.Select(entry => entry.ToColorEntry())
119+
.ToArray() ?? [];
120+
}
121+
catch (InvalidOperationException ex) {
122+
// Report a diagnostic if XML parsing fails
123+
context.ReportDiagnostic(Diagnostic.Create(
124+
new DiagnosticDescriptor(
125+
"ASB01",
126+
"Invalid XML Format",
127+
$"Failed to parse the XML file: {file.Path}. Error: {ex.Message}",
128+
"CodeGeneration",
129+
DiagnosticSeverity.Warning,
130+
true),
131+
Location.None));
132+
return [];
133+
}
134+
catch (FormatException ex) {
135+
// Handle errors when parsing color values
136+
context.ReportDiagnostic(Diagnostic.Create(
137+
new DiagnosticDescriptor(
138+
"ASB02",
139+
"Invalid Color Formatting",
140+
$"Error while parsing RGB values in file: {file.Path}. Error: {ex.Message}",
141+
"CodeGeneration",
142+
DiagnosticSeverity.Warning,
143+
true),
144+
Location.None));
145+
return [];
146+
}
147+
}
148+
}

src/CodeOfChaos.Ansi.Generators/Class1.cs

Lines changed: 0 additions & 4 deletions
This file was deleted.

src/CodeOfChaos.Ansi.Generators/CodeOfChaos.Ansi.Generators.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
4242
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.12.0" PrivateAssets="all" />
4343
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all" />
44-
<PackageReference Include="CodeOfChaos.GeneratorTools" Version="1.1.2" Pack="true" GeneratePathProperty="true" PrivateAssets="all" />
44+
<PackageReference Include="CodeOfChaos.GeneratorTools" Version="1.2.0" Pack="true" GeneratePathProperty="true" PrivateAssets="all" />
4545
</ItemGroup>
4646

4747
<ItemGroup>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"$schema": "https://json.schemastore.org/launchsettings.json",
3+
"profiles": {
4+
"DebugRoslynSourceGenerator": {
5+
"commandName": "DebugRoslynComponent",
6+
"targetProject": "../CodeOfChaos.Ansi/CodeOfChaos.Ansi.csproj"
7+
}
8+
}
9+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text.RegularExpressions;
8+
using System.Xml.Serialization;
9+
10+
namespace CodeOfChaos.Ansi.Generators.Xml;
11+
// ---------------------------------------------------------------------------------------------------------------------
12+
// Code
13+
// ---------------------------------------------------------------------------------------------------------------------
14+
[XmlRoot("ColorEntries")]
15+
public class ColorEntryContainer {
16+
[XmlElement("ColorEntry")]
17+
public List<XmlColorEntry> Entries { get; set; } = [];// Default to an empty list
18+
}
19+
20+
public class XmlColorEntry {
21+
[XmlAttribute("Name")]
22+
public string Name { get; set; } = string.Empty;// Name attribute for the color
23+
24+
[XmlAttribute("Color")]
25+
public string Color { get; set; } = string.Empty;// RGB values as a comma-separated string
26+
27+
private readonly Regex _regexCommaSeparated = new(@"^(\d+)[,.;:](\d+)[,.;:](\d+)$", RegexOptions.Compiled);
28+
private readonly Regex _regexHexFormat = new(@"^#?([A-Fa-f0-9]{6})$", RegexOptions.Compiled);
29+
30+
public ColorEntry ToColorEntry() {
31+
if (_regexCommaSeparated.Match(Color) is {Success : true } match) return new ColorEntry {
32+
Name = Name,
33+
Codes = [
34+
int.Parse(match.Groups[1].Value),
35+
int.Parse(match.Groups[2].Value),
36+
int.Parse(match.Groups[3].Value)
37+
]
38+
};
39+
40+
if (_regexHexFormat.IsMatch(Color)) {
41+
string hex = Color.TrimStart('#');
42+
return new ColorEntry {
43+
Name = Name,
44+
Codes = Enumerable.Range(0, 3)
45+
.Select(i => Convert.ToInt32(hex.Substring(i * 2, 2), 16))
46+
.ToArray()
47+
};
48+
}
49+
50+
throw new FormatException("Invalid Color format.");
51+
}
52+
}
53+
54+
public class ColorEntry {
55+
public string Name { get; set; } = string.Empty;// Mapped Name
56+
57+
public int[] Codes { get; set; } = [];// Mapped and split RGB values
58+
59+
public string Colors => string.Join(",", Codes);
60+
}

src/CodeOfChaos.Ansi/AnsiStringBuilder.cs

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<ColorEntries>
2+
<ColorEntry Name="Red" Color="FF0000"/>
3+
<ColorEntry Name="Green" Color="0,255,0"/>
4+
<ColorEntry Name="Blue" Color="0,0,255"/>
5+
<ColorEntry Name="Yellow" Color="255,255,0"/>
6+
</ColorEntries>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
namespace CodeOfChaos.Ansi;
5+
6+
// ---------------------------------------------------------------------------------------------------------------------
7+
// Code
8+
// ---------------------------------------------------------------------------------------------------------------------
9+
public partial class AnsiBackgroundBuilder : AnsiStringBuilder {
10+
private AnsiBackgroundBuilder BuilderAction(Action action) {
11+
action();
12+
return this;
13+
}
14+
}

0 commit comments

Comments
 (0)