Skip to content

Commit a18a589

Browse files
committed
--allowed-values
1 parent 8ce9901 commit a18a589

File tree

5 files changed

+158
-2
lines changed

5 files changed

+158
-2
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ sensor-data transform -r -e .out /path/to/logs/ output.csv
7272
- `--remove-empty-json` - Remove empty JSON input lines (e.g., `[{}]`, `[]`)
7373
- `--not-empty <column>` - Skip rows where column is empty
7474
- `--only-value <col:val>` - Only include rows where column equals value
75+
- `--allowed-values <column> <values|file>` - Only include rows where column is in allowed values
7576
- `--clean` - Shorthand for `--remove-empty-json --not-empty value --remove-errors`
7677
- `-v` - Verbose output
7778
- `-V` - Very verbose output
@@ -108,6 +109,7 @@ sensor-data count -r -e .out /path/to/logs/
108109
- `--not-empty <column>` - Skip rows where column is empty
109110
- `--only-value <col:val>` - Only include rows where column equals value
110111
- `--exclude-value <col:val>` - Exclude rows where column equals value
112+
- `--allowed-values <column> <values|file>` - Only include rows where column is in allowed values
111113
- `--clean` - Shorthand for `--remove-empty-json --not-empty value --remove-errors`
112114
- `-v` - Verbose output
113115
- `-V` - Very verbose output
@@ -167,6 +169,7 @@ sensor-data stats --clean input.out
167169
- `-f, --follow` - Follow mode: continuously read input and update stats
168170
- `--only-value <col:val>` - Only include rows where column equals value
169171
- `--exclude-value <col:val>` - Exclude rows where column equals value
172+
- `--allowed-values <column> <values|file>` - Only include rows where column is in allowed values
170173
- `--not-empty <column>` - Skip rows where column is empty
171174
- `--remove-empty-json` - Remove empty JSON input lines
172175
- `--remove-errors` - Remove error readings (DS18B20 value=85 or -127)

debian/sensor-data.1

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ Only include rows where column has specific value (can be used multiple times).
8484
.BI \-\-exclude\-value " col:val"
8585
Exclude rows where column has specific value (can be used multiple times).
8686
.TP
87+
.BI \-\-allowed\-values " column" " values|file"
88+
Only include rows where column value is in the allowed list.
89+
The second argument can be comma-separated values (e.g., ds18b20,dht22)
90+
or a file path with one value per line.
91+
.TP
8792
.B \-\-remove\-errors
8893
Remove error readings (DS18B20 value=85 or \-127).
8994
.TP
@@ -133,6 +138,11 @@ Only include rows where column has specific value (can be used multiple times).
133138
.BI \-\-exclude\-value " col:val"
134139
Exclude rows where column has specific value (can be used multiple times).
135140
.TP
141+
.BI \-\-allowed\-values " column" " values|file"
142+
Only include rows where column value is in the allowed list.
143+
The second argument can be comma-separated values (e.g., ds18b20,dht22)
144+
or a file path with one value per line.
145+
.TP
136146
.B \-\-remove\-errors
137147
Remove error readings (DS18B20 value=85 or \-127).
138148
.TP
@@ -189,6 +199,11 @@ Only include rows where column has specific value (can be used multiple times).
189199
.BI \-\-exclude\-value " col:val"
190200
Exclude rows where column has specific value (can be used multiple times).
191201
.TP
202+
.BI \-\-allowed\-values " column" " values|file"
203+
Only include rows where column value is in the allowed list.
204+
The second argument can be comma-separated values (e.g., ds18b20,dht22)
205+
or a file path with one value per line.
206+
.TP
192207
.BI \-\-not\-empty " column"
193208
Skip rows where column is empty (can be used multiple times).
194209
.TP

include/command_base.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class CommandBase {
4343
std::set<std::string> notEmptyColumns;
4444
std::map<std::string, std::set<std::string>> onlyValueFilters;
4545
std::map<std::string, std::set<std::string>> excludeValueFilters;
46+
std::map<std::string, std::set<std::string>> allowedValues;
4647

4748
// Constructor with default values
4849
CommandBase()
@@ -124,6 +125,22 @@ class CommandBase {
124125
}
125126
}
126127

