From 11e3e3c48ba3b4f406e94c9ce021eb03db7f02a8 Mon Sep 17 00:00:00 2001 From: Liliana Marie Prikler Date: Fri, 14 Jan 2022 16:00:56 +0100 Subject: [PATCH] Add recstat utility. 2022-01-14 Liliana Marie Prikler * utils/recstat.c: New file. * utils/Makefile.am (bin_PROGRAMS): Add recstat. (recstat_SOURCES, recstat_LDADD): New variables. --- utils/Makefile.am | 6 +- utils/recstat.c | 313 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 utils/recstat.c diff --git a/utils/Makefile.am b/utils/Makefile.am index 4538d67..0098ee0 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -24,7 +24,7 @@ else endif bin_PROGRAMS = recinf recsel recins recdel recset recfix recfmt \ - csv2rec rec2csv $(MDB2REC) + recstat csv2rec rec2csv $(MDB2REC) COMMONSRC = recutl.h recutl.c COMMONLIBS = $(top_builddir)/lib/librecutils.la $(top_builddir)/src/librec.la @@ -71,6 +71,10 @@ recfmt_SOURCES = $(COMMONSRC) \ recfmt.c recfmt_LDADD = $(COMMONLIBS) +recstat_SOURCES = $(COMMONSRC) \ + recstat.c +recstat_LDADD = $(COMMONLIBS) + csv2rec_SOURCES = $(COMMONSRC) \ csv2rec.c csv2rec_LDADD = $(COMMONLIBS) \ diff --git a/utils/recstat.c b/utils/recstat.c new file mode 100644 index 0000000..912bb7f --- /dev/null +++ b/utils/recstat.c @@ -0,0 +1,313 @@ +/* recstat.c - Gathering statistics. */ + +/* Copyright (C) 2022 Liliana Marie Prikler */ + +/* This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include +#include +#include +#include +#include +#define _(str) gettext (str) + +#include +#include + +enum +{ + COMMON_ARGS +}; + +static const struct option GNU_longOptions[] = + { + COMMON_LONG_ARGS, + {NULL, 0, NULL, 0} + }; + +static const char* stat_rtd = "\ +%rec: statistic\n\ +%key: name\n\ +%allowed: type expression quick_string\n\ +%type: name regexp /[a-zA-Z%][a-zA-Z0-9_]*/\n\ +%unique: type\n\ +%constraint: #expression + #quick_string <= 1\n\ +"; + +void +recutl_print_help (void) +{ + /* TRANSLATORS: --help output, recstat synopsis. + no-wrap */ + printf (_("\ +Usage: recstat STAT_FILE [FILE]...\n")); + + fputs (_("\ +Aggregate rec statistics.\n"), stdout); + + puts (""); + + recutl_print_help_common (); + + puts (""); + + recutl_print_help_footer (); +} + +void +recstat_parse_args (int argc, + char **argv) +{ + char c; + int ret; + + while ((ret = getopt_long (argc, + argv, + "", + GNU_longOptions, + NULL)) != -1) + { + c = ret; + switch (c) + { + COMMON_ARGS_CASES + default: + exit (EXIT_FAILURE); + } + } + +} + +rec_db_t +recstat_read_stats (int argc, char **argv) +{ + int statsind; + FILE *in; + char *file_name; + rec_db_t db; + rec_parser_t parser; + rec_record_t rtd, file_rtd; + rec_rset_t rset; + rec_buf_t errors; + + rtd = rec_parse_record_str (stat_rtd); + + if (!rtd) + recutl_fatal (_("recstats_read_stats: error parsing type descriptor.\ +Please report this.\n")); + + statsind = optind++; + if (statsind == argc) + recutl_fatal (_("no stats file given.\n")); + + file_name = argv[statsind]; + if (!(in = fopen (file_name, "r"))) + recutl_fatal (_("cannot read file %s\n"), file_name); + + parser = rec_parser_new (in, file_name); + if (!rec_parse_rset (parser, &rset)) + recutl_fatal (_("no statistics found in %s"), file_name); + + /* Reset descriptor for rset */ + rec_rset_set_descriptor (rset, rtd); + rec_rset_set_descriptor_pos (rset, 0); + + db = rec_db_new (); + + if (!db || !rec_db_insert_rset (db, rset, 0)) + recutl_fatal (_("could not allocate statistics database")); + + rec_parser_destroy (parser); + return db; +} + +int +recstat_process (rec_db_t stats, rec_db_t db) +{ + rec_rset_t results; + rec_record_t result, stat; + rec_mset_iterator_t iterator; + rec_writer_t writer; + int err; + + /* unsupported query fields, set to default values */ + const char *password, *join; + size_t index, random, flags; + rec_fex_t fex, group_by_fields, sort_by_fields; + password = join = NULL; + index = random = flags = 0; + fex = group_by_fields = sort_by_fields = NULL; + + result = rec_record_new (); + results = rec_rset_new (); + if (!result || !results) + { + recutl_error (_("out of memory\n")); + return EXIT_FAILURE; + } + + { + char *errors; + size_t errors_size; + rec_buf_t buf; + buf = rec_buf_new (&errors, &errors_size); + + if (rec_int_check_db (stats, true, false, buf) || + rec_int_check_db (db, true, false, buf)) + { + rec_buf_close (buf); + fprintf (stderr, "%s", errors); + return EXIT_FAILURE; + } + rec_buf_close (buf); + } + + err = 0; + { + rec_rset_t rset; + rec_mset_t mset; + + rset = rec_db_get_rset (stats, 0); + mset = rec_rset_mset (rset); + iterator = rec_mset_iterator (mset); + } + + while (rec_mset_iterator_next (&iterator, + MSET_RECORD, + &stat, + NULL)) + { + rec_field_t field, res_field; + const char *name, *type, *quick, *ex; + char *res_field_name; + char *res_field_value; + rec_sex_t sex; + rec_rset_t rset; + + field = rec_record_get_field_by_name (stat, "name", 0); + name = rec_field_value (field); + + field = rec_record_get_field_by_name (stat, "type", 0); + type = field ? rec_field_value (field) : NULL; + + field = rec_record_get_field_by_name (stat, "quick_string", 0); + quick = field ? rec_field_value (field) : NULL; + + field = rec_record_get_field_by_name (stat, "expression", 0); + if (field) + { + ex = rec_field_value (field); + sex = rec_sex_new (false); + if (!rec_sex_compile (sex, ex)) + { + recutl_error (_("invalid selection expression: %s"), ex); + err = 1; + break; + } + } + else + sex = NULL; + + rset = rec_db_query (db, + type, + join, + &index, + sex, + quick, + random, + fex, + password, + group_by_fields, + sort_by_fields, + flags); + + res_field_name = xstrdup (name); + /* XXX: support all rec aggregates */ + asprintf (&res_field_value, "%zu", rec_rset_num_records (rset)); + + if (!res_field_name || !res_field_value) + { + recutl_error (_("out of memory\n")); + err = 1; + break; + } + + res_field = rec_field_new (res_field_name, res_field_value); + + if (!rec_mset_append (rec_record_mset (result), + MSET_FIELD, + (void*)res_field, + MSET_FIELD)) + { + recutl_error (_("out of memory\n")); + err = 1; + break; + } + } + + rec_mset_iterator_free (&iterator); + + if (err) + { + rec_record_destroy (result); + rec_rset_destroy (results); + return err; + } + + if (!rec_mset_append (rec_rset_mset (results), + MSET_RECORD, + (void*)result, + MSET_ANY)) + { + recutl_error (_("out of memory\n")); + rec_record_destroy (result); + rec_rset_destroy (results); + return EXIT_FAILURE; + } + + + writer = rec_writer_new (stdout); + rec_writer_set_skip_comments (writer, true); + rec_writer_set_mode (writer, REC_WRITER_NORMAL); + rec_write_rset (writer, results); + rec_writer_destroy (writer); + + rec_rset_destroy (results); +} + +int +main (int argc, char **argv) +{ + int res; + rec_db_t stats, db; + + res = 0; + + recutl_init ("recstat"); + + recstat_parse_args (argc, argv); + + stats = recstat_read_stats (argc, argv); + db = recutl_build_db (argc, argv); + + if (!recstat_process (stats, db)) + res = 1; + + rec_db_destroy (stats); + rec_db_destroy (db); + return res; +} -- 2.34.0