< Summary

Information
Class: ArturRios.Data.Export.Exporters.CsvExporter<T>
Assembly: ArturRios.Data.Export
File(s): /home/runner/work/dotnet-data/dotnet-data/src/ArturRios.Data.Export/Exporters/CsvExporter.cs
Line coverage
100%
Covered lines: 15
Uncovered lines: 0
Coverable lines: 15
Total lines: 49
Line coverage: 100%
Branch coverage
100%
Covered branches: 12
Total branches: 12
Branch coverage: 100%
Method coverage
100%
Covered methods: 3
Fully covered methods: 3
Total methods: 3
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
WriteCoreAsync()100%44100%
Escape(...)100%88100%

File(s)

/home/runner/work/dotnet-data/dotnet-data/src/ArturRios.Data.Export/Exporters/CsvExporter.cs

#LineLine coverage
 1using ArturRios.Data.Export.Abstractions;
 2using ArturRios.Data.Export.Configuration;
 3using Microsoft.Extensions.Logging;
 4
 5namespace ArturRios.Data.Export.Exporters;
 6
 7/// <summary>Writes records as delimited text using the shared column map and RFC 4180 quoting.</summary>
 8/// <remarks>
 9///     Quoting follows RFC 4180: a field is quoted when it holds the delimiter, a double quote or a line
 10///     break, and an embedded quote is doubled. The delimiter and the line terminator are not fixed to the
 11///     RFC's comma and CRLF - the delimiter comes from the options and lines end with the platform's
 12///     newline - so the output is RFC 4180 only when those happen to match.
 13/// </remarks>
 14/// <typeparam name="T">The record type.</typeparam>
 15/// <param name="options">CSV options.</param>
 16/// <param name="logger">Optional logger; see <see cref="ExporterBase{T}" />.</param>
 17public class CsvExporter<T>(CsvOptions options, ILogger<CsvExporter<T>>? logger = null)
 1618    : ExporterBase<T>(logger) where T : class
 19{
 20    /// <inheritdoc />
 21    protected override async Task WriteCoreAsync(IEnumerable<T> data, Stream destination, CancellationToken ct)
 22    {
 1223        var columns = ColumnMap.For<T>();
 1224        var writer = new StreamWriter(destination, options.Encoding, leaveOpen: true);
 25
 1226        await using var writerScope = writer.ConfigureAwait(false);
 27
 1228        if (options.IncludeHeader)
 29        {
 1030            await writer.WriteLineAsync(string.Join(options.Delimiter,
 4031                columns.Select(c => Escape(c.Header, options.Delimiter)))).ConfigureAwait(false);
 32        }
 33
 4434        foreach (var item in data)
 35        {
 1036            ct.ThrowIfCancellationRequested();
 1037            await writer.WriteLineAsync(string.Join(options.Delimiter,
 4038                columns.Select(c => Escape(ValueRenderer.Render(c.Getter(item)), options.Delimiter)))).ConfigureAwait(fa
 39        }
 40
 1241        await writer.FlushAsync(ct).ConfigureAwait(false);
 1242    }
 43
 44    private static string Escape(string field, char delimiter)
 45    {
 6046        var mustQuote = field.Contains(delimiter) || field.Contains('"') || field.Contains('\n') || field.Contains('\r')
 6047        return mustQuote ? $"\"{field.Replace("\"", "\"\"")}\"" : field;
 48    }
 49}