recutils/utils/recfmt.c
2025-08-31 14:58:19 -04:00

272 lines
6.2 KiB
C

/* recfmt.c - Format records and generate reports. */
/* Copyright (C) 2010-2022 Jose E. Marchesi */
/* 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <string.h>
#include <getopt.h>
#include <stdlib.h>
#include <xalloc.h>
#include <regex.h>
#include <gettext.h>
#define _(str) gettext (str)
#include <rec.h>
#include <recutl.h>
/*
* Global variables
*/
char *recfmt_template = NULL;
/*
* Command line options management.
*/
enum
{
COMMON_ARGS,
FILE_ARG
};
static const struct option GNU_longOptions[] =
{
COMMON_LONG_ARGS,
{"file", required_argument, NULL, FILE_ARG},
{NULL, 0, NULL, 0}
};
/*
* Functions.
*/
void
recutl_print_help (void)
{
/* TRANSLATORS: --help output, recfmt synopsis.
no-wrap */
printf (_("\
Usage: recfmt [OPTION]... [TEMPLATE]\n"));
/* TRANSLATORS: --help output, recfmt arguments.
no-wrap */
fputs(_("\
Apply a template to records read from standard input.\n"), stdout);
puts ("");
/* TRANSLATORS: --help output, recfmt arguments.
no-wrap */
fputs(_("\
-f, --file=FILENAME read the template to apply from a file.\n"),
stdout);
recutl_print_help_common ();
puts ("");
recutl_print_help_footer ();
}
void
recfmt_parse_args (int argc,
char **argv)
{
char c;
int ret;
while ((ret = getopt_long (argc,
argv,
"f:",
GNU_longOptions,
NULL)) != -1)
{
c = ret;
switch (c)
{
COMMON_ARGS_CASES
case FILE_ARG:
case 'f':
/* Read the template from the specified file and store it in
recfmt_template. */
recfmt_template = recutl_read_file (optarg);
if (!recfmt_template)
recutl_fatal (_("can't open file %s for reading.\n"),
optarg);
break;
default:
exit (EXIT_FAILURE);
}
}
/* See if the template is specified in the command line. */
if (optind < argc)
{
if (recfmt_template)
recutl_fatal (_("don't specify a template in the command line\
and -f at the same time.\n"));
if ((argc - optind) != 1)
{
recutl_print_help ();
exit (EXIT_FAILURE);
}
recfmt_template = xstrdup (argv[optind++]);
}
}
char *
recfmt_get_subst (rec_record_t record,
char *str)
{
char *res;
rec_sex_t sex;
sex = rec_sex_new (false);
if (!rec_sex_compile (sex, str))
recutl_fatal (_("invalid expression in a template slot.\n"));
res = rec_sex_eval_str (sex, record);
if (!res)
recutl_fatal (_("error evaluating expression in a template slot.\n"));
rec_sex_destroy (sex);
return res;
}
char *
recfmt_apply_template (rec_record_t record,
char *template)
{
rec_buf_t result_buf;
char *result;
char *tmp;
size_t tmp_size;
size_t result_size;
char *p;
regex_t regexp;
regmatch_t matches;
char *subst_str;
/* Replace occurrences of:
{{Name[N]}}
where Name[N] is the name of a field with an optional subscript.
If the subscript is not present then it is assumed to be 0. If
the contents between {{...}} are not a field name then replace the
slot with the empty string.
*/
if (regcomp (&regexp, "\\{\\{" "[^{}]*" "\\}\\}", REG_EXTENDED) != 0)
recutl_fatal (_("recfmt_apply_template: error compiling regexp.\
Please report this.\n"));
result_buf = rec_buf_new (&result, &result_size);
p = template;
while (*p
&& (regexec (&regexp, p, 1, &matches, 0) == 0)
&& (matches.rm_so != -1))
{
/* Add the prolog, if any. */
if (matches.rm_so > 0)
{
tmp = xmalloc (matches.rm_so + 1);
memcpy (tmp, p, matches.rm_so);
tmp[matches.rm_so] = '\0';
rec_buf_puts (tmp, result_buf);
free (tmp);
}
/* Get the match. */
tmp_size = matches.rm_eo - matches.rm_so - 4;
tmp = xmalloc (tmp_size + 1);
memcpy (tmp, p + matches.rm_so + 2, tmp_size);
tmp[tmp_size] = '\0';
/* Advance p. */
p = p + matches.rm_eo;
/* Get the substitution text and append it to the result. */
subst_str = recfmt_get_subst (record, tmp);
if (subst_str)
{
rec_buf_puts (subst_str, result_buf);
free (subst_str);
}
free (tmp);
}
/* Add the epilog, if any. */
if (*p)
rec_buf_puts (p, result_buf);
rec_buf_close (result_buf);
return result;
}
void
recfmt_process_db (rec_db_t db, char *template)
{
size_t n_rset;
rec_rset_t rset;
rec_record_t record;
char *result;
rec_mset_iterator_t iter;
/* Iterate on all the record sets. */
for (n_rset = 0; n_rset < rec_db_size (db); n_rset++)
{
rset = rec_db_get_rset (db, n_rset);
/* Iterate on all the records. */
iter = rec_mset_iterator (rec_rset_mset (rset));
while (rec_mset_iterator_next (&iter, MSET_RECORD,
(const void**) &record, NULL))
{
/* Apply the template to this record. */
result = recfmt_apply_template (record, template);
if (result && (*result != '\0'))
{
printf ("%s", result);
free (result);
}
}
rec_mset_iterator_free (&iter);
}
}
int
main (int argc, char *argv[])
{
rec_db_t db;
recutl_init ("recfmt");
recfmt_parse_args (argc, argv);
db = recutl_read_db_from_file (NULL);
if (db && recfmt_template)
recfmt_process_db (db, recfmt_template);
return EXIT_SUCCESS;
}