Skip to content

Commit

Permalink
feat: minimal schema with fields and runtime type (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
Seddryck authored Jan 4, 2025
1 parent acee7d7 commit a44987a
Show file tree
Hide file tree
Showing 30 changed files with 448 additions and 88 deletions.
53 changes: 48 additions & 5 deletions PocketCsvReader.Testing/Configuration/CsvReaderBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,60 @@ namespace PocketCsvReader.Testing.Configuration;
public class CsvReaderBuilderTest
{
[Test]
public void WithDescriptor_ShouldSetDescriptor()
public void WithDialectFunc_ShouldSetDialect()
{
var builder = new CsvReaderBuilder().WithDialectDescriptor
var builder = new CsvReaderBuilder().WithDialect
(
(desc) => desc
(dialect) => dialect
.WithDelimiter(Delimiter.Tab)
.WithLineTerminator(LineTerminator.LineFeed)
);
var reader = builder.Build();
Assert.That(reader.Profile.Descriptor.Delimiter, Is.EqualTo('\t'));
Assert.That(reader.Profile.Descriptor.LineTerminator, Is.EqualTo("\n"));
Assert.That(reader.Profile.Dialect.Delimiter, Is.EqualTo('\t'));
Assert.That(reader.Profile.Dialect.LineTerminator, Is.EqualTo("\n"));
}

[Test]
public void WithDialect_ShouldSetDialect()
{
var builder = new CsvReaderBuilder().WithDialect
(
new DialectDescriptorBuilder()
.WithDelimiter(Delimiter.Tab)
.WithLineTerminator(LineTerminator.LineFeed)
);
var reader = builder.Build();
Assert.That(reader.Profile.Dialect.Delimiter, Is.EqualTo('\t'));
Assert.That(reader.Profile.Dialect.LineTerminator, Is.EqualTo("\n"));
}

[Test]
public void WithSchemaFunc_ShouldSetSchema()
{
var builder = new CsvReaderBuilder().WithSchema
(
(schema) => schema
.Indexed()
.WithField<int>("foo")
.WithField(typeof(bool), "bar")
);
var reader = builder.Build();
Assert.That(reader.Profile.Schema, Is.Not.Null);
Assert.That(reader.Profile.Schema!.Fields, Is.Not.Null.And.Not.Empty);
}

[Test]
public void WithSchema_ShouldSetSchema()
{
var builder = new CsvReaderBuilder().WithSchema
(
new SchemaDescriptorBuilder()
.Indexed()
.WithField<int>("foo")
.WithField(typeof(bool), "bar")
);
var reader = builder.Build();
Assert.That(reader.Profile.Schema, Is.Not.Null);
Assert.That(reader.Profile.Schema!.Fields, Is.Not.Null.And.Not.Empty);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,9 @@ public void WithCsvDdfVersion_ShouldSetCsvDdfVersionToValue()
.Build();
var csvReader = new CsvReader(new CsvProfile(descriptor));

Assert.That(csvReader.Profile.Descriptor.Delimiter, Is.EqualTo('\t'));
Assert.That(csvReader.Profile.Descriptor.LineTerminator, Is.EqualTo("\r"));
Assert.That(csvReader.Profile.Descriptor.QuoteChar, Is.EqualTo('\''));
Assert.That(csvReader.Profile.Descriptor.DoubleQuote, Is.True);
Assert.That(csvReader.Profile.Dialect.Delimiter, Is.EqualTo('\t'));
Assert.That(csvReader.Profile.Dialect.LineTerminator, Is.EqualTo("\r"));
Assert.That(csvReader.Profile.Dialect.QuoteChar, Is.EqualTo('\''));
Assert.That(csvReader.Profile.Dialect.DoubleQuote, Is.True);
}
}
112 changes: 112 additions & 0 deletions PocketCsvReader.Testing/Configuration/SchemaDescriptionBuilderTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using PocketCsvReader.Configuration;

namespace PocketCsvReader.Testing.Configuration;
public class SchemaDescriptionBuilderTest
{
[Test]
[TestCase(typeof(int))]
[TestCase(typeof(bool))]
public void WithField_ShouldSetField(Type type)
{
var descriptor = new SchemaDescriptorBuilder()
.Indexed()
.WithField(type)
.Build();
Assert.That(descriptor, Is.Not.Null);
Assert.That(descriptor!.Fields, Has.Length.EqualTo(1));
Assert.That(descriptor.Fields[0].RuntimeType, Is.EqualTo(type));
}

[Test]
[TestCase(typeof(int), typeof(bool))]
[TestCase(typeof(bool), typeof(bool), typeof(string))]
public void WithField_ShouldSetFields(params Type[] types)
{
var builder = new SchemaDescriptorBuilder().Indexed();
foreach (var type in types)
builder.WithField(type);
var descriptor = builder.Build();
Assert.That(descriptor, Is.Not.Null);
Assert.That(descriptor!.Fields, Has.Length.EqualTo(types.Length));
foreach (var (field, type) in descriptor.Fields.Zip(types))
Assert.That(field.RuntimeType, Is.EqualTo(type));
}

[Test]
public void WithFieldGeneric_ShouldSetField()
{
var descriptor = new SchemaDescriptorBuilder()
.Indexed()
.WithField<int>()
.Build();
Assert.That(descriptor, Is.Not.Null);
Assert.That(descriptor!.Fields, Has.Length.EqualTo(1));
Assert.That(descriptor.Fields[0].RuntimeType, Is.EqualTo(typeof(int)));
}

[Test]
public void WithFieldsNamed_ShouldSetFields()
{
var descriptor = new SchemaDescriptorBuilder()
.Indexed()
.WithField<int>("foo")
.WithField(typeof(bool), "bar")
.Build();
Assert.That(descriptor, Is.Not.Null);
Assert.That(descriptor!.Fields, Has.Length.EqualTo(2));
Assert.That(descriptor.Fields[0].RuntimeType, Is.EqualTo(typeof(int)));
Assert.That(descriptor.Fields[0].Name, Is.EqualTo("foo"));
Assert.That(descriptor.Fields[1].Name, Is.EqualTo("bar"));
Assert.That(descriptor.Fields[1].RuntimeType, Is.EqualTo(typeof(bool)));
}

[Test]
public void NamedWithFields_ShouldSetFields()
{
var descriptor = new SchemaDescriptorBuilder()
.Named()
.WithField<int>("foo")
.WithField(typeof(bool), "bar")
.Build();
Assert.That(descriptor, Is.Not.Null);
Assert.That(descriptor!.Fields, Has.Length.EqualTo(2));
Assert.That(descriptor.Fields["foo"].RuntimeType, Is.EqualTo(typeof(int)));
Assert.That(descriptor.Fields["foo"].Name, Is.EqualTo("foo"));
Assert.That(descriptor.Fields["bar"].RuntimeType, Is.EqualTo(typeof(bool)));
Assert.That(descriptor.Fields["bar"].Name, Is.EqualTo("bar"));

foreach (var field in descriptor.Fields)
Assert.That(field.Name, Is.Not.Null.Or.Empty);
}

[Test]
public void DuplicateNames_ShouldThrow()
{
var descriptor = new SchemaDescriptorBuilder()
.Named()
.WithField<int>("foo")
.WithField<bool>("foo");

var ex = Assert.Throws<DuplicateNameException>(() => descriptor.Build());
Assert.That(ex!.Message, Does.Contain("'foo'"));
}

[Test]
public void EmptyNames_ShouldThrow()
{
var descriptor = new SchemaDescriptorBuilder()
.Named()
.WithField<int>("")
.WithField<bool>("foo");

var ex = Assert.Throws<ArgumentException>(() => descriptor.Build());
Assert.That(ex!.Message.ToLower(), Does.Contain("empty or null"));
}
}
10 changes: 5 additions & 5 deletions PocketCsvReader/CharParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ public CharParser(CsvProfile profile)
FirstCharOfField = Profile.ParserOptimizations.LookupTableChar
? new FirstCharOfFieldLookupParser(this).Parse
: new FirstCharOfFieldParser(this).Parse;
LineTerminatorParser = Profile.Descriptor.LineTerminator.Length == 1
LineTerminatorParser = Profile.Dialect.LineTerminator.Length == 1
? new FirstCharOfRecordParser(this)
: new LineTerminatorParser(this, Profile.Descriptor.LineTerminator.Length);
: new LineTerminatorParser(this, Profile.Dialect.LineTerminator.Length);
LineTerminator = LineTerminatorParser.Parse;
Comment = new CommentParser(this, Profile.Descriptor.LineTerminator.Length).Parse;
Comment = new CommentParser(this, Profile.Dialect.LineTerminator.Length).Parse;
CharOfField = Profile.ParserOptimizations.LookupTableChar
? new CharOfFieldLookupParser(this).Parse
: new CharOfFieldParser(this).Parse;
CharOfQuotedField = new CharOfQuotedFieldParser(this).Parse;
AfterQuoteChar = Profile.Descriptor.DoubleQuote
AfterQuoteChar = Profile.Dialect.DoubleQuote
? new AfterQuoteCharDoubleParser(this).Parse
: new AfterQuoteCharParser(this).Parse;
AfterEscapeCharQuotedField = new AfterEscapeCharQuotedFieldParser(this).Parse;
Expand All @@ -77,7 +77,7 @@ public void Reset()

public ParserState ParseEof()
{
if (Internal == FirstCharOfRecord || Internal == Comment || (Internal == LineTerminator && Profile.Descriptor.LineTerminator.Length == 1))
if (Internal == FirstCharOfRecord || Internal == Comment || (Internal == LineTerminator && Profile.Dialect.LineTerminator.Length == 1))
return ParserState.Eof;
else if (Internal == FirstCharOfField)
{
Expand Down
4 changes: 2 additions & 2 deletions PocketCsvReader/CharParsing/AfterEscapeChar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public AfterEscapeCharParser(CharParser parser)

public override ParserState Parse(char c)
{
if (c == Parser.Profile.Descriptor.Delimiter
|| c == Parser.Profile.Descriptor.EscapeChar)
if (c == Parser.Profile.Dialect.Delimiter
|| c == Parser.Profile.Dialect.EscapeChar)
{
Parser.SetEscapedField();
Parser.Switch(Parser.CharOfField);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public AfterEscapeCharQuotedFieldParser(CharParser parser)

public override ParserState Parse(char c)
{
if (c == Parser.Profile.Descriptor.QuoteChar
|| c == Parser.Profile.Descriptor.EscapeChar)
if (c == Parser.Profile.Dialect.QuoteChar
|| c == Parser.Profile.Dialect.EscapeChar)
{
Parser.SetEscapedField();
Parser.Switch(Parser.CharOfQuotedField);
Expand Down
2 changes: 1 addition & 1 deletion PocketCsvReader/CharParsing/AfterQuoteCharDoubleParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public AfterQuoteCharDoubleParser(CharParser parser)

public override ParserState Parse(char c)
{
if (c == Parser.Profile.Descriptor.QuoteChar)
if (c == Parser.Profile.Dialect.QuoteChar)
{
Parser.SetEscapedField();
Parser.Switch(Parser.CharOfQuotedField);
Expand Down
6 changes: 3 additions & 3 deletions PocketCsvReader/CharParsing/AfterQuoteCharParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ public AfterQuoteCharParser(CharParser parser)

public virtual ParserState Parse(char c)
{
if (c == Parser.Profile.Descriptor.Delimiter)
if (c == Parser.Profile.Dialect.Delimiter)
{
Parser.SetFieldEnd(-2);
Parser.Switch(Parser.FirstCharOfField);
return ParserState.Field;
}

if (c == Parser.Profile.Descriptor.LineTerminator[0])
if (c == Parser.Profile.Dialect.LineTerminator[0])
{
Parser.SetFieldEnd(-2);
Parser.Switch(Parser.LineTerminator);
return Parser.Profile.Descriptor.LineTerminator.Length == 1
return Parser.Profile.Dialect.LineTerminator.Length == 1
? ParserState.Record
: ParserState.Continue;
}
Expand Down
6 changes: 3 additions & 3 deletions PocketCsvReader/CharParsing/CharOfFieldLookupParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ internal class CharOfFieldLookupParser : IInternalCharParser
public CharOfFieldLookupParser(CharParser parser)
{
(Parser, FirstCharOfLineTerminator, Delimiter, EscapeChar)
= (parser, parser.Profile.Descriptor.LineTerminator[0], parser.Profile.Descriptor.Delimiter
, parser.Profile.Descriptor.EscapeChar);
= (parser, parser.Profile.Dialect.LineTerminator[0], parser.Profile.Dialect.Delimiter
, parser.Profile.Dialect.EscapeChar);

InterestingChars = new bool[char.MaxValue + 1];
InterestingChars[Delimiter] = true;
Expand All @@ -42,7 +42,7 @@ public virtual ParserState Parse(char c)
{
Parser.SetFieldEnd(-1);
Parser.Switch(Parser.LineTerminator);
return Parser.Profile.Descriptor.LineTerminator.Length == 1
return Parser.Profile.Dialect.LineTerminator.Length == 1
? ParserState.Record
: ParserState.Continue;
}
Expand Down
6 changes: 3 additions & 3 deletions PocketCsvReader/CharParsing/CharOfFieldParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ internal class CharOfFieldParser : IInternalCharParser

public CharOfFieldParser(CharParser parser)
=> (Parser, FirstCharOfLineTerminator, Delimiter, EscapeChar)
= (parser, parser.Profile.Descriptor.LineTerminator[0], parser.Profile.Descriptor.Delimiter
, parser.Profile.Descriptor.EscapeChar);
= (parser, parser.Profile.Dialect.LineTerminator[0], parser.Profile.Dialect.Delimiter
, parser.Profile.Dialect.EscapeChar);

public virtual ParserState Parse(char c)
{
Expand All @@ -30,7 +30,7 @@ public virtual ParserState Parse(char c)
{
Parser.SetFieldEnd(-1);
Parser.Switch(Parser.LineTerminator);
return Parser.Profile.Descriptor.LineTerminator.Length == 1
return Parser.Profile.Dialect.LineTerminator.Length == 1
? ParserState.Record
: ParserState.Continue;
}
Expand Down
4 changes: 2 additions & 2 deletions PocketCsvReader/CharParsing/CharOfQuotedFieldParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ public CharOfQuotedFieldParser(CharParser parser)

public virtual ParserState Parse(char c)
{
if (c == Parser.Profile.Descriptor.QuoteChar)
if (c == Parser.Profile.Dialect.QuoteChar)
{
Parser.Switch(Parser.AfterQuoteChar);
return ParserState.Continue;
}

if (c == Parser.Profile.Descriptor.EscapeChar)
if (c == Parser.Profile.Dialect.EscapeChar)
{
Parser.Switch(Parser.AfterEscapeCharQuotedField);
return ParserState.Continue;
Expand Down
6 changes: 3 additions & 3 deletions PocketCsvReader/CharParsing/FirstCharOfFieldLookupParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ internal class FirstCharOfFieldLookupParser : IInternalCharParser
public FirstCharOfFieldLookupParser(CharParser parser)
{
(Parser, FirstCharOfLineTerminator, QuoteChar, Delimiter, IsSkipInitialSpace, EscapeChar)
= (parser, parser.Profile.Descriptor.LineTerminator[0], parser.Profile.Descriptor.QuoteChar
, parser.Profile.Descriptor.Delimiter, parser.Profile.Descriptor.SkipInitialSpace
, parser.Profile.Descriptor.EscapeChar);
= (parser, parser.Profile.Dialect.LineTerminator[0], parser.Profile.Dialect.QuoteChar
, parser.Profile.Dialect.Delimiter, parser.Profile.Dialect.SkipInitialSpace
, parser.Profile.Dialect.EscapeChar);

InterestingChars = new bool[char.MaxValue + 1];
InterestingChars[Delimiter] = true;
Expand Down
6 changes: 3 additions & 3 deletions PocketCsvReader/CharParsing/FirstCharOfFieldParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ internal class FirstCharOfFieldParser : IInternalCharParser

public FirstCharOfFieldParser(CharParser parser)
=> (Parser, FirstCharOfLineTerminator, QuoteChar, Delimiter, IsSkipInitialSpace, EscapeChar)
= (parser, parser.Profile.Descriptor.LineTerminator[0], parser.Profile.Descriptor.QuoteChar
, parser.Profile.Descriptor.Delimiter, parser.Profile.Descriptor.SkipInitialSpace
, parser.Profile.Descriptor.EscapeChar);
= (parser, parser.Profile.Dialect.LineTerminator[0], parser.Profile.Dialect.QuoteChar
, parser.Profile.Dialect.Delimiter, parser.Profile.Dialect.SkipInitialSpace
, parser.Profile.Dialect.EscapeChar);

public virtual ParserState Parse(char c)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal class FirstCharOfQuotedFieldParser : IInternalCharParser

public FirstCharOfQuotedFieldParser(CharParser parser)
=> (Parser, QuoteChar, EscapeChar)
= (parser, parser.Profile.Descriptor.QuoteChar, parser.Profile.Descriptor.EscapeChar);
= (parser, parser.Profile.Dialect.QuoteChar, parser.Profile.Dialect.EscapeChar);

public virtual ParserState Parse(char c)
{
Expand Down
6 changes: 3 additions & 3 deletions PocketCsvReader/CharParsing/FirstCharOfRecordParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ internal class FirstCharOfRecordParser : FirstCharOfFieldParser
public FirstCharOfRecordParser(CharParser parser)
: base(parser)
{
CommentChar = Parser.Profile.Descriptor.CommentChar;
CommentRows = Parser.Profile.Descriptor.CommentRows ?? [];
HeaderRows = Parser.Profile.Descriptor.Header ? Parser.Profile.Descriptor.HeaderRows : [];
CommentChar = Parser.Profile.Dialect.CommentChar;
CommentRows = Parser.Profile.Dialect.CommentRows ?? [];
HeaderRows = Parser.Profile.Dialect.Header ? Parser.Profile.Dialect.HeaderRows : [];
}

public override ParserState Parse(char c)
Expand Down
2 changes: 1 addition & 1 deletion PocketCsvReader/CharParsing/LineTerminatorParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ private bool IsLast()

public ParserState Parse(char c)
{
if (c == Parser.Profile.Descriptor.LineTerminator[Index])
if (c == Parser.Profile.Dialect.LineTerminator[Index])
{
Index++;
if (IsLast())
Expand Down
Loading

0 comments on commit a44987a

Please sign in to comment.