Library: add some mutual integration between DataFile and CSVData

...both are related to CSV, and it is conceivable
to create inline CSVData in a test case to populate a DataFile
This commit is contained in:
Fischlurch 2024-04-02 21:18:23 +02:00
parent 03c2191649
commit f37e651b61
5 changed files with 94 additions and 30 deletions

View file

@ -51,7 +51,6 @@
#include "lib/null-value.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "lib/format-string.hpp"
#include "lib/format-util.hpp"
#include "lib/regex.hpp"
#include <limits>
@ -170,7 +169,10 @@ namespace stat {
operator string() const
{
return util::join (*this, "\n");
std::ostringstream buffer;
for (string const& line : *this)
buffer << line << '\n';
return buffer.str();
}

View file

@ -71,7 +71,13 @@
**
** std::vector<int>& counters = daz.n.data;
** \endcode
**
** \par Variations
** The standard case is to have a table backed by persistent file storage,
** which can be initially empty. Under some conditions, especially for tests
** - the DataFile can be created without filename
** - it can be created from a CSVData, which is a `std::vector` of CSV-strings
** - it can be [rendered into CSV strings](\ref #renderCSV)
** - a (new) storage file name can be [given later](\ref saveAs)
** @see DataCSV_test
**
*/
@ -198,6 +204,12 @@ namespace stat{
loadData();
}
DataFile (CSVData const& csv)
: filename_{}
{
appendFrom (csv);
}
/* === Data Access === */
@ -222,12 +234,15 @@ namespace stat{
return rowCnt;
}
string
dumpCSV() const
CSVData
renderCSV() const
{
string csv;
CSVData csv{{}};
csv.reserve (size()+1);
auto header = generateHeaderSpec();
std::swap (csv[0], header);
for (uint i=0; i < size(); ++i)
csv += formatCSVRow(i) + '\n';
csv.emplace_back (formatCSVRow(i));
return csv;
}
@ -291,6 +306,17 @@ namespace stat{
});
}
void
appendFrom (CSVData const& csv)
{
if (isnil (csv)) return;
verifyHeaderSpec (csv[0]);
for (size_t row=1; row<csv.size(); ++row)
if (not isnil (csv[row]))
appendRowFromCSV (csv[row]);
}
/** @param lineLimit number of rows to retain, back from the newest */
void
@ -408,14 +434,14 @@ namespace stat{
});
}
string
generateHeaderSpec()
CSVLine
generateHeaderSpec() const
{
string csv;
CSVLine csv;
forAllColumns(
[&](auto& col)
{
appendCsvField (csv, col.header);
csv += col.header;
});
return csv;
}
@ -448,7 +474,7 @@ namespace stat{
}
string
CSVLine
formatCSVRow (size_t rownum) const
{
if (this->empty())
@ -457,11 +483,11 @@ namespace stat{
throw error::Logic{_Fmt{"Attempt to access row #%d beyond range [0..%d]."}
% rownum % (size()-1)};
string csvLine;
CSVLine csvLine;
forAllColumns(
[&](auto& col)
{
appendCsvField (csvLine, col.data.at(rownum));
csvLine += col.data.at(rownum);
});
return csvLine;
}

View file

@ -65,7 +65,7 @@ namespace test{
/** @test TODO
* @todo WIP 4/24 🔁 define implement
* @todo WIP 4/24 🔁 define implement
*/
void
simpeUsage()
@ -78,12 +78,13 @@ namespace test{
,{4,5}
,{5,8}
,{6,13}
,{7,21}
,{7,21.55}
}});
cout << gnuplot <<endl;
}
/** @test TODO
* @todo WIP 4/24 🔁 define implement
*/

View file

