33
44#include < iostream>
55#include < fstream>
6+ #include < sstream>
67#include < string>
78#include < vector>
89#include < map>
@@ -18,6 +19,7 @@ class DataReader {
1819 long long maxDate;
1920 int verbosity;
2021 std::string inputFormat; // "json" or "csv"
22+ int tailLines; // 0 = read all, >0 = read only last n lines
2123
2224 bool passesDateFilter (const std::map<std::string, std::string>& reading) const {
2325 if (minDate <= 0 && maxDate <= 0 ) return true ;
@@ -26,8 +28,8 @@ class DataReader {
2628 }
2729
2830public:
29- DataReader (long long minDate = 0 , long long maxDate = 0 , int verbosity = 0 , const std::string& format = " json" )
30- : minDate(minDate), maxDate(maxDate), verbosity(verbosity), inputFormat(format) {}
31+ DataReader (long long minDate = 0 , long long maxDate = 0 , int verbosity = 0 , const std::string& format = " json" , int tailLines = 0 )
32+ : minDate(minDate), maxDate(maxDate), verbosity(verbosity), inputFormat(format), tailLines(tailLines) {}
3133
3234 // Internal helper to process a stream (CSV or JSON format)
3335 template <typename Callback>
@@ -94,16 +96,81 @@ class DataReader {
9496 void processFile (const std::string& filename, Callback callback) {
9597 if (verbosity >= 1 ) {
9698 std::cout << " Processing file: " << filename << std::endl;
99+ if (tailLines > 0 ) {
100+ std::cout << " (reading last " << tailLines << " lines only)" << std::endl;
101+ }
97102 }
98103
99- std::ifstream infile (filename);
100- if (!infile) {
101- std::cerr << " Warning: Cannot open file: " << filename << std::endl;
102- return ;
103- }
104+ bool isCSV = FileUtils::isCsvFile (filename);
104105
105- processStream (infile, FileUtils::isCsvFile (filename), callback, filename);
106- infile.close ();
106+ if (tailLines > 0 ) {
107+ // Use tail mode
108+ if (isCSV) {
109+ // CSV: read header from file, then process tail lines
110+ std::ifstream headerFile (filename);
111+ if (!headerFile) {
112+ std::cerr << " Warning: Cannot open file: " << filename << std::endl;
113+ return ;
114+ }
115+ std::string headerLine;
116+ std::vector<std::string> csvHeaders;
117+ if (std::getline (headerFile, headerLine) && !headerLine.empty ()) {
118+ bool needMore = false ;
119+ csvHeaders = CsvParser::parseCsvLine (headerFile, headerLine, needMore);
120+ }
121+ headerFile.close ();
122+
123+ // Read tail lines (+1 to account for header potentially being in tail)
124+ auto lines = FileUtils::readTailLines (filename, tailLines + 1 );
125+ int lineNum = 0 ;
126+
127+ for (const auto & line : lines) {
128+ lineNum++;
129+ if (line.empty ()) continue ;
130+ // Skip if this is the header line
131+ if (line == headerLine) continue ;
132+
133+ auto fields = CsvParser::parseCsvLine (line);
134+ if (fields.empty ()) continue ;
135+
136+ std::map<std::string, std::string> reading;
137+ for (size_t i = 0 ; i < std::min (csvHeaders.size (), fields.size ()); ++i) {
138+ reading[csvHeaders[i]] = fields[i];
139+ }
140+
141+ if (!passesDateFilter (reading)) continue ;
142+
143+ callback (reading, lineNum, filename);
144+ }
145+ } else {
146+ // JSON: just process tail lines
147+ auto lines = FileUtils::readTailLines (filename, tailLines);
148+ int lineNum = 0 ;
149+
150+ for (const auto & line : lines) {
151+ lineNum++;
152+ if (line.empty ()) continue ;
153+
154+ auto readings = JsonParser::parseJsonLine (line);
155+ for (const auto & reading : readings) {
156+ if (reading.empty ()) continue ;
157+
158+ if (!passesDateFilter (reading)) continue ;
159+
160+ callback (reading, lineNum, filename);
161+ }
162+ }
163+ }
164+ } else {
165+ std::ifstream infile (filename);
166+ if (!infile) {
167+ std::cerr << " Warning: Cannot open file: " << filename << std::endl;
168+ return ;
169+ }
170+
171+ processStream (infile, isCSV, callback, filename);
172+ infile.close ();
173+ }
107174 }
108175};
109176
0 commit comments