Compare commits

..

6 Commits
v1.0.0 ... main

Author SHA1 Message Date
6e42881fdb Merge branch '2-prepare-release-of-1-1-0' into 'main'
Resolve "Prepare release of 1.1.0"

Closes #2

See merge request open-source/dotnet/csv-metrics-logger!2
2024-05-21 11:41:28 +00:00
Thorsten Sommer
8bb35bed6e
Prepared 1.1.0 release 2024-05-21 13:40:30 +02:00
85b4550e4f Merge branch '1-need-support-for-type-params' into 'main'
Resolve "Need support for type params"

Closes #1

See merge request open-source/dotnet/csv-metrics-logger!1
2024-05-21 07:37:09 +00:00
Thorsten Sommer
a81e88c727
Added support for generic properties 2024-05-21 09:31:46 +02:00
Thorsten Sommer
549a34eed1
Fixed issue where code generator runs multiple times 2024-05-21 09:29:43 +02:00
Thorsten Sommer
106ec09ac5
Update 2024-05-21 09:29:08 +02:00
9 changed files with 143 additions and 32 deletions

View File

@ -10,9 +10,9 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include build output in the package. Necessary for analyzers. --> <IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include build output in the package. Necessary for analyzers. -->
<AssemblyVersion>1.0.0</AssemblyVersion> <AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion> <FileVersion>1.1.0</FileVersion>
<PackageVersion>1.0.0</PackageVersion> <PackageVersion>1.1.0</PackageVersion>
<Authors>Thorsten Sommer</Authors> <Authors>Thorsten Sommer</Authors>
<PackageProjectUrl>https://devops.tsommer.org/open-source/dotnet/csv-metrics-logger</PackageProjectUrl> <PackageProjectUrl>https://devops.tsommer.org/open-source/dotnet/csv-metrics-logger</PackageProjectUrl>
<RepositoryUrl>https://devops.tsommer.org/open-source/dotnet/csv-metrics-logger</RepositoryUrl> <RepositoryUrl>https://devops.tsommer.org/open-source/dotnet/csv-metrics-logger</RepositoryUrl>

View File

@ -29,6 +29,9 @@ public class CSVConverterGenerator : ISourceGenerator
private void ProcessStructure(GeneratorExecutionContext context, TypeDeclarationSyntax dataStructure) private void ProcessStructure(GeneratorExecutionContext context, TypeDeclarationSyntax dataStructure)
{ {
var generatedFileName = $"{dataStructure.Identifier}.Generated.cs";
var namespaceName = this.GetNamespaceFrom(dataStructure);
var accessModifiers = string.Join(' ', dataStructure.Modifiers.Select(n => n.Text)); var accessModifiers = string.Join(' ', dataStructure.Modifiers.Select(n => n.Text));
var declarationType = dataStructure switch var declarationType = dataStructure switch
{ {
@ -37,22 +40,27 @@ public class CSVConverterGenerator : ISourceGenerator
_ => string.Empty _ => string.Empty
}; };
var namespaceName = this.GetNamespaceFrom(dataStructure);
var parameterProperties = dataStructure.ParameterList?.Parameters.Select(n => n.Identifier.ValueText).ToList() ?? []; var typeParameters = dataStructure switch
var bodyProperties = dataStructure.Members.OfType<PropertyDeclarationSyntax>().Select(n => n.Identifier.ValueText).ToList(); {
var allProperties = new List<string>(parameterProperties); { TypeParameterList: not null } => $"<{string.Join(", ", dataStructure.TypeParameterList.Parameters.Select(t => t.Identifier.Text))}>",
allProperties.AddRange(bodyProperties); _ => string.Empty
};
var parameterProperties = dataStructure.ParameterList?.Parameters.Select(n => n.Identifier.ValueText) ?? [];
var bodyProperties = dataStructure.Members.OfType<PropertyDeclarationSyntax>().Select(n => n.Identifier.ValueText);
var allProperties = parameterProperties.Concat(bodyProperties).ToList();
var numberProperties = allProperties.Count; var numberProperties = allProperties.Count;
var header = string.Join(ARRAY_DELIMITER, allProperties.Select(n => $"\"{n}\"")); 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 allFieldsString = string.Join(ARRAY_DELIMITER, allProperties.Select(n => $$"""string.Create(CultureInfo.InvariantCulture, $"{this.{{n}}}")"""));
var sourceCode = SourceText.From( var sourceCode = SourceText.From(
$$""" $$"""
using System.Globalization; using System.Globalization;
using CSV_Metrics_Logger; using CSV_Metrics_Logger;
namespace {{namespaceName}}; namespace {{namespaceName}};
{{accessModifiers}} {{declarationType}} {{dataStructure.Identifier}} : IConvertToCSV {{accessModifiers}} {{declarationType}} {{dataStructure.Identifier}}{{typeParameters}} : IConvertToCSV
{ {
public uint GetCSVColumnCount() public uint GetCSVColumnCount()
{ {
@ -71,7 +79,7 @@ public class CSVConverterGenerator : ISourceGenerator
} }
""", Encoding.UTF8); """, Encoding.UTF8);
context.AddSource($"{dataStructure.Identifier}.Generated.cs", sourceCode); context.AddSource(generatedFileName, sourceCode);
} }
private string GetNamespaceFrom(SyntaxNode? node) => node switch private string GetNamespaceFrom(SyntaxNode? node) => node switch

View File

@ -18,7 +18,7 @@ List<TestData> testData =
]; ];
var filename = Path.GetTempFileName(); var filename = Path.GetTempFileName();
await using var storage = CSVStorage<TestData>.Create(fileName); await using var storage = CSVStorage<TestData>.Create(filename);
foreach (var data in testData) foreach (var data in testData)
storage.Write(data); storage.Write(data);
@ -26,13 +26,37 @@ foreach (var data in testData)
You might use `storage.Write` from multiple threads. The logger will handle the synchronization for you. You might use `storage.Write` from multiple threads. The logger will handle the synchronization for you.
CSV Metrics Logger uses a source generator. You have to use the following NuGet packages: CSV Metrics Logger uses a source generator. You need to use the following NuGet packages:
```xml ```xml
<PackageReference Include="CSVMetricsLoggerGenerator" Version="1.0.0" /> <PackageReference Include="CSVMetricsLoggerGenerator" Version="1.1.0" />
<PackageReference Include="CSVMetricsLogger" Version="1.0.0" /> <PackageReference Include="CSVMetricsLogger" Version="1.1.0" />
``` ```
Your data must be modeled as a structure; classes are not supported. It does not matter if you are using a (readonly) record struct or a regular struct. The only requirement is that the struct must be a partial struct. The source generator will generate the missing part of the struct for you. Each public property will be used as a column in the CSV file. The property type might be any type, as long as it supports the `ToString(CultureInfo)` method. Your data must be modeled as a structure; classes are not supported. It does not matter if you are using a (readonly) record struct or a regular struct. The only requirement is that the struct must be a partial struct. The source generator will generate the missing part of the struct for you. Each public property will be used as a column in the CSV file. The property type can be any type, as long as it supports the ToString() method.
Additionally, you can use generic type parameters in your struct, as long as there is a suitable ToString() overload. Here is an example:
```csharp
using CSV_Metrics_Logger;
[CSVRecord]
public readonly partial record struct GenericTestData<TNum>(string Name, sbyte Age, TNum Measure) where TNum : IFloatingPointIeee754<TNum>;
List<TestData> testData =
[
new TestData<float>("Name 1", 14, 47.53f),
new TestData<float>("Name 2", 25, 19.84f),
new TestData<float>("Name 3", 36, 38.78f),
new TestData<float>("Name 4", 47, 17.25f),
new TestData<float>("Name 5", 58, 73.89f),
];
var filename = Path.GetTempFileName();
await using var storage = CSVStorage<TestData<float>>.Create(filename);
foreach (var data in testData)
storage.Write(data);
```
For each data structure, you create a CSVStorage instance. The CSVStorage instance is a disposable object. You must dispose of it when you are done with it. When the CSV file already exists, the CSVStorage object will append data to the existing file; the header will not be written again. For each data structure, you create a CSVStorage instance. The CSVStorage instance is a disposable object. You must dispose of it when you are done with it. When the CSV file already exists, the CSVStorage object will append data to the existing file; the header will not be written again.

View File

@ -9,9 +9,9 @@
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors> <WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<AssemblyVersion>1.0.0</AssemblyVersion> <AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion> <FileVersion>1.1.0</FileVersion>
<PackageVersion>1.0.0</PackageVersion> <PackageVersion>1.1.0</PackageVersion>
<Authors>Thorsten Sommer</Authors> <Authors>Thorsten Sommer</Authors>
<PackageProjectUrl>https://devops.tsommer.org/open-source/dotnet/csv-metrics-logger</PackageProjectUrl> <PackageProjectUrl>https://devops.tsommer.org/open-source/dotnet/csv-metrics-logger</PackageProjectUrl>
<RepositoryUrl>https://devops.tsommer.org/open-source/dotnet/csv-metrics-logger</RepositoryUrl> <RepositoryUrl>https://devops.tsommer.org/open-source/dotnet/csv-metrics-logger</RepositoryUrl>
@ -25,10 +25,16 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(UseLocalProjects)' == 'true'"> <ItemGroup Condition="'$(UseLocalProjects)' == 'true'">
<ProjectReference Include="..\CSV Metrics Logger Generator\CSV Metrics Logger Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> <ProjectReference Include="..\CSV Metrics Logger Generator\CSV Metrics Logger Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>build; analyzers; buildtransitive</IncludeAssets>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(UseLocalProjects)' == 'false'"> <ItemGroup Condition="'$(UseLocalProjects)' == 'false'">
<PackageReference Include="CSVMetricsLoggerGenerator" Version="1.0.0" /> <PackageReference Include="CSVMetricsLoggerGenerator" Version="1.0.0" OutputItemType="Analyzer" ReferenceOutputAssembly="false">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

View File

@ -18,7 +18,7 @@ List<TestData> testData =
]; ];
var filename = Path.GetTempFileName(); var filename = Path.GetTempFileName();
await using var storage = CSVStorage<TestData>.Create(fileName); await using var storage = CSVStorage<TestData>.Create(filename);
foreach (var data in testData) foreach (var data in testData)
storage.Write(data); storage.Write(data);
@ -26,13 +26,37 @@ foreach (var data in testData)
You might use `storage.Write` from multiple threads. The logger will handle the synchronization for you. You might use `storage.Write` from multiple threads. The logger will handle the synchronization for you.
CSV Metrics Logger uses a source generator. You have to use the following NuGet packages: CSV Metrics Logger uses a source generator. You need to use the following NuGet packages:
```xml ```xml
<PackageReference Include="CSVMetricsLoggerGenerator" Version="1.0.0" /> <PackageReference Include="CSVMetricsLoggerGenerator" Version="1.1.0" />
<PackageReference Include="CSVMetricsLogger" Version="1.0.0" /> <PackageReference Include="CSVMetricsLogger" Version="1.1.0" />
``` ```
Your data must be modeled as a structure; classes are not supported. It does not matter if you are using a (readonly) record struct or a regular struct. The only requirement is that the struct must be a partial struct. The source generator will generate the missing part of the struct for you. Each public property will be used as a column in the CSV file. The property type might be any type, as long as it supports the `ToString(CultureInfo)` method. Your data must be modeled as a structure; classes are not supported. It does not matter if you are using a (readonly) record struct or a regular struct. The only requirement is that the struct must be a partial struct. The source generator will generate the missing part of the struct for you. Each public property will be used as a column in the CSV file. The property type can be any type, as long as it supports the ToString() method.
Additionally, you can use generic type parameters in your struct, as long as there is a suitable ToString() overload. Here is an example:
```csharp
using CSV_Metrics_Logger;
[CSVRecord]
public readonly partial record struct GenericTestData<TNum>(string Name, sbyte Age, TNum Measure) where TNum : IFloatingPointIeee754<TNum>;
List<TestData> testData =
[
new TestData<float>("Name 1", 14, 47.53f),
new TestData<float>("Name 2", 25, 19.84f),
new TestData<float>("Name 3", 36, 38.78f),
new TestData<float>("Name 4", 47, 17.25f),
new TestData<float>("Name 5", 58, 73.89f),
];
var filename = Path.GetTempFileName();
await using var storage = CSVStorage<TestData<float>>.Create(filename);
foreach (var data in testData)
storage.Write(data);
```
For each data structure, you create a CSVStorage instance. The CSVStorage instance is a disposable object. You must dispose of it when you are done with it. When the CSV file already exists, the CSVStorage object will append data to the existing file; the header will not be written again. For each data structure, you create a CSVStorage instance. The CSVStorage instance is a disposable object. You must dispose of it when you are done with it. When the CSV file already exists, the CSVStorage object will append data to the existing file; the header will not be written again.

View File

@ -18,7 +18,7 @@ List<TestData> testData =
]; ];
var filename = Path.GetTempFileName(); var filename = Path.GetTempFileName();
await using var storage = CSVStorage<TestData>.Create(fileName); await using var storage = CSVStorage<TestData>.Create(filename);
foreach (var data in testData) foreach (var data in testData)
storage.Write(data); storage.Write(data);
@ -26,13 +26,37 @@ foreach (var data in testData)
You might use `storage.Write` from multiple threads. The logger will handle the synchronization for you. You might use `storage.Write` from multiple threads. The logger will handle the synchronization for you.
CSV Metrics Logger uses a source generator. You have to use the following NuGet packages: CSV Metrics Logger uses a source generator. You need to use the following NuGet packages:
```xml ```xml
<PackageReference Include="CSVMetricsLoggerGenerator" Version="1.0.0" /> <PackageReference Include="CSVMetricsLoggerGenerator" Version="1.1.0" />
<PackageReference Include="CSVMetricsLogger" Version="1.0.0" /> <PackageReference Include="CSVMetricsLogger" Version="1.1.0" />
``` ```
Your data must be modeled as a structure; classes are not supported. It does not matter if you are using a (readonly) record struct or a regular struct. The only requirement is that the struct must be a partial struct. The source generator will generate the missing part of the struct for you. Each public property will be used as a column in the CSV file. The property type might be any type, as long as it supports the `ToString(CultureInfo)` method. Your data must be modeled as a structure; classes are not supported. It does not matter if you are using a (readonly) record struct or a regular struct. The only requirement is that the struct must be a partial struct. The source generator will generate the missing part of the struct for you. Each public property will be used as a column in the CSV file. The property type can be any type, as long as it supports the ToString() method.
Additionally, you can use generic type parameters in your struct, as long as there is a suitable ToString() overload. Here is an example:
```csharp
using CSV_Metrics_Logger;
[CSVRecord]
public readonly partial record struct GenericTestData<TNum>(string Name, sbyte Age, TNum Measure) where TNum : IFloatingPointIeee754<TNum>;
List<TestData> testData =
[
new TestData<float>("Name 1", 14, 47.53f),
new TestData<float>("Name 2", 25, 19.84f),
new TestData<float>("Name 3", 36, 38.78f),
new TestData<float>("Name 4", 47, 17.25f),
new TestData<float>("Name 5", 58, 73.89f),
];
var filename = Path.GetTempFileName();
await using var storage = CSVStorage<TestData<float>>.Create(filename);
foreach (var data in testData)
storage.Write(data);
```
For each data structure, you create a CSVStorage instance. The CSVStorage instance is a disposable object. You must dispose of it when you are done with it. When the CSV file already exists, the CSVStorage object will append data to the existing file; the header will not be written again. For each data structure, you create a CSVStorage instance. The CSVStorage instance is a disposable object. You must dispose of it when you are done with it. When the CSV file already exists, the CSVStorage object will append data to the existing file; the header will not be written again.

8
Tests/TestGeneric.cs Normal file
View File

@ -0,0 +1,8 @@
using System.Numerics;
using CSV_Metrics_Logger;
namespace Tests;
[CSVRecord]
public readonly partial record struct TestGeneric<TNum>(string Name, sbyte Age, TNum Value) where TNum : IFloatingPointIeee754<TNum>;

View File

@ -60,6 +60,23 @@ public sealed class Tests
}); });
} }
[Test]
public void TestGeneric()
{
var testData = new TestGeneric<float>("Bob", 120, 4.78f);
Assert.Multiple(() =>
{
var numberColumns = testData.GetCSVColumnCount();
Assert.That(numberColumns, Is.EqualTo(3));
var header = testData.GetCSVHeaders();
Assert.That(header, Is.EquivalentTo(new[] { "Name", "Age", "Value" }));
var dataLine = testData.ConvertToCSVDataLine();
Assert.That(dataLine, Is.EquivalentTo(new[] { "Bob", "120", "4.78" }));
});
}
[Test] [Test]
public void TestRegularStruct() public void TestRegularStruct()
{ {

View File

@ -12,7 +12,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0"/> <PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NUnit" Version="3.14.0"/> <PackageReference Include="NUnit" Version="3.14.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.9.0"/> <PackageReference Include="NUnit.Analyzers" Version="3.9.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>