@@ -26,6 +26,11 @@ long long DataCounter::countFromFile(const std::string& filename) {
2626 if (reading.empty ()) return ;
2727 if (shouldIncludeReading (reading)) {
2828 count++;
29+ if (!byColumn.empty ()) {
30+ auto it = reading.find (byColumn);
31+ std::string value = (it != reading.end ()) ? it->second : " (missing)" ;
32+ valueCounts[value]++;
33+ }
2934 }
3035 });
3136
@@ -60,6 +65,11 @@ long long DataCounter::countFromStdin() {
6065
6166 if (shouldIncludeReading (reading)) {
6267 count++;
68+ if (!byColumn.empty ()) {
69+ auto it = reading.find (byColumn);
70+ std::string value = (it != reading.end ()) ? it->second : " (missing)" ;
71+ valueCounts[value]++;
72+ }
6373 }
6474 }
6575 } else {
@@ -75,6 +85,11 @@ long long DataCounter::countFromStdin() {
7585 if (reading.empty ()) continue ;
7686 if (shouldIncludeReading (reading)) {
7787 count++;
88+ if (!byColumn.empty ()) {
89+ auto it = reading.find (byColumn);
90+ std::string value = (it != reading.end ()) ? it->second : " (missing)" ;
91+ valueCounts[value]++;
92+ }
7893 }
7994 }
8095 }
@@ -242,7 +257,7 @@ void DataCounter::countFromFileFollow(const std::string& filename) {
242257
243258// ===== Constructor =====
244259
245- DataCounter::DataCounter (int argc, char * argv[]) : followMode(false ) {
260+ DataCounter::DataCounter (int argc, char * argv[]) : followMode(false ), byColumn( " " ), outputFormat( " human " ) {
246261 // Check for help flag first
247262 for (int i = 1 ; i < argc; ++i) {
248263 std::string arg = argv[i];
@@ -252,25 +267,40 @@ DataCounter::DataCounter(int argc, char* argv[]) : followMode(false) {
252267 }
253268 }
254269
255- // Parse --follow flag
256- for (int i = 1 ; i < argc; ++i) {
270+ // Parse count-specific arguments and build filtered argv for CommonArgParser
271+ std::vector<char *> filteredArgv;
272+ for (int i = 0 ; i < argc; ++i) {
257273 std::string arg = argv[i];
258274 if (arg == " --follow" || arg == " -f" ) {
259275 followMode = true ;
276+ continue ;
277+ } else if ((arg == " --by-column" || arg == " -b" ) && i + 1 < argc) {
278+ byColumn = argv[i + 1 ];
279+ i++; // Skip the value
280+ continue ;
281+ } else if ((arg == " --output-format" || arg == " -of" ) && i + 1 < argc) {
282+ outputFormat = argv[i + 1 ];
283+ if (outputFormat != " human" && outputFormat != " csv" && outputFormat != " json" ) {
284+ std::cerr << " Error: --output-format must be 'human', 'csv', or 'json'" << std::endl;
285+ exit (1 );
286+ }
287+ i++; // Skip the value
288+ continue ;
260289 }
290+ filteredArgv.push_back (argv[i]);
261291 }
262292
263- // Parse common flags and collect files
293+ // Parse common flags and collect files using filtered argv
264294 CommonArgParser parser;
265- if (!parser.parse (argc, argv )) {
295+ if (!parser.parse (static_cast < int >(filteredArgv. size ()), filteredArgv. data () )) {
266296 exit (1 );
267297 }
268298
269299 copyFromParser (parser);
270300
271- // Check for unknown options (count-specific: -f/--follow)
272- std::string unknownOpt = CommonArgParser::checkUnknownOptions (argc, argv,
273- { " -f " , " --follow " } );
301+ // Check for unknown options using filtered argv
302+ std::string unknownOpt = CommonArgParser::checkUnknownOptions (
303+ static_cast < int >(filteredArgv. size ()), filteredArgv. data () );
274304 if (!unknownOpt.empty ()) {
275305 std::cerr << " Error: Unknown option '" << unknownOpt << " '" << std::endl;
276306 printCountUsage (argv[0 ]);
@@ -309,7 +339,55 @@ void DataCounter::count() {
309339 }
310340 }
311341
312- std::cout << totalCount << std::endl;
342+ if (!byColumn.empty ()) {
343+ // Convert to vector and sort by count descending
344+ std::vector<std::pair<std::string, long long >> results (valueCounts.begin (), valueCounts.end ());
345+ std::sort (results.begin (), results.end (),
346+ [](const std::pair<std::string, long long >& a, const std::pair<std::string, long long >& b) {
347+ return a.second > b.second ; // Sort by count descending
348+ });
349+
350+ if (outputFormat == " json" ) {
351+ std::cout << " [" ;
352+ bool first = true ;
353+ for (const auto & pair : results) {
354+ if (!first) std::cout << " ," ;
355+ first = false ;
356+ std::cout << " {\" " << byColumn << " \" :\" " << pair.first
357+ << " \" ,\" count\" :" << pair.second << " }" ;
358+ }
359+ std::cout << " ]\n " ;
360+ } else if (outputFormat == " csv" ) {
361+ std::cout << byColumn << " ,count\n " ;
362+ for (const auto & pair : results) {
363+ std::cout << pair.first << " ," << pair.second << " \n " ;
364+ }
365+ } else {
366+ // Human-readable format (default)
367+ // Find max value width for alignment
368+ size_t maxValueWidth = byColumn.length ();
369+ for (const auto & pair : results) {
370+ maxValueWidth = std::max (maxValueWidth, pair.first .length ());
371+ }
372+
373+ std::cout << " Counts by " << byColumn << " :\n\n " ;
374+ std::cout << std::left;
375+ std::cout.width (maxValueWidth + 2 );
376+ std::cout << byColumn;
377+ std::cout << " Count\n " ;
378+ std::cout << std::string (maxValueWidth + 2 + 10 , ' -' ) << " \n " ;
379+
380+ for (const auto & pair : results) {
381+ std::cout.width (maxValueWidth + 2 );
382+ std::cout << pair.first ;
383+ std::cout << pair.second << " \n " ;
384+ }
385+
386+ std::cout << " \n Total: " << totalCount << " reading(s)\n " ;
387+ }
388+ } else {
389+ std::cout << totalCount << std::endl;
390+ }
313391}
314392
315393// ===== Usage printing =====
@@ -322,7 +400,9 @@ void DataCounter::printCountUsage(const char* progName) {
322400 std::cerr << std::endl;
323401 std::cerr << " Options:" << std::endl;
324402 std::cerr << " -if, --input-format <fmt> Input format for stdin: json or csv (default: json)" << std::endl;
403+ std::cerr << " -of, --output-format <fmt> Output format: human (default), csv, or json" << std::endl;
325404 std::cerr << " -f, --follow Follow mode: continuously monitor file/stdin for new data" << std::endl;
405+ std::cerr << " -b, --by-column <col> Show counts per value in the specified column" << std::endl;
326406 std::cerr << " -r, --recursive Recursively process subdirectories" << std::endl;
327407 std::cerr << " -v Verbose output (show progress)" << std::endl;
328408 std::cerr << " -V Very verbose output (show detailed progress)" << std::endl;
@@ -345,6 +425,7 @@ void DataCounter::printCountUsage(const char* progName) {
345425 std::cerr << " " << progName << " count --remove-errors sensor1.out" << std::endl;
346426 std::cerr << " " << progName << " count --only-value type:temperature sensor1.out" << std::endl;
347427 std::cerr << " " << progName << " count --clean sensor.out # exclude empty values" << std::endl;
428+ std::cerr << " " << progName << " count --by-column sensor sensor1.out # count per sensor" << std::endl;
348429 std::cerr << " " << progName << " count --follow sensor.out" << std::endl;
349430 std::cerr << " tail -f sensor.out | " << progName << " count --follow" << std::endl;
350431}
0 commit comments