@safe unittest
{
    import std.csv;

    import std.exception : collectException;
    import std.algorithm.searching : count;
    string text = "a,b,c\nHello,65";
    auto ex = collectException!CSVException(csvReader(text).count);
    assert(ex.toString == "(Row: 0, Col: 0) Row 2's length 2 does not match previous length of 3.");
}

@safe unittest
{
    import std.csv;

    import std.exception : collectException;
    import std.algorithm.searching : count;
    import std.typecons : Tuple;
    string text = "a,b\nHello,65";
    auto ex = collectException!CSVException(csvReader!(Tuple!(string,int))(text).count);
    assert(ex.toString == "(Row: 1, Col: 2) Unexpected 'b' when converting from type string to type int");
}

@safe unittest
{
    import std.csv;

    import std.exception : assertThrown;
    string text = "a,\"b,c\nHello,65,2.5";
    assertThrown!IncompleteCellException(text.csvReader(["a","b","c"]));
}

@safe unittest
{
    import std.csv;

    import std.exception : assertThrown;
    string text = "a,b,c\nHello,65,2.5";
    assertThrown!HeaderMismatchException(text.csvReader(["b","c","invalid"]));
}

@safe unittest
{
    import std.csv;

    import std.algorithm.comparison : equal;
    import std.algorithm.searching : count;
    import std.exception : assertThrown;

    string text = "a,b,c\nHello,65,\"2.5";
    assertThrown!IncompleteCellException(text.csvReader.count);

    // ignore the exceptions and try to handle invalid CSV
    auto firstLine = text.csvReader!(string, Malformed.ignore)(null).front;
    assert(firstLine.equal(["Hello", "65", "2.5"]));
}

@safe unittest
{
    import std.csv;

    import std.algorithm.comparison : equal;
    string text = "76,26,22";
    auto records = text.csvReader!int;
    assert(records.equal!equal([
        [76, 26, 22],
    ]));
}

@safe unittest
{
    import std.csv;

    import std.algorithm.comparison : equal;
    string text = "Hello;65;2.5\nWorld;123;7.5";
    struct Layout
    {
        string name;
        int value;
        double other;
    }

    auto records = text.csvReader!Layout(';');
    assert(records.equal([
        Layout("Hello", 65, 2.5),
        Layout("World", 123, 7.5),
    ]));
}

@safe unittest
{
    import std.csv;

    string text = "A \" is now part of the data";
    auto records = text.csvReader!(string, Malformed.ignore);
    auto record = records.front;

    assert(record.front == text);
}

@safe unittest
{
    import std.csv;

    import std.algorithm.comparison : equal;
    string text = "a,b,c\nHello,65,63.63\nWorld,123,3673.562";
    auto records = text.csvReader!int(["b"]);

    assert(records.equal!equal([
        [65],
        [123],
    ]));
}

@safe unittest
{
    import std.csv;

    import std.algorithm.comparison : equal;
    string text = "a,b,c\nHello,65,2.5\nWorld,123,7.5";
    struct Layout
    {
        int value;
        double other;
        string name;
    }

    auto records = text.csvReader!Layout(["b","c","a"]);
    assert(records.equal([
        Layout(65, 2.5, "Hello"),
        Layout(123, 7.5, "World")
    ]));
}

@safe unittest
{
    import std.csv;

    string text = "a,b,c\nHello,65,63.63";
    auto records = text.csvReader(null);

    assert(records.header == ["a","b","c"]);
}

@safe unittest
{
    import std.csv;

    import std.algorithm.comparison : equal;

    string text = "76,26,22\n1,2\n3,4,5,6";
    auto records = text.csvReader!int(',', '"', true);

    assert(records.equal!equal([
        [76, 26, 22],
        [1, 2],
        [3, 4, 5, 6]
    ]));
}

@safe unittest
{
    import std.csv;

    import std.algorithm.comparison : equal;

    static struct Three
    {
        int a;
        int b;
        int c;
    }

    string text = "76,26,22\n1,2\n3,4,5,6";
    auto records = text.csvReader!Three(',', '"', true);

    assert(records.equal([
        Three(76, 26, 22),
        Three(1, 2, 0),
        Three(3, 4, 5)
    ]));
}

@safe unittest
{
    import std.csv;

    import std.algorithm.comparison : equal;

    auto text = "Name,Occupation,Salary\r" ~
        "Joe,Carpenter,300000\nFred,Blacksmith\r\n";

    auto r = csvReader!(string[string])(text, null, ',', '"', true);

    assert(r.equal([
        [ "Name" : "Joe", "Occupation" : "Carpenter", "Salary" : "300000" ],
        [ "Name" : "Fred", "Occupation" : "Blacksmith" ]
    ]));
}

@safe unittest
{
    import std.csv;

    import std.array : appender;
    import std.range.primitives : popFront;

    string str = "65,63\n123,3673";

    auto a = appender!(char[])();

    csvNextToken(str,a,',','"');
    assert(a.data == "65");
    assert(str == ",63\n123,3673");

    str.popFront();
    a.shrinkTo(0);
    csvNextToken(str,a,',','"');
    assert(a.data == "63");
    assert(str == "\n123,3673");

    str.popFront();
    a.shrinkTo(0);
    csvNextToken(str,a,',','"');
    assert(a.data == "123");
    assert(str == ",3673");
}