128+
// Check allowed values filters
129+
for (const auto& filter : allowedValues) {
130+
auto it = reading.find(filter.first);
131+
if (it == reading.end() || filter.second.count(it->second) == 0) {
132+
if (verbosity >= 2) {
133+
if (it == reading.end()) {
134+
std::cerr << " Skipping row: missing column '" << filter.first << "'" << std::endl;
135+
} else {
136+
std::cerr << " Skipping row: column '" << filter.first << "' value '"
137+
<< it->second << "' not in allowed values" << std::endl;
138+
}
139+
}
140+
return false;
141+
}
142+
}
143+
127144
// Check for error readings
128145
if (removeErrors && ErrorDetector::isErrorReading(reading)) {
129146
if (verbosity >= 2) {
@@ -212,6 +229,7 @@ class CommandBase {
212229
hasInputFiles = !inputFiles.empty();
213230
onlyValueFilters = parser.getOnlyValueFilters();
214231
excludeValueFilters = parser.getExcludeValueFilters();
232+
allowedValues = parser.getAllowedValues();
215233
notEmptyColumns = parser.getNotEmptyColumns();
216234
removeEmptyJson = parser.getRemoveEmptyJson();
217235
removeErrors = parser.getRemoveErrors();
@@ -256,6 +274,12 @@ class CommandBase {
256274
}
257275
std::cerr << std::endl;
258276
}
277+
if (!allowedValues.empty()) {
278+
for (const auto& filter : allowedValues) {
279+
std::cerr << "Allowed values for '" << filter.first << "': "
280+
<< filter.second.size() << " value(s)" << std::endl;
281+
}
282+
}
259283
}
260284
}
261285

include/common_arg_parser.h

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define COMMON_ARG_PARSER_H
33

