66
77// ===== Private helper methods =====
88
9+ /* *
10+ * Check if a reading should be output based on filters and rejectMode.
11+ * In normal mode: output if passes filters
12+ * In reject mode: output if fails filters (but not for empty/whitespace-only readings)
13+ */
14+ bool SensorDataTransformer::shouldOutputReading (const std::map<std::string, std::string>& reading) {
15+ bool passesFilter = shouldIncludeReading (reading);
16+ return rejectMode ? !passesFilter : passesFilter;
17+ }
18+
919bool SensorDataTransformer::hasActiveFilters () const {
10- return !notEmptyColumns.empty () || !onlyValueFilters.empty () ||
11- !excludeValueFilters.empty () || removeErrors || removeWhitespace ||
20+ return rejectMode || !notEmptyColumns.empty () || !onlyValueFilters.empty () ||
21+ !excludeValueFilters.empty () || !allowedValues. empty () || removeErrors || removeWhitespace ||
1222 removeEmptyJson || minDate > 0 || maxDate > 0 ;
1323}
1424
@@ -138,7 +148,7 @@ void SensorDataTransformer::writeRowsFromFile(const std::string& filename, std::
138148 reading[csvHeaders[i]] = fields[i];
139149 }
140150
141- if (!shouldIncludeReading (reading)) continue ;
151+ if (!shouldOutputReading (reading)) continue ;
142152 writeRow (reading, headers, outfile);
143153 }
144154 } else {
@@ -149,7 +159,7 @@ void SensorDataTransformer::writeRowsFromFile(const std::string& filename, std::
149159 auto readings = JsonParser::parseJsonLine (line);
150160 for (const auto & reading : readings) {
151161 if (reading.empty ()) continue ;
152- if (!shouldIncludeReading (reading)) continue ;
162+ if (!shouldOutputReading (reading)) continue ;
153163 writeRow (reading, headers, outfile);
154164 }
155165 }
@@ -194,7 +204,7 @@ void SensorDataTransformer::writeRowsFromFileJson(const std::string& filename, s
194204 reading[csvHeaders[i]] = fields[i];
195205 }
196206
197- if (!shouldIncludeReading (reading)) continue ;
207+ if (!shouldOutputReading (reading)) continue ;
198208
199209 if (!firstOutput) outfile << " \n " ;
200210 firstOutput = false ;
@@ -217,7 +227,7 @@ void SensorDataTransformer::writeRowsFromFileJson(const std::string& filename, s
217227
218228 for (const auto & reading : readings) {
219229 if (reading.empty ()) continue ;
220- if (shouldIncludeReading (reading)) {
230+ if (shouldOutputReading (reading)) {
221231 filtered.push_back (reading);
222232 }
223233 }
@@ -263,7 +273,7 @@ void SensorDataTransformer::processStdinData(const std::vector<std::string>& lin
263273 reading[csvHeaders[i]] = fields[i];
264274 }
265275
266- if (!shouldIncludeReading (reading)) continue ;
276+ if (!shouldOutputReading (reading)) continue ;
267277 writeRow (reading, headers, outfile);
268278 }
269279 } else {
@@ -273,7 +283,7 @@ void SensorDataTransformer::processStdinData(const std::vector<std::string>& lin
273283 auto readings = JsonParser::parseJsonLine (line);
274284 for (const auto & reading : readings) {
275285 if (reading.empty ()) continue ;
276- if (!shouldIncludeReading (reading)) continue ;
286+ if (!shouldOutputReading (reading)) continue ;
277287 writeRow (reading, headers, outfile);
278288 }
279289 }
@@ -306,7 +316,7 @@ void SensorDataTransformer::processStdinDataJson(const std::vector<std::string>&
306316 reading[csvHeaders[i]] = fields[i];
307317 }
308318
309- if (!shouldIncludeReading (reading)) continue ;
319+ if (!shouldOutputReading (reading)) continue ;
310320
311321 if (!firstOutput) outfile << " \n " ;
312322 firstOutput = false ;
@@ -330,17 +340,22 @@ void SensorDataTransformer::writeRow(const std::map<std::string, std::string>& r
330340
331341// ===== Constructor =====
332342
333- SensorDataTransformer::SensorDataTransformer (int argc, char * argv[])
343+ SensorDataTransformer::SensorDataTransformer (int argc, char * argv[], bool rejectModeParam )
334344 : outputFormat(" " )
335345 , removeWhitespace(false )
346+ , rejectMode(rejectModeParam)
336347 , numThreads(4 )
337348 , usePrototype(false ) {
338349
339350 // Check for help flag first
340351 for (int i = 1 ; i < argc; ++i) {
341352 std::string arg = argv[i];
342353 if (arg == " --help" || arg == " -h" ) {
343- printTransformUsage (argv[0 ]);
354+ if (rejectMode) {
355+ printListRejectsUsage (argv[0 ]);
356+ } else {
357+ printTransformUsage (argv[0 ]);
358+ }
344359 exit (0 );
345360 }
346361 }
@@ -450,7 +465,7 @@ void SensorDataTransformer::transform() {
450465
451466 for (const auto & reading : readings) {
452467 if (reading.empty ()) continue ;
453- if (shouldIncludeReading (reading)) {
468+ if (shouldOutputReading (reading)) {
454469 filtered.push_back (reading);
455470 }
456471 }
@@ -714,3 +729,35 @@ void SensorDataTransformer::printTransformUsage(const char* progName) {
714729 std::cerr << " " << progName << " transform --only-value type:temperature -r -e .out -o output.csv /logs" << std::endl;
715730 std::cerr << " " << progName << " transform --only-value type:temperature --only-value unit:C -o output.csv /logs" << std::endl;
716731}
732+
733+ void SensorDataTransformer::printListRejectsUsage (const char * progName) {
734+ std::cerr << " Usage: " << progName << " list-rejects [options] [<input_file(s)_or_directory(ies)>]" << std::endl;
735+ std::cerr << std::endl;
736+ std::cerr << " List rejected sensor readings (inverse of transform)." << std::endl;
737+ std::cerr << " Outputs readings that would be filtered OUT by the specified filters." << std::endl;
738+ std::cerr << " Accepts the same options as 'transform'." << std::endl;
739+ std::cerr << std::endl;
740+ std::cerr << " Options:" << std::endl;
741+ std::cerr << " -o, --output <file> Output file (default: stdout)" << std::endl;
742+ std::cerr << " -if, --input-format <fmt> Input format for stdin: json or csv (default: json)" << std::endl;
743+ std::cerr << " -of, --output-format <fmt> Output format: json or csv (default: json)" << std::endl;
744+ std::cerr << " -r, --recursive Recursively process subdirectories" << std::endl;
745+ std::cerr << " -v Verbose output (show progress)" << std::endl;
746+ std::cerr << " -V Very verbose output (show detailed progress)" << std::endl;
747+ std::cerr << " -e, --extension <ext> Filter files by extension (e.g., .out or out)" << std::endl;
748+ std::cerr << " -d, --depth <n> Maximum recursion depth (0 = current dir only)" << std::endl;
749+ std::cerr << " --not-empty <column> List rows where column IS empty" << std::endl;
750+ std::cerr << " --only-value <col:val> List rows where column does NOT have this value" << std::endl;
751+ std::cerr << " --exclude-value <col:val> List rows where column HAS this value" << std::endl;
752+ std::cerr << " --allowed-values <col> <values|file> List rows where column is NOT in allowed list" << std::endl;
753+ std::cerr << " --remove-errors List error readings (DS18B20 value=85 or -127)" << std::endl;
754+ std::cerr << " --remove-empty-json List empty JSON input lines" << std::endl;
755+ std::cerr << " --clean Shorthand for --remove-empty-json --not-empty value --remove-errors" << std::endl;
756+ std::cerr << " --min-date <date> List readings before this date" << std::endl;
757+ std::cerr << " --max-date <date> List readings after this date" << std::endl;
758+ std::cerr << std::endl;
759+ std::cerr << " Examples:" << std::endl;
760+ std::cerr << " " << progName << " list-rejects --remove-errors sensor1.out # Show error readings" << std::endl;
761+ std::cerr << " " << progName << " list-rejects --clean sensor1.out # Show filtered readings" << std::endl;
762+ std::cerr << " cat data.out | " << progName << " list-rejects --not-empty value # Show rows with empty value" << std::endl;
763+ }
0 commit comments