diff --git a/CSV Metrics Logger Generator/CSV Metrics Logger Generator.csproj b/CSV Metrics Logger Generator/CSV Metrics Logger Generator.csproj new file mode 100644 index 0000000..9c5d44a --- /dev/null +++ b/CSV Metrics Logger Generator/CSV Metrics Logger Generator.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + latest + enable + enable + CS8600;CS8602;CS8603 + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/CSV Metrics Logger Generator/CSVConverterGenerator.cs b/CSV Metrics Logger Generator/CSVConverterGenerator.cs new file mode 100644 index 0000000..43dcd67 --- /dev/null +++ b/CSV Metrics Logger Generator/CSVConverterGenerator.cs @@ -0,0 +1,86 @@ +using System.Text; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace CSV_Metrics_Logger_Generator; + +[Generator] +public class CSVConverterGenerator : ISourceGenerator +{ + private const string ARRAY_DELIMITER = ", "; + + #region Implementation of ISourceGenerator + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.SyntaxReceiver is not SyntaxReceiver receiver) + return; + + foreach (var dataStructure in receiver.DataStructures) + this.ProcessStructure(context, dataStructure); + } + + private void ProcessStructure(GeneratorExecutionContext context, TypeDeclarationSyntax dataStructure) + { + var accessModifiers = string.Join(' ', dataStructure.Modifiers.Select(n => n.Text)); + var declarationType = dataStructure switch + { + RecordDeclarationSyntax => "record struct", + StructDeclarationSyntax => "struct", + + _ => string.Empty + }; + var namespaceName = this.GetNamespaceFrom(dataStructure); + var parameterProperties = dataStructure.ParameterList?.Parameters.Select(n => n.Identifier.ValueText).ToList() ?? []; + var bodyProperties = dataStructure.Members.OfType().Select(n => n.Identifier.ValueText).ToList(); + var allProperties = new List(parameterProperties); + allProperties.AddRange(bodyProperties); + + var numberProperties = allProperties.Count; + var header = string.Join(ARRAY_DELIMITER, allProperties.Select(n => $"\"{n}\"")); + var allFieldsString = string.Join(ARRAY_DELIMITER, allProperties.Select(n => $"this.{n}.ToString(CultureInfo.InvariantCulture)")); + + var sourceCode = SourceText.From( + $$""" + using System.Globalization; + using CSV_Metrics_Logger; + namespace {{namespaceName}}; + {{accessModifiers}} {{declarationType}} {{dataStructure.Identifier}} : IConvertToCSV + { + public uint GetCSVColumnCount() + { + return (uint){{numberProperties}}; + } + + public IList GetCSVHeaders() + { + return [ {{header}} ]; + } + + public IList ConvertToCSVDataLine() + { + return [ {{allFieldsString}} ]; + } + } + """, Encoding.UTF8); + + context.AddSource($"{dataStructure.Identifier}.Generated.cs", sourceCode); + } + + private string GetNamespaceFrom(SyntaxNode? node) => node switch + { + BaseNamespaceDeclarationSyntax namespaceDeclarationSyntax => namespaceDeclarationSyntax.Name.ToString(), + null => string.Empty, + + _ => this.GetNamespaceFrom(node.Parent) + }; + + #endregion +} \ No newline at end of file diff --git a/CSV Metrics Logger Generator/SyntaxReceiver.cs b/CSV Metrics Logger Generator/SyntaxReceiver.cs new file mode 100644 index 0000000..ef96e9a --- /dev/null +++ b/CSV Metrics Logger Generator/SyntaxReceiver.cs @@ -0,0 +1,39 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CSV_Metrics_Logger_Generator; + +internal sealed class SyntaxReceiver : ISyntaxReceiver +{ + public List DataStructures { get; } = []; + + #region Implementation of ISyntaxReceiver + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + // + // The data structures we are looking for: + // - any kind of structs (record struct, readonly record struct, regular struct) + // - with the attribute [CSVRecord] + // + + // Check if the syntax node is a struct declaration: + TypeDeclarationSyntax? structNode = syntaxNode switch + { + RecordDeclarationSyntax rds => rds, + StructDeclarationSyntax sds => sds, + + _ => null + }; + + // No struct? + if (structNode is null) + return; + + // Check if the struct has the attribute [CSVRecord]: + if (structNode.AttributeLists.Any(asl => asl.Attributes.Any(n => n.Name.ToString() == "CSVRecord"))) + this.DataStructures.Add(structNode); + } + + #endregion +} \ No newline at end of file diff --git a/CSV Metrics Logger.sln b/CSV Metrics Logger.sln index 7cbdcd0..fbc8414 100644 --- a/CSV Metrics Logger.sln +++ b/CSV Metrics Logger.sln @@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSV Metrics Logger", "CSV Metrics Logger\CSV Metrics Logger.csproj", "{BFFEE7AC-0227-4FCA-BBE7-4F6E23CD5CD7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSV Metrics Logger Generator", "CSV Metrics Logger Generator\CSV Metrics Logger Generator.csproj", "{559505B4-5322-4CFD-ABB9-D835C2A7EC09}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +14,13 @@ Global {BFFEE7AC-0227-4FCA-BBE7-4F6E23CD5CD7}.Debug|Any CPU.Build.0 = Debug|Any CPU {BFFEE7AC-0227-4FCA-BBE7-4F6E23CD5CD7}.Release|Any CPU.ActiveCfg = Release|Any CPU {BFFEE7AC-0227-4FCA-BBE7-4F6E23CD5CD7}.Release|Any CPU.Build.0 = Release|Any CPU + {559505B4-5322-4CFD-ABB9-D835C2A7EC09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {559505B4-5322-4CFD-ABB9-D835C2A7EC09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {559505B4-5322-4CFD-ABB9-D835C2A7EC09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {559505B4-5322-4CFD-ABB9-D835C2A7EC09}.Release|Any CPU.Build.0 = Release|Any CPU + {871708D2-633C-4D78-86EF-A1B72043B652}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {871708D2-633C-4D78-86EF-A1B72043B652}.Debug|Any CPU.Build.0 = Debug|Any CPU + {871708D2-633C-4D78-86EF-A1B72043B652}.Release|Any CPU.ActiveCfg = Release|Any CPU + {871708D2-633C-4D78-86EF-A1B72043B652}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/CSV Metrics Logger/CSV Metrics Logger.csproj b/CSV Metrics Logger/CSV Metrics Logger.csproj index ed5bd50..c4d0220 100644 --- a/CSV Metrics Logger/CSV Metrics Logger.csproj +++ b/CSV Metrics Logger/CSV Metrics Logger.csproj @@ -9,4 +9,8 @@ CS8600;CS8602;CS8603 + + + + diff --git a/CSV Metrics Logger/CSVRecordAttribute.cs b/CSV Metrics Logger/CSVRecordAttribute.cs new file mode 100644 index 0000000..977b306 --- /dev/null +++ b/CSV Metrics Logger/CSVRecordAttribute.cs @@ -0,0 +1,4 @@ +namespace CSV_Metrics_Logger; + +[AttributeUsage(AttributeTargets.Struct)] +public sealed class CSVRecordAttribute : Attribute; \ No newline at end of file diff --git a/CSV Metrics Logger/IConvertToCSV.cs b/CSV Metrics Logger/IConvertToCSV.cs new file mode 100644 index 0000000..9f61e82 --- /dev/null +++ b/CSV Metrics Logger/IConvertToCSV.cs @@ -0,0 +1,10 @@ +namespace CSV_Metrics_Logger; + +public interface IConvertToCSV +{ + public uint GetCSVColumnCount(); + + public IList GetCSVHeaders(); + + public IList ConvertToCSVDataLine(); +} \ No newline at end of file