Skip to content

Commit 2f10cf0

Browse files
committed
--exclude-value
1 parent 87771a7 commit 2f10cf0

File tree

3 files changed

+137
-3
lines changed

3 files changed

+137
-3
lines changed

debian/sensor-data.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ Skip rows where column is empty (can be used multiple times).
7373
.BI \-\-only\-value " col:val"
7474
Only include rows where column has specific value (can be used multiple times).
7575
.TP
76+
.BI \-\-exclude\-value " col:val"
77+
Exclude rows where column has specific value (can be used multiple times).
78+
.TP
7679
.B \-\-remove\-errors
7780
Remove error readings (DS18B20 value=85 or \-127).
7881
.TP

include/sensor_data_converter.h

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ class SensorDataConverter {
3636
int numThreads;
3737
bool usePrototype;
3838
std::set<std::string> notEmptyColumns; // Columns that must not be empty
39-
std::map<std::string, std::string> onlyValueFilters; // Column:value pairs for filtering
39+
std::map<std::string, std::string> onlyValueFilters; // Column:value pairs for filtering (include)
40+
std::map<std::string, std::string> excludeValueFilters; // Column:value pairs for filtering (exclude)
4041
int verbosity; // 0 = normal, 1 = verbose (-v), 2 = very verbose (-V)
4142
bool removeErrors; // Remove error readings (e.g., DS18B20 temperature = 85)
4243
bool removeWhitespace; // Remove extra whitespace from output (compact format)
@@ -48,7 +49,7 @@ class SensorDataConverter {
4849
// Check if any filtering is active (affects whether we can pass-through JSON lines)
4950
bool hasActiveFilters() const {
5051
return !notEmptyColumns.empty() || !onlyValueFilters.empty() ||
51-
removeErrors || removeWhitespace || minDate > 0 || maxDate > 0;
52+
!excludeValueFilters.empty() || removeErrors || removeWhitespace || minDate > 0 || maxDate > 0;
5253
}
5354

5455
// Execute sc-prototype command and parse columns from JSON output
@@ -426,7 +427,7 @@ class SensorDataConverter {
426427
}
427428
}
428429

429-
// Check value filters
430+
// Check value filters (include)
430431
for (const auto& filter : onlyValueFilters) {
431432
auto it = reading.find(filter.first);
432433
if (it == reading.end() || it->second != filter.second) {
@@ -442,6 +443,18 @@ class SensorDataConverter {
442443
}
443444
}
444445

446+
// Check value filters (exclude)
447+
for (const auto& filter : excludeValueFilters) {
448+
auto it = reading.find(filter.first);
449+
if (it != reading.end() && it->second == filter.second) {
450+
if (verbosity >= 2) {
451+
std::cerr << " Skipping row: column '" << filter.first << "' has excluded value '"
452+
<< filter.second << "'" << std::endl;
453+
}
454+
return false;
455+
}
456+
}
457+
445458
// Check for error readings
446459
if (removeErrors && ErrorDetector::isErrorReading(reading)) {
447460
if (verbosity >= 2) {
@@ -629,6 +642,22 @@ class SensorDataConverter {
629642
std::cerr << "Error: " << arg << " requires an argument" << std::endl;
630643
exit(1);
631644
}
645+
} else if (arg == "--exclude-value") {
646+
if (i + 1 < argc) {
647+
++i;
648+
std::string filter = argv[i];
649+
size_t colonPos = filter.find(':');
650+
if (colonPos == std::string::npos || colonPos == 0 || colonPos == filter.length() - 1) {
651+
std::cerr << "Error: --exclude-value requires format 'column:value'" << std::endl;
652+
exit(1);
653+
}
654+
std::string column = filter.substr(0, colonPos);
655+
std::string value = filter.substr(colonPos + 1);
656+
excludeValueFilters[column] = value;
657+
} else {
658+
std::cerr << "Error: " << arg << " requires an argument" << std::endl;
659+
exit(1);
660+
}
632661
} else if (arg == "-F" || arg == "--output-format") {
633662
if (i + 1 < argc) {
634663
++i;
@@ -988,6 +1017,7 @@ class SensorDataConverter {
9881017
std::cerr << " --use-prototype Use sc-prototype command to define columns" << std::endl;
9891018
std::cerr << " --not-empty <column> Skip rows where column is empty (can be used multiple times)" << std::endl;
9901019
std::cerr << " --only-value <col:val> Only include rows where column has specific value (can be used multiple times)" << std::endl;
1020+
std::cerr << " --exclude-value <col:val> Exclude rows where column has specific value (can be used multiple times)" << std::endl;
9911021
std::cerr << " --remove-errors Remove error readings (DS18B20 value=85 or -127)" << std::endl;
9921022
std::cerr << " --remove-whitespace Remove extra whitespace from output (compact format)" << std::endl;
9931023
std::cerr << " --min-date <date> Filter readings after this date (Unix timestamp, ISO date, or DD/MM/YYYY)" << std::endl;

tests/test_convert.sh

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,107 @@ else
802802
fi
803803
rm -f output.json
804804

805+
# Test 25: --exclude-value filter (exclude specific sensor)
806+
echo ""
807+
echo "Test 25: --exclude-value filter"
808+
result=$(cat <<'EOF' | ./sensor-data convert --exclude-value sensor:dht22
809+
[{"sensor": "ds18b20", "value": "22.5"}]
810+
[{"sensor": "dht22", "value": "45"}]
811+
[{"sensor": "ds18b20", "value": "23.0"}]
812+
EOF
813+
)
814+
# Should exclude dht22 readings, keep ds18b20
815+
if echo "$result" | grep -q "ds18b20" && ! echo "$result" | grep -q "dht22"; then
816+
echo " ✓ PASS"
817+
PASSED=$((PASSED + 1))
818+
else
819+
echo " ✗ FAIL - Expected ds18b20 readings only (dht22 excluded)"
820+
echo " Got: $result"
821+
FAILED=$((FAILED + 1))
822+
fi
823+
824+
# Test 25a: --exclude-value with multiple exclusions
825+
echo ""
826+
echo "Test 25a: --exclude-value with multiple exclusions"
827+
result=$(cat <<'EOF' | ./sensor-data convert --exclude-value sensor:dht22 --exclude-value sensor:bmp280
828+
[{"sensor": "ds18b20", "value": "22.5"}]
829+
[{"sensor": "dht22", "value": "45"}]
830+
[{"sensor": "bmp280", "value": "1013"}]
831+
[{"sensor": "ds18b20", "value": "23.0"}]
832+
EOF
833+
)
834+
# Should exclude both dht22 and bmp280
835+
if echo "$result" | grep -q "ds18b20" && ! echo "$result" | grep -q "dht22\|bmp280"; then
836+
echo " ✓ PASS"
837+
PASSED=$((PASSED + 1))
838+
else
839+
echo " ✗ FAIL - Expected only ds18b20 (dht22 and bmp280 excluded)"
840+
echo " Got: $result"
841+
FAILED=$((FAILED + 1))
842+
fi
843+
844+
# Test 25b: --exclude-value combined with --only-value
845+
echo ""
846+
echo "Test 25b: --exclude-value combined with --only-value"
847+
result=$(cat <<'EOF' | ./sensor-data convert --only-value sensor:ds18b20 --exclude-value value:85
848+
[{"sensor": "ds18b20", "value": "22.5"}]
849+
[{"sensor": "ds18b20", "value": "85"}]
850+
[{"sensor": "dht22", "value": "45"}]
851+
[{"sensor": "ds18b20", "value": "23.0"}]
852+
EOF
853+
)
854+
# Should include only ds18b20 but exclude the one with value=85
855+
count=$(echo "$result" | grep -c "ds18b20" || true)
856+
if [ "$count" -eq 2 ] && ! echo "$result" | grep -q '"value": "85"'; then
857+
echo " ✓ PASS"
858+
PASSED=$((PASSED + 1))
859+
else
860+
echo " ✗ FAIL - Expected 2 ds18b20 readings (excluding value=85)"
861+
echo " Got: $result"
862+
FAILED=$((FAILED + 1))
863+
fi
864+
865+
# Test 25c: --exclude-value on CSV output
866+
echo ""
867+
echo "Test 25c: --exclude-value with CSV output"
868+
result=$(cat <<'EOF' | ./sensor-data convert -F csv --exclude-value sensor:dht22
869+
[{"sensor": "ds18b20", "value": "22.5"}]
870+
[{"sensor": "dht22", "value": "45"}]
871+
[{"sensor": "ds18b20", "value": "23.0"}]
872+
EOF
873+
)
874+
# Should have CSV with ds18b20 rows only
875+
ds_count=$(echo "$result" | grep -c "ds18b20" || true)
876+
dht_count=$(echo "$result" | grep -c "dht22" || true)
877+
if [ "$ds_count" -eq 2 ] && [ "$dht_count" -eq 0 ]; then
878+
echo " ✓ PASS"
879+
PASSED=$((PASSED + 1))
880+
else
881+
echo " ✗ FAIL - Expected 2 ds18b20 rows in CSV, 0 dht22"
882+
echo " Got: $result"
883+
FAILED=$((FAILED + 1))
884+
fi
885+
886+
# Test 25d: --exclude-value with file input
887+
echo ""
888+
echo "Test 25d: --exclude-value with file input"
889+
mkdir -p testdir
890+
cat > testdir/test.out << 'EOF'
891+
[{"sensor": "ds18b20", "value": "22.5"}, {"sensor": "dht22", "value": "45"}]
892+
[{"sensor": "bmp280", "value": "1013"}, {"sensor": "ds18b20", "value": "23.0"}]
893+
EOF
894+
result=$(./sensor-data convert -F json --exclude-value sensor:dht22 --exclude-value sensor:bmp280 testdir/test.out)
895+
rm -rf testdir
896+
# Should exclude dht22 and bmp280, keep only ds18b20
897+
if echo "$result" | grep -q "ds18b20" && ! echo "$result" | grep -q "dht22\|bmp280"; then
898+
echo " ✓ PASS"
899+
PASSED=$((PASSED + 1))
900+
else
901+
echo " ✗ FAIL - Expected only ds18b20 readings"
902+
echo " Got: $result"
903+
FAILED=$((FAILED + 1))
904+
fi
905+
805906
# Summary
806907
echo ""
807908
echo "================================"

0 commit comments

Comments
 (0)