44
#include <iostream>
5+
#include <fstream>
56
#include <string>
67
#include <algorithm>
78
#include <map>
@@ -22,6 +23,7 @@ class CommonArgParser {
2223
std::vector<std::string> inputFiles;
2324
std::map<std::string, std::set<std::string>> onlyValueFilters;
2425
std::map<std::string, std::set<std::string>> excludeValueFilters;
26+
std::map<std::string, std::set<std::string>> allowedValues;
2527
std::set<std::string> notEmptyColumns;
2628
bool removeEmptyJson;
2729
bool removeErrors;
@@ -134,6 +136,51 @@ class CommonArgParser {
134136
std::cerr << "Error: --exclude-value requires an argument" << std::endl;
135137
return false;
136138
}
139+
} else if (arg == "--allowed-values") {
140+
if (i + 2 < argc) {
141+
++i;
142+
std::string column = argv[i];
143+
++i;
144+
std::string valuesArg = argv[i];
145+
// Check if valuesArg is a file or comma-separated values
146+
std::ifstream file(valuesArg);
147+
if (file.good()) {
148+
// It's a file - read values line by line
149+
std::string line;
150+
while (std::getline(file, line)) {
151+
// Trim whitespace
152+
size_t start = line.find_first_not_of(" \t\r\n");
153+
size_t end = line.find_last_not_of(" \t\r\n");
154+
if (start != std::string::npos && end != std::string::npos) {
155+
allowedValues[column].insert(line.substr(start, end - start + 1));
156+
}
157+
}
158+
} else {
159+
// Treat as comma-separated values
160+
size_t pos = 0;
161+
while (pos < valuesArg.length()) {
162+
size_t commaPos = valuesArg.find(',', pos);
163+
if (commaPos == std::string::npos) {
164+
commaPos = valuesArg.length();
165+
}
166+
std::string val = valuesArg.substr(pos, commaPos - pos);
167+
// Trim whitespace
168+
size_t start = val.find_first_not_of(" \t");
169+
size_t end = val.find_last_not_of(" \t");
170+
if (start != std::string::npos && end != std::string::npos) {
171+
allowedValues[column].insert(val.substr(start, end - start + 1));
172+
}
173+
pos = commaPos + 1;
174+
}
175+
}
176+
if (allowedValues[column].empty()) {
177+
std::cerr << "Error: --allowed-values requires at least one value" << std::endl;
178+
return false;
179+
}
180+
} else {
181+
std::cerr << "Error: --allowed-values requires <column> and <values|file>" << std::endl;
182+
return false;
183+
}
137184
} else if (arg == "-c" || arg == "--column") {
138185
// Skip this flag and its argument - handled by StatsAnalyser
139186
if (i + 1 < argc) {
@@ -208,6 +255,7 @@ class CommonArgParser {
208255
const std::vector<std::string>& getInputFiles() const { return inputFiles; }
209256
const std::map<std::string, std::set<std::string>>& getOnlyValueFilters() const { return onlyValueFilters; }
210257
const std::map<std::string, std::set<std::string>>& getExcludeValueFilters() const { return excludeValueFilters; }
258+
const std::map<std::string, std::set<std::string>>& getAllowedValues() const { return allowedValues; }
211259
const std::set<std::string>& getNotEmptyColumns() const { return notEmptyColumns; }
212260
bool getRemoveEmptyJson() const { return removeEmptyJson; }
213261
bool getRemoveErrors() const { return removeErrors; }
@@ -231,15 +279,16 @@ class CommonArgParser {
231279

232280
// Common filtering options
233281
static const std::set<std::string> filterOptions = {
234-
"--not-empty", "--only-value", "--exclude-value",
282+
"--not-empty", "--only-value", "--exclude-value", "--allowed-values",
235283
"--remove-errors", "--remove-empty-json", "--clean"
236284
};
237285

238286
// Options that take arguments (need to skip the next arg)
287+
// Note: --allowed-values takes TWO args but we handle that specially
239288
static const std::set<std::string> optionsWithArgs = {
240289
"-if", "--input-format", "-e", "--extension", "-d", "--depth",
241290
"--min-date", "--max-date", "--not-empty", "--only-value",
242-
"--exclude-value", "-o", "--output", "-of", "--output-format",
291+
"--exclude-value", "--allowed-values", "-o", "--output", "-of", "--output-format",
243292
"-c", "--column"
244293
};
245294

tests/test_count.sh

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,71 @@ else
453453
FAILED=$((FAILED + 1))
454454
fi
455455

456+
# Test: --allowed-values with comma-separated list
457+
echo ""
458+
echo "Test: --allowed-values with comma-separated values"
459+
result=$(cat <<'EOF' | ./sensor-data count --allowed-values sensor ds18b20,dht22
460+
[{"sensor": "ds18b20", "value": "22.5"}]
461+
[{"sensor": "dht22", "value": "45"}]
462+
[{"sensor": "bmp280", "value": "1013"}]
463+
[{"sensor": "ds18b20", "value": "23.0"}]
464+
EOF
465+
)
466+
if [ "$result" = "3" ]; then
467+
echo " ✓ PASS"
468+
PASSED=$((PASSED + 1))
469+
else
470+
echo " ✗ FAIL"
471+
echo " Expected: 3 (ds18b20 x2 + dht22 x1)"
472+
echo " Got: $result"
473+
FAILED=$((FAILED + 1))
474+
fi
475+
476+
# Test: --allowed-values with file
477+
echo ""
478+
echo "Test: --allowed-values with file of values"
479+
# Create a temp file with allowed values
480+
tmpfile=$(mktemp)
481+
echo "ds18b20" > "$tmpfile"
482+
echo "dht22" >> "$tmpfile"
483+
result=$(cat <<'EOF' | ./sensor-data count --allowed-values sensor "$tmpfile"
484+
[{"sensor": "ds18b20", "value": "22.5"}]
485+
[{"sensor": "dht22", "value": "45"}]
486+
[{"sensor": "bmp280", "value": "1013"}]
487+
[{"sensor": "ds18b20", "value": "23.0"}]
488+
EOF
489+
)
490+
rm -f "$tmpfile"
491+
if [ "$result" = "3" ]; then
492+
echo " ✓ PASS"
493+
PASSED=$((PASSED + 1))
494+
else
495+
echo " ✗ FAIL"
496+
echo " Expected: 3 (ds18b20 x2 + dht22 x1)"
497+
echo " Got: $result"
498+
FAILED=$((FAILED + 1))
499+
fi
500+
501+
# Test: --allowed-values with single value
502+
echo ""
503+
echo "Test: --allowed-values with single value"
504+
result=$(cat <<'EOF' | ./sensor-data count --allowed-values sensor ds18b20
505+
[{"sensor": "ds18b20", "value": "22.5"}]
506+
[{"sensor": "dht22", "value": "45"}]
507+
[{"sensor": "bmp280", "value": "1013"}]
508+
[{"sensor": "ds18b20", "value": "23.0"}]
509+
EOF
510+
)
511+
if [ "$result" = "2" ]; then
512+
echo " ✓ PASS"
513+
PASSED=$((PASSED + 1))
514+
else
515+
echo " ✗ FAIL"
516+
echo " Expected: 2 (ds18b20 x2)"
517+
echo " Got: $result"
518+
FAILED=$((FAILED + 1))
519+
fi
520+
456521
# Summary
457522
echo ""
458523
echo "================================"

0 commit comments

Comments
 (0)