@ -327,9 +327,10 @@ namespace test{
dat.dupRow();
dat.id = "last";
dat.off *= -1;
// can dump the contents as CSV
CHECK (dat.dumpCSV() ==
R"("",0,0
// can render the contents as CSV
CHECK (dat.renderCSV() ==
R"("ID","Value","Offset"
"",0,0
"mid",5.5,1
"last",5.5,-1
)"_expect);
@ -364,8 +365,8 @@ R"("ID","Value","Offset"
auto appended = (CSVLine{} += 5.5) += Symbol();
CHECK (appended == "5.5,\"\""_expect);
CHECK (CSVData({"eeny","meeny","miny","moe"}) == "\"eeny\",\"meeny\",\"miny\",\"moe\""_expect);
CHECK (CSVData({"eeny , meeny","miny","moe"}) == "\"eeny , meeny\"\n\"miny\"\n\"moe\""_expect); // you dirty dirty dishrag you
CHECK (CSVData({"eeny","meeny","miny","moe"}) == "\"eeny\",\"meeny\",\"miny\",\"moe\"\n"_expect);
CHECK (CSVData({"eeny , meeny","miny","moe"}) == "\"eeny , meeny\"\n\"miny\"\n\"moe\"\n"_expect); // you dirty dirty dishrag you
auto csv = CSVData{{"la","la","schland"}
,{{3.2,1l,88}
@ -381,7 +382,23 @@ R"("la","la","schland"
"mit","mia","ned"
";"
false
)"_expect);
VERIFY_FAIL ("Header mismatch in CSV file", TestTab{csv} );
csv = CSVData{{"ID","Value","Offset"}
,{{"Baby","toe"}
}};
VERIFY_FAIL ("unable to parse \"toe\"", TestTab{csv} );
csv = CSVData{{"ID","Value","Offset"}
,{{"Baby",1.6180,23}
,{"Tiger",10101,-5}
}};
TestTab dat{csv};
CHECK (dat.val == 1.0101e4);
CHECK (dat.renderCSV() == string(csv));
}
};

View file

@ -112157,14 +112157,20 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1710516999984" ID="ID_476868581" MODIFIED="1710517031610" TEXT="stat::DataSpan mit Lumiera-Iterator-Framework kompatibel machen">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1710516999984" ID="ID_476868581" MODIFIED="1712095672484" TEXT="stat::DataSpan mit Lumiera-Iterator-Framework kompatibel machen">
<icon BUILTIN="button_ok"/>
<node CREATED="1710517020971" ID="ID_1049280485" MODIFIED="1710517029789" TEXT="das ist ein Vorgriff auf C++20">
<icon BUILTIN="idea"/>
</node>
<node COLOR="#338800" CREATED="1711836137562" ID="ID_108223729" MODIFIED="1711836155134" TEXT="schon mal free-functions begin() | end() hinzugef&#xfc;gt">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1712095673899" ID="ID_179465663" MODIFIED="1712095687761" TEXT="incl. Erzeugen von Teil-Bereichen">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1712095688617" ID="ID_503885767" MODIFIED="1712095695468" TEXT="Test: mit lib::explore()">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node CREATED="1710351128227" ID="ID_1587957134" MODIFIED="1710351136356" TEXT="Erg&#xe4;nzungen">
<icon BUILTIN="idea"/>
@ -112186,8 +112192,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node COLOR="#435e98" CREATED="1710351185059" ID="ID_431256200" MODIFIED="1710352477645" STYLE="fork" TEXT="saveAs()-Funktion bereistellen"/>
</node>
<node COLOR="#338800" CREATED="1711983612740" ID="ID_902546947" MODIFIED="1712089787472" TEXT="vereinfachte / erweiterte CSV-Notation f&#xfc;r Tests">
<linktarget COLOR="#2a9fe5" DESTINATION="ID_902546947" ENDARROW="Default" ENDINCLINATION="-1334;77;" ID="Arrow_ID_47054806" SOURCE="ID_1350863024" STARTARROW="None" STARTINCLINATION="-1467;-42;"/>
<linktarget COLOR="#3483c8" DESTINATION="ID_902546947" ENDARROW="Default" ENDINCLINATION="-1334;77;" ID="Arrow_ID_606537608" SOURCE="ID_1834207024" STARTARROW="None" STARTINCLINATION="1430;149;"/>
<linktarget COLOR="#2a9fe5" DESTINATION="ID_902546947" ENDARROW="Default" ENDINCLINATION="-1334;77;" ID="Arrow_ID_47054806" SOURCE="ID_1350863024" STARTARROW="None" STARTINCLINATION="-1467;-42;"/>
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1711983784796" ID="ID_1350290271" MODIFIED="1712081833591" TEXT="CSVLine : string der Felder anh&#xe4;ngen kann">
<icon BUILTIN="button_ok"/>
@ -112203,8 +112209,17 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="idea"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1712081944646" ID="ID_685990414" MODIFIED="1712081986841" TEXT="kann aus DataTable gewonnen werden">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1712081944646" ID="ID_685990414" MODIFIED="1712095588465" TEXT="kann aus DataFile gewonnen werden">
<icon BUILTIN="button_ok"/>
<node CREATED="1712095483749" ID="ID_448300363" MODIFIED="1712095501564" TEXT="erscheint nur m&#xe4;&#xdf;ig sinnvoll...">
<icon BUILTIN="smiley-neutral"/>
</node>
<node COLOR="#435e98" CREATED="1712095518601" ID="ID_1818911968" MODIFIED="1712095586479" TEXT="Umgekehrt: die dump-Funktion von DataFile durch CSVData ersetzen">
<icon BUILTIN="idea"/>
</node>
<node COLOR="#435e98" CREATED="1712095554779" ID="ID_1265654486" MODIFIED="1712095586479" TEXT="...und damit ist es auch ein neuer ctor sinnvoll: DataFile{CSVData}">
<font ITALIC="true" NAME="SansSerif" SIZE="12"/>
</node>
</node>
</node>
</node>
@ -112260,10 +112275,6 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node COLOR="#338800" CREATED="1711983648446" ID="ID_1858721218" MODIFIED="1712089737113" TEXT="demonnstrate_CSV_Notation">
<icon BUILTIN="button_ok"/>
<node COLOR="#228091" CREATED="1711983651689" ID="ID_1350863024" MODIFIED="1712089777858" TEXT="sollte doch noch etwas die Notation von Testdaten vereinfachen">
<arrowlink COLOR="#2a9fe5" DESTINATION="ID_902546947" ENDARROW="Default" ENDINCLINATION="-1334;77;" ID="Arrow_ID_47054806" STARTARROW="None" STARTINCLINATION="-1467;-42;"/>
<icon BUILTIN="yes"/>
</node>
</node>
<node COLOR="#338800" CREATED="1710460938455" ID="ID_757574171" MODIFIED="1710466412661" TEXT="verify_persistentDataFile">
<icon BUILTIN="button_ok"/>
@ -114421,9 +114432,16 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
</node>
<node COLOR="#338800" CREATED="1711820811033" ID="ID_1587185713" MODIFIED="1711904722630" TEXT="Header + Datentupel&lt;double&gt;">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1712095887150" HGAP="33" ID="ID_675366089" LINK="#ID_1395935730" MODIFIED="1712095932364" TEXT="Umweg &#xfc;ber CSVData" VSHIFT="8">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
<node COLOR="#228091" CREATED="1711983651689" ID="ID_1350863024" MODIFIED="1712089777858" TEXT="sollte doch noch etwas die Notation von Testdaten vereinfachen">
<arrowlink COLOR="#2a9fe5" DESTINATION="ID_902546947" ENDARROW="Default" ENDINCLINATION="-1334;77;" ID="Arrow_ID_47054806" STARTARROW="None" STARTINCLINATION="-1467;-42;"/>
<icon BUILTIN="yes"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711900330296" ID="ID_1080765414" MODIFIED="1711900374682" TEXT="typischerweise: Familie von Aufruf-Front-ends">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1711900349274" ID="ID_1386251374" MODIFIED="1711900360595" TEXT="der eigentliche Aufruf kann mit einem Rec::Mutator erfolgen"/>