Skip to content

Commit

Permalink
Add SepWriterOptions.WriteHeader (#107)
Browse files Browse the repository at this point in the history
Fixes #71
  • Loading branch information
nietras committed Mar 8, 2024
1 parent 592ca80 commit da18795
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 30 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,13 @@ public Sep Sep { get; init; }
/// May be `null` for default culture.
/// </summary>
public CultureInfo? CultureInfo { get; init; }
/// <summary>
/// Specifies whether to write a header row
/// before data rows. Requires all columns
/// to have a name. Otherwise, columns can be
/// added by indexing alone.
/// </summary>
public bool WriteHeader { get; init; } = true;
```

## Limitations and Constraints
Expand Down Expand Up @@ -1754,6 +1761,7 @@ namespace nietras.SeparatedValues
public SepWriterOptions(nietras.SeparatedValues.Sep sep) { }
public System.Globalization.CultureInfo? CultureInfo { get; init; }
public nietras.SeparatedValues.Sep Sep { get; init; }
public bool WriteHeader { get; init; }
}
}
```
74 changes: 74 additions & 0 deletions src/Sep.Test/SepWriterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,80 @@ public void SepWriterTest_Extensions_ToFile()
File.Delete(fileName);
}

[TestMethod]
public void SepWriterTest_WriteHeader_False()
{
using var writer = Sep.Writer(o => o with { WriteHeader = false }).ToText();
{
using var row = writer.NewRow();
row["A"].Set("1");
row[1].Format(2);
row[2].Set($"{2 * 17}");
}
{
using var row = writer.NewRow();
// Order of cols is not important after first row written
row[2].Set("65");
row[1].Format(3);
row["A"].Set($"{23,3}");
}
var expected =
@"1;2;34
23;3;65
";
Assert.AreEqual(expected, writer.ToString());
}

[TestMethod]
public void SepWriterTest_WriteHeader_False_UnknownColName()
{
using var writer = Sep.Writer(o => o with { WriteHeader = false }).ToText();
{
using var row = writer.NewRow();
row["A"].Set("1");
row[1].Format(2);
row[2].Set($"{2 * 17}");
}
{
using var row = writer.NewRow();
row[2].Set("65");
row[1].Format(3);
row["A"].Set($"{23,3}");

var e = AssertThrowsException<KeyNotFoundException>(row,
r => { r["B"].Set("Test"); });
Assert.AreEqual("B", e.Message);
}
var expected =
@"1;2;34
23;3;65
";
Assert.AreEqual(expected, writer.ToString());
}

[TestMethod]
public void SepWriterTest_WriteHeader_False_ColMissingInSecondRow()
{
using var writer = Sep.Writer(o => o with { WriteHeader = false }).ToText();
{
using var row1 = writer.NewRow();
row1[0].Set("A");
row1[1].Set("B");
}
{
var row2 = writer.NewRow();
row2[1].Set("Y");
var e = AssertThrowsException<InvalidOperationException>(row2,
r => { r.Dispose(); });
Assert.AreEqual("Not all expected columns have been set.", e.Message);
}
// Expected output should only be valid rows
var expected =
@"A;B
";
Assert.AreEqual(expected, writer.ToString());
}

static SepWriter CreateWriter() =>
Sep.New(';').Writer().ToText();

Expand Down
9 changes: 8 additions & 1 deletion src/Sep/Internals/SepThrow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,14 @@ internal static void InvalidOperationException_WriterDoesNotHaveActiveRow()
internal static void InvalidOperationException_NotAllColsSet(List<ColImpl> cols, string[] colNamesHeader)
{
// TODO: Make detailed exception
throw new InvalidOperationException($"Not all expected columns '{string.Join(",", colNamesHeader)}' have been set.");
if (colNamesHeader.Length == 0)
{
throw new InvalidOperationException($"Not all expected columns have been set.");
}
else
{
throw new InvalidOperationException($"Not all expected columns '{string.Join(",", colNamesHeader)}' have been set.");
}
}

[DoesNotReturn]
Expand Down
14 changes: 7 additions & 7 deletions src/Sep/SepWriter.Row.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ public void Dispose()
internal ColImpl GetOrAddCol(int colIndex)
{
var cols = _cols;
//if (colIndex == sbs.Count)
//{
// // TODO: Add without name if WriteHeader false
//}
var c = cols[colIndex];
return c;
if (colIndex == cols.Count && !_writeHeader)
{
var col = new ColImpl(this, colIndex, string.Empty, SepStringBuilderPool.Take());
_cols.Add(col);
}
return cols[colIndex];
}

internal ColImpl GetOrAddCol(string colName)
Expand All @@ -109,7 +109,7 @@ internal ColImpl GetOrAddCol(string colName)

if (!_colNameToCol.TryGetValue(colName, out var col))
{
if (!_headerWritten)
if (!_headerWrittenOrSkipped)
{
var colIndex = _colNameToCol.Count;
col = new ColImpl(this, colIndex, colName, SepStringBuilderPool.Take());
Expand Down
52 changes: 30 additions & 22 deletions src/Sep/SepWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public partial class SepWriter : IDisposable
const int DefaultCapacity = 16;
readonly Sep _sep;
readonly CultureInfo? _cultureInfo;
readonly bool _writeHeader;
readonly TextWriter _writer;
internal readonly List<(string ColName, int ColIndex)> _colNameCache = new(DefaultCapacity);

Expand All @@ -20,14 +21,15 @@ public partial class SepWriter : IDisposable
internal string[] _colNamesHeader = Array.Empty<string>();

internal readonly SepArrayPoolAccessIndexed _arrayPool = new();
bool _headerWritten = false;
bool _headerWrittenOrSkipped = false;
bool _newRowActive = false;
int _cacheIndex = 0;

public SepWriter(SepWriterOptions options, TextWriter writer)
{
_sep = options.Sep;
_cultureInfo = options.CultureInfo;
_writeHeader = options.WriteHeader;
_writer = writer;
}

Expand All @@ -47,37 +49,43 @@ internal void EndRow(Row row)
{
if (!_newRowActive) { SepThrow.InvalidOperationException_WriterDoesNotHaveActiveRow(); }

A.Assert(_colNameToCol.Count == _cols.Count);
A.Assert(!_writeHeader || _colNameToCol.Count == _cols.Count);
var cols = _cols;

// Header
if (!_headerWritten)
if (!_headerWrittenOrSkipped)
{
A.Assert(_colNamesHeader.Length == 0);
if (cols.Count != _colNamesHeader.Length)
if (_writeHeader)
{
_colNamesHeader = new string[cols.Count];
}
var notFirstHeader = false;
for (var colIndex = 0; colIndex < cols.Count; ++colIndex)
{
var col = cols[colIndex];
A.Assert(colIndex == col.Index);

if (notFirstHeader)
A.Assert(_colNamesHeader.Length == 0);
if (cols.Count != _colNamesHeader.Length)
{
_writer.Write(_sep.Separator);
_colNamesHeader = new string[cols.Count];
}
var notFirstHeader = false;
for (var colIndex = 0; colIndex < cols.Count; ++colIndex)
{
var col = cols[colIndex];
A.Assert(colIndex == col.Index);

if (notFirstHeader)
{
_writer.Write(_sep.Separator);
}
var name = col.Name;
_writer.Write(name);
_colNamesHeader[colIndex] = name;
notFirstHeader = true;
}
var name = col.Name;
_writer.Write(name);
_colNamesHeader[colIndex] = name;
notFirstHeader = true;
_writer.WriteLine();
}
_writer.WriteLine();
_headerWritten = true;
_headerWrittenOrSkipped = true;
}
else
{
// Note this prevents writing different number of cols (or less cols
// than previous row) in case of no header written. Revisit this if
// variable cols count is needed.
for (var colIndex = 0; colIndex < cols.Count; ++colIndex)
{
var col = cols[colIndex];
Expand All @@ -86,7 +94,7 @@ internal void EndRow(Row row)
SepThrow.InvalidOperationException_NotAllColsSet(cols, _colNamesHeader);
}
}
A.Assert(cols.Count == _colNamesHeader.Length);
A.Assert(!_writeHeader || cols.Count == _colNamesHeader.Length);
}

// New Row
Expand Down
8 changes: 8 additions & 0 deletions src/Sep/SepWriterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public SepWriterOptions(Sep sep)
{
Sep = sep;
CultureInfo = SepDefaults.CultureInfo;
WriteHeader = true;
}

/// <summary>
Expand All @@ -21,4 +22,11 @@ public SepWriterOptions(Sep sep)
/// May be `null` for default culture.
/// </summary>
public CultureInfo? CultureInfo { get; init; }
/// <summary>
/// Specifies whether to write a header row
/// before data rows. Requires all columns
/// to have a name. Otherwise, columns can be
/// added by indexing alone.
/// </summary>
public bool WriteHeader { get; init; } = true;
}

0 comments on commit da18795

Please sign in to comment.