Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package csvprintf for openSUSE:Factory checked in at 2021-12-22 20:18:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/csvprintf (Old) and /work/SRC/openSUSE:Factory/.csvprintf.new.2520 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "csvprintf" Wed Dec 22 20:18:03 2021 rev:10 rq:941990 version:1.3.1 Changes: -------- --- /work/SRC/openSUSE:Factory/csvprintf/csvprintf.changes 2021-12-10 21:52:41.674900777 +0100 +++ /work/SRC/openSUSE:Factory/.csvprintf.new.2520/csvprintf.changes 2021-12-22 20:19:06.359876281 +0100 @@ -1,0 +2,10 @@ +Tue Dec 14 21:16:11 UTC 2021 - Archie Cobbs <archie.co...@gmail.com> + +- Update to release 1.3.1 + + Added "-c" flag for explicit column names + + Added "-n" flag that only reads column names + + Added "-p" flag for prefixing names + + Omit special variable names in Bash mode + + Fixed build error on systems without 'u_char' defined + +------------------------------------------------------------------- Old: ---- csvprintf-1.3.0.obscpio New: ---- csvprintf-1.3.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ csvprintf.spec ++++++ --- /var/tmp/diff_new_pack.89w0Za/_old 2021-12-22 20:19:06.807876491 +0100 +++ /var/tmp/diff_new_pack.89w0Za/_new 2021-12-22 20:19:06.811876493 +0100 @@ -17,7 +17,7 @@ Name: csvprintf -Version: 1.3.0 +Version: 1.3.1 Release: 0 Summary: Simple CSV file parser for the UNIX command line License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.89w0Za/_old 2021-12-22 20:19:06.839876506 +0100 +++ /var/tmp/diff_new_pack.89w0Za/_new 2021-12-22 20:19:06.843876508 +0100 @@ -2,8 +2,8 @@ <service mode="localonly" name="obs_scm"> <param name="scm">git</param> <param name="url">https://github.com/archiecobbs/csvprintf</param> - <param name="versionformat">1.3.0</param> - <param name="revision">1.3.0</param> + <param name="versionformat">1.3.1</param> + <param name="revision">1.3.1</param> <param name="filename">csvprintf</param> </service> <service mode="buildtime" name="tar"/> ++++++ csvprintf-1.3.0.obscpio -> csvprintf-1.3.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/CHANGES new/csvprintf-1.3.1/CHANGES --- old/csvprintf-1.3.0/CHANGES 2021-12-09 20:47:36.000000000 +0100 +++ new/csvprintf-1.3.1/CHANGES 2021-12-14 22:11:31.000000000 +0100 @@ -1,3 +1,11 @@ +Version 1.3.1 released December 14, 2021 + + - Added "-c" flag for explicit column names + - Added "-n" flag that only reads column names + - Added "-p" flag for prefixing names + - Omit special variable names in Bash mode + - Fixed build error on systems without 'u_char' defined + Version 1.3.0 released December 9, 2021 - Added "-b" flag for new Bash output mode diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/Makefile.am new/csvprintf-1.3.1/Makefile.am --- old/csvprintf-1.3.0/Makefile.am 2021-12-09 20:47:36.000000000 +0100 +++ new/csvprintf-1.3.1/Makefile.am 2021-12-14 22:11:31.000000000 +0100 @@ -30,7 +30,14 @@ .PHONY: tests tests: csvprintf - cd tests && ./run.sh + @echo '************' + @echo 'TEST SUITE 1' + @echo '************' + @cd tests && ./run.sh + @echo '************' + @echo 'TEST SUITE 2' + @echo '************' + @cd tests && ./run2.sh subst= sed \ -e 's|@PACKAGE[@]|$(PACKAGE)|g' \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/configure.ac new/csvprintf-1.3.1/configure.ac --- old/csvprintf-1.3.0/configure.ac 2021-12-09 20:47:36.000000000 +0100 +++ new/csvprintf-1.3.1/configure.ac 2021-12-14 22:11:31.000000000 +0100 @@ -16,11 +16,11 @@ # under the License. # -AC_INIT([csvprintf - Simple CSV file parser for the UNIX command line], [1.3.0], [https://github.com/archiecobbs/csvprintf], [csvprintf]) +AC_INIT([csvprintf - Simple CSV file parser for the UNIX command line],[1.3.1],[https://github.com/archiecobbs/csvprintf],[csvprintf]) AC_CONFIG_AUX_DIR(scripts) AM_INIT_AUTOMAKE dnl AM_MAINTAINER_MODE -AC_PREREQ(2.59) +AC_PREREQ([2.69]) AC_REVISION($Id$) AC_PREFIX_DEFAULT(/usr) AC_PROG_MAKE_SET @@ -57,28 +57,27 @@ [if test `uname -o` = 'Cygwin' -a -f /usr/lib/libiconv.a; then LIBS="-liconv ${LIBS}"; else AC_MSG_ERROR([required function iconv_open missing]); fi]) # Check for required header files -AC_HEADER_STDC AC_CHECK_HEADERS(sys/wait.h assert.h ctype.h err.h errno.h stddef.h stdint.h stdio.h stdlib.h string.h unistd.h, [], [AC_MSG_ERROR([required header file '$ac_header' missing])]) # Optional features AC_ARG_ENABLE(assertions, - AC_HELP_STRING([--enable-assertions], + AS_HELP_STRING([--enable-assertions], [enable debugging sanity checks (default NO)]), [test x"$enableval" = "xyes" || AC_DEFINE(NDEBUG, 1, [disable assertions])], [AC_DEFINE(NDEBUG, 1, [disable assertions])]) AC_ARG_ENABLE(gprof, - AC_HELP_STRING([--enable-gprof], + AS_HELP_STRING([--enable-gprof], [Compile and link with gprof(1) support (default NO)]), [test x"$enableval" = "xyes" && CFLAGS="${CFLAGS} -pg"]) AC_ARG_ENABLE(Werror, - AC_HELP_STRING([--enable-Werror], + AS_HELP_STRING([--enable-Werror], [enable compilation with -Werror flag (default NO)]), [test x"$enableval" = "xyes" && CFLAGS="${CFLAGS} -Werror"]) # Generated files AC_CONFIG_FILES(Makefile) -AM_CONFIG_HEADER(config.h) +AC_CONFIG_HEADERS(config.h) # Go AC_OUTPUT diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/csvprintf.1.in new/csvprintf-1.3.1/csvprintf.1.in --- old/csvprintf-1.3.0/csvprintf.1.in 2021-12-09 20:47:36.000000000 +0100 +++ new/csvprintf-1.3.1/csvprintf.1.in 2021-12-14 22:11:31.000000000 +0100 @@ -81,7 +81,7 @@ specifier will print the number of columns in the record. .Pp When the -.Fl i +.Fl n flag is given, the first row is assumed to contain column names and is not output. This allows symbolic, instead of numeric, column accessors to be used. A symbolic column accessor is the column name enclosed in curly braces. @@ -97,26 +97,22 @@ so the use of symbolic column accessors adds an extra consistency check. .Sh XML Mode With -.Fl x -or -.Fl X , +.Fl x , the entire file is converted into an XML document. .Pp The document element is .Ar "<csv>" . +.Pp Each CSV row becomes a .Ar "<row>" element containing its individual column values as sub-elements. .Pp -With -.Fl x , -the sub-elements are +The column value sub-elements are named .Ar "<col1>" , .Ar "<col2>" , -etc. -.Pp -With -.Fl X +etc.; +with +.Fl i , the sub-elements use the column names read from the first row (with illegal characters replaced by underscores). .Pp In XML mode, a character encoding must be assumed; see @@ -125,28 +121,25 @@ The .Nm xml2csv command can convert XML documents generated by -.Nm -back into CSV files. +.Nm "csvprintf -x" +back into CSV. .Sh JSON Mode With .Fl j , each row is converted into a JSON document. .Pp -This form is described by RFC 7464 and consists of concatenated JSON documents, -each framed by ASCII RS and LF control characters. -This output is compatible with the +This form is described by RFC 7464 and consists of concatenated JSON documents +framed by ASCII RS and LF control characters, which is compatible with the .Xr jq 1 utility's .Fl \-seq flag. .Pp -Without -.Fl i , -each row is written as a string array. -With +Normally each row is written as a string array; +with .Fl i , -each row is written as an object, with a field for each column name. -An error occurs if two CSV columns have the same name. +each row is written as an object, using column names for fields. +An error occurs if two columns have the same name. .Pp In JSON mode, a character encoding must be assumed; see .Fl e . @@ -155,13 +148,11 @@ .Fl b , each row is converted into .Xr bash 1 -variable assignments which may be applied with the +variable assignment(s) which may be applied with the .Xr eval 1 command. .Pp -Without -.Fl i , -the output assigns +Normally the output just assigns .Ar ROW as an array of values. The resulting output can be used like this: @@ -177,7 +168,7 @@ With .Fl i , each column value is assigned to a separate variable whose name is the corresponding column name -(with underscores replacing non-alphanumeric characters), and an error occurs if two CSV columns have the same name. +(with underscores replacing non-alphanumeric characters), and an error occurs if two variables have the same name. .Pp So an input file like this: .Bd -literal -offset indent @@ -188,13 +179,30 @@ .Pp can be processed like this: .Bd -literal -offset indent -cat input.csv | csvprintf -bi | while read LINE; do +cat input.csv | csvprintf -bi -p ROW_ | while read LINE; do eval "${LINE}" - echo "First name: ${First_Name}" - echo "Last name: ${Last_Name}" - echo "Registered: ${Registered___}" + echo "First name: ${ROW_First_Name}" + echo "Last name: ${ROW_Last_Name}" + echo "Registered: ${ROW_Registered___}" done .Ed +.Pp +The +.Fl i +flag opens a potential security hole because Bash has several special variables like +.Ar PATH , +.Ar TMPDIR , +etc., which could be overwritten by malicious input. +To prevent this, +.Nm +omits known Bash variables. +They can be explicitly white-listed using the +.Fl c +flag. +.Pp +In any case, use of the +.Fl p +flag is recommended in Bash Mode to help avoid namespace collisions. .Sh Input Encoding In all modes, lines must be terminated by LF bytes or CR+LF byte pairs, and the separator and quote characters must be recognizable as single byte values. This parsing behavior is compatible with ASCII, ISO-8859-1, UTF-8, etc., but not multi-byte encodings such as UTF-16, which must be re-encoded (e.g., to UTF-8) first. @@ -211,6 +219,16 @@ Convert each CSV row into a .Xr bash 1 variable assignment line. +.It Fl c Ar colname +Specify a column to be included when using column names in XML, JSON, or Bash output. +.Pp +Without this flag, all columns are included. +When this flag is used one or more times, +only the specified columns are included. +.Pp +If any +.Ar colname +doesn't exist, an error occurs. .It Fl e Specify input character encoding for XML or JSON mode. .Pp @@ -222,15 +240,16 @@ .Nm reads from standard input. .It Fl i -Assume the first CSV record contains column names and omit that record from the output. +Use column names read from the first record in the output. .Pp -In normal mode, enable symbolic column accessors. -.Pp -In XML mode, use column names for the inner-most XML elements. +In normal mode, or when used with the +.Fl x +flag, this flag is equivalent to +.Fl n . .Pp In JSON mode, output objects instead of arrays and use column names for the object fields. .Pp -In Bash mode, output multiple variable assignments using column names instead of a single +In Bash mode, output a variable for each column instead of a single .Ar ROW array variable. .Pp @@ -240,9 +259,23 @@ reverts to using .Ar col1 , .Ar col2 , -etc. +etc., for any extra columns. +.Pp +This flag implies +.Fl n . .It Fl j -Convert the CSV input into a JavaScript Object Notation (JSON) text sequence document. +Convert the input into a JavaScript Object Notation (JSON) text sequence document. +.It Fl n +Assume the first CSV record contains column names and omit from the output. +.Pp +In normal mode, enable symbolic column accessors. +.It Fl p +Specify a common prefix (UTF-8 encoding) to use with all column names in the output. +.Pp +This flag is ignored unless +.Fl i +is specified. +.Pp .It Fl q Specify an alternate CSV column quote character. The usual backslash escape sequences are accepted. @@ -258,14 +291,12 @@ .It Fl v Output version information and exit. .It Fl x -Convert the CSV input into an XML document. +Convert the input into an XML document. .It Fl X -Same as -.Fl x , -but it also causes the values in the first row to be used as the XML tag names for the corresponding columns. +Convert the input into an XML document using column names for value sub-elements. .Pp This flag implies -.Fl i . +.Fl n . .El .Sh CSV FORMAT .Nm diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/main.c new/csvprintf-1.3.1/main.c --- old/csvprintf-1.3.0/main.c 2021-12-09 20:47:36.000000000 +0100 +++ new/csvprintf-1.3.1/main.c 2021-12-14 22:11:31.000000000 +0100 @@ -36,9 +36,10 @@ #define XML_OUTPUT_ENCODING "UTF-8" #define MODE_NORMAL 0 // normal mode -#define MODE_XML 1 // XML mode -#define MODE_JSON 2 // JSON mode -#define MODE_BASH 3 // bash mode +#define MODE_XML_PLAIN 1 // plain XML mode +#define MODE_XML_NAMES 2 // XML mode with names +#define MODE_JSON 3 // JSON mode +#define MODE_BASH 4 // bash mode struct col { char *buf; @@ -55,6 +56,21 @@ static int quote = DEFAULT_QUOTE_CHAR; static int fsep = DEFAULT_FSEP_CHAR; +static const char *bash_special_vars[] = { + "BASH", "BASHOPTS", "BASHPID", "BASH_ALIASES", "BASH_ARGC", "BASH_ARGV", "BASH_CMDS", "BASH_COMMAND", + "BASH_EXECUTION_STRING", "BASH_LINENO", "BASH_LOADABLES_PATH", "BASH_REMATCH", "BASH_SOURCE", "BASH_SUBSHELL", + "BASH_VERSINFO", "BASH_VERSION", "COMP_CWORD", "COMP_KEY", "COMP_LINE", "COMP_POINT", "COMP_TYPE", "COMP_WORDBREAKS", + "COMP_WORDS", "COPROC", "DIRSTACK", "EUID", "FUNCNAME", "GROUPS", "HISTCMD", "HOSTNAME", "HOSTTYPE", "LINENO", + "MACHTYPE", "MAPFILE", "OLDPWD", "OPTARG", "OPTIND", "OSTYPE", "PIPESTATUS", "PPID", "PWD", "RANDOM", "READLINE_LINE", + "READLINE_POINT", "REPLY", "SECONDS", "SHELLOPTS", "SHLVL", "UID", "BASH_COMPAT", "BASH_ENV", "BASH_XTRACEFD", "CDPATH", + "CHILD_MAX", "COLUMNS", "COMPREPLY", "EMACS", "ENV", "EXECIGNORE", "FCEDIT", "FIGNORE", "FUNCNEST", "GLOBIGNORE", + "HISTCONTROL", "HISTFILE", "HISTFILESIZE", "HISTIGNORE", "HISTSIZE", "HISTTIMEFORMAT", "HOME", "HOSTFILE", "IFS", + "IGNOREEOF", "INPUTRC", "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_NUMERIC", "LC_TIME", "LINES", + "MAIL", "MAILCHECK", "MAILPATH", "OPTERR", "PATH", "POSIXLY_CORRECT", "PROMPT_COMMAND", "PROMPT_DIRTRIM", "PS0", "PS1", + "PS2", "PS3", "PS4", "SHELL", "TIMEFORMAT", "TMOUT", "TMPDIR", "auto_resume", "histchars" +}; +#define NUM_BASH_SPECIAL_VARS (sizeof(bash_special_vars) / sizeof(*bash_special_vars)) + static int parsechar(const char *str); static int parsefmt(char *fmt, const struct row *column_names, unsigned int **argsp); static int readcol(FILE *fp, struct row *row, int *linenum); @@ -75,6 +91,10 @@ static char *eataccessor(const char *fspec, const char *desc, const struct row *column_names, char *s, int *nargs, unsigned int *args); static void addcolumn(struct row *row, const struct col *col); +static void addstring(struct row *row, const char *const string); +static int findstring(struct row *row, const char *const string); +static int findstring2(const char *const *list, size_t num, const char *const string); +static void growrow(struct row *row); static void addchar(struct col *col, int ch); static void trim(struct col *col); static void usage(void); @@ -85,33 +105,40 @@ { const char *input = "-"; const char *encoding = "ISO-8859-1"; + const char *name_prefix = ""; char *format = NULL; iconv_t icd = NULL; FILE *fp = NULL; struct row row; struct row column_names; + struct row allowed_column_names; unsigned int *args = NULL; int mode = -1; - int read_column_names = 0; - int column_name_tags = 0; + int read_column_names = 0; // strip off first row containing column names + int use_column_names = 0; // use column names from first row in output int first_row = 0; int nargs = 0; int file_done; int linenum; + int new_mode; int ch; // Initialize memset(&row, 0, sizeof(row)); memset(&column_names, 0, sizeof(column_names)); + memset(&allowed_column_names, 0, sizeof(allowed_column_names)); // Parse command line - while ((ch = getopt(argc, argv, "be:f:hijq:s:vxX")) != -1) { + while ((ch = getopt(argc, argv, "bc:e:f:hijnp:q:s:vxX")) != -1) { switch (ch) { case 'b': if (mode != -1 && mode != MODE_BASH) errx(1, "flag \"%c\" conflicts with previous mode flag", ch); mode = MODE_BASH; break; + case 'c': + addstring(&allowed_column_names, optarg); + break; case 'e': encoding = optarg; break; @@ -120,23 +147,28 @@ break; case 'i': read_column_names = 1; + use_column_names = 1; + break; + case 'n': + read_column_names = 1; break; case 'j': if (mode != -1 && mode != MODE_JSON) errx(1, "flag \"%c\" conflicts with previous mode flag", ch); mode = MODE_JSON; break; + case 'X': case 'x': - if (mode != -1 && !(mode == MODE_XML && !column_name_tags)) + new_mode = ch == 'X' ? MODE_XML_NAMES : MODE_XML_PLAIN; + if (mode != -1 && mode != new_mode) errx(1, "flag \"%c\" conflicts with previous mode flag", ch); - mode = MODE_XML; + if ((mode = new_mode) == MODE_XML_NAMES) { + use_column_names = 1; + read_column_names = 1; + } break; - case 'X': - if (mode != -1 && !(mode == MODE_XML && column_name_tags)) - errx(1, "flag \"%c\" conflicts with previous mode flag", ch); - mode = MODE_XML; - column_name_tags = 1; - read_column_names = 1; + case 'p': + name_prefix = optarg; break; case 'q': if ((quote = parsechar(optarg)) == -1) @@ -167,9 +199,15 @@ exit(1); } + // Backward compatbitility hack + if (mode == MODE_XML_PLAIN) + use_column_names = 0; + // Sanity check if (quote == fsep) err(1, "quote and field separators cannot be the same character"); + if (allowed_column_names.num > 0 && !read_column_names) + err(1, "\"-c\" flag requires \"-n\" flag"); // Get and (maybe) parse format string (normal mode only) if (mode == MODE_NORMAL) { @@ -188,7 +226,8 @@ // Initialize iconv switch (mode) { - case MODE_XML: + case MODE_XML_PLAIN: + case MODE_XML_NAMES: case MODE_JSON: if ((icd = iconv_open(XML_OUTPUT_ENCODING, encoding)) == (iconv_t)-1) err(1, "%s", encoding); @@ -198,7 +237,7 @@ } // XML opening - if (mode == MODE_XML) { + if (mode == MODE_XML_PLAIN || mode == MODE_XML_NAMES) { printf("<?xml version=\"1.0\" encoding=\"%s\"?>\n", XML_OUTPUT_ENCODING); printf("<csv>\n"); } @@ -227,6 +266,7 @@ // Gather column names from first row, if configured if (first_row && read_column_names) { + int i, j; // Convert to UTF-8 if needed if (icd != NULL) @@ -240,12 +280,15 @@ if (mode == MODE_NORMAL) nargs = parsefmt(format, &column_names, &args); + // Check that all explicitly specified columns are actually present + for (i = 0; i < allowed_column_names.num; i++) { + if (!findstring(&column_names, allowed_column_names.fields[i])) + errx(1, "column \"%s\" not found", allowed_column_names.fields[i]); + } + // Check for illegal or duplicate column names switch (mode) { case MODE_JSON: - { - int i, j; - for (i = 0; i < column_names.num - 1; i++) { for (j = i + 1; j < column_names.num; j++) { if (strcmp(column_names.fields[i], column_names.fields[j]) == 0) @@ -253,21 +296,21 @@ } } break; - } case MODE_BASH: - { - int i, j; - for (i = 0; i < column_names.num; i++) { - const char *const namei = column_names.fields[i]; + char *namei; + if (asprintf(&namei, "%s%s", name_prefix, column_names.fields[i]) == -1) + err(1, "asprintf"); if (*namei == '\0') errx(1, "illegal empty string column name"); for (j = i + 1; j < column_names.num; j++) { - const char *const namej = column_names.fields[j]; + char *namej; int same = 1; int k; + if (asprintf(&namej, "%s%s", name_prefix, column_names.fields[j]) == -1) + err(1, "asprintf"); for (k = 0; namei[k] != '\0' || namej[k] != '\0'; k++) { if (namei[k] == '\0' || namej[k] == '\0' || bash_name_safe(namei[k], k == 0) != bash_name_safe(namej[k], k == 0)) { @@ -277,10 +320,11 @@ } if (same) errx(1, "duplicate (bash variable) column names \"%s\" and \"%s\"", namei, namej); + free(namej); } + free(namei); } break; - } default: break; } @@ -299,29 +343,42 @@ convert_to_utf8(icd, &row, linenum); // Output row - printf("\x1e%c", read_column_names ? '{' : '['); + printf("\x1e%c", use_column_names ? '{' : '['); for (col = 0; col < row.num; col++) { + // Check whether column should be included + if (use_column_names + && allowed_column_names.num > 0 + && col < column_names.num + && !findstring(&allowed_column_names, column_names.fields[col])) + continue; + // Add comma if needed if (col > 0) putchar(','); // Add column name (if using object notation) - if (read_column_names) { - if (col < column_names.num) + if (use_column_names) { + if (col < column_names.num) { + putchar('"'); + print_json_string(name_prefix, linenum); print_json_string(column_names.fields[col], linenum); - else + putchar('"'); + } else printf("\"col%d\"", col + 1); putchar(':'); } // Add column value + putchar('"'); print_json_string(row.fields[col], linenum); + putchar('"'); } - printf("%c\n", read_column_names ? '}' : ']'); + printf("%c\n", use_column_names ? '}' : ']'); break; } - case MODE_XML: + case MODE_XML_PLAIN: + case MODE_XML_NAMES: { int col; @@ -338,11 +395,19 @@ int uclen; int i; + // Check whether column should be included + if (use_column_names + && allowed_column_names.num > 0 + && col < column_names.num + && !findstring(&allowed_column_names, column_names.fields[col])) + continue; + // Open XML tag printf(" <"); - if (column_name_tags && col < column_names.num) + if (use_column_names && col < column_names.num) { + print_xml_tag_name(name_prefix, linenum); print_xml_tag_name(column_names.fields[col], linenum); - else + } else printf("col%d", col + 1); printf(">"); @@ -361,9 +426,10 @@ // Close XML tag printf("</"); - if (column_name_tags && col < column_names.num) + if (use_column_names && col < column_names.num) { + print_xml_tag_name(name_prefix, linenum); print_xml_tag_name(column_names.fields[col], linenum); - else + } else printf("col%d", col + 1); printf(">\n"); } @@ -372,24 +438,40 @@ } case MODE_BASH: { + char bash_name_buf[64]; // buffer just needs to be be enough to hold any of the bash_special_vars[] int col; // Start array (if needed) - if (!read_column_names) + if (!use_column_names) printf("ROW=("); // Output row for (col = 0; col < row.num; col++) { + // Check whether column should be included + if (use_column_names + && allowed_column_names.num > 0 + && col < column_names.num + && !findstring(&allowed_column_names, column_names.fields[col])) + continue; + + // Elide any BASH special variable names + if (use_column_names && col < column_names.num) { + snprintf(bash_name_buf, sizeof(bash_name_buf), "%s%s", name_prefix, column_names.fields[col]); + if (findstring2(bash_special_vars, NUM_BASH_SPECIAL_VARS, bash_name_buf)) + continue; + } + // Add space - if (col > 0 || !read_column_names) + if (col > 0 || !use_column_names) putchar(' '); // Add column name (if using column names) - if (read_column_names) { - if (col < column_names.num) + if (use_column_names) { + if (col < column_names.num) { + print_bash_name(name_prefix); print_bash_name(column_names.fields[col]); - else + } else printf("col%d", col + 1); putchar('='); } @@ -398,12 +480,12 @@ print_bash_value(row.fields[col]); // Add separator - if (read_column_names) + if (use_column_names) putchar(';'); } // End array (if needed) - if (!read_column_names) + if (!use_column_names) printf(" )"); // End line @@ -465,7 +547,7 @@ } // XML closing - if (mode == MODE_XML) + if (mode == MODE_XML_PLAIN || mode == MODE_XML_NAMES) printf("</csv>\n"); // Clean up iconv @@ -559,7 +641,7 @@ // See if plain single quotes will work for (i = 0; string[i] != '\0'; i++) { - if (string[i] == '\'' || !isprint((u_char)string[i])) { + if (string[i] == '\'' || !isprint((unsigned char)string[i])) { single_quotes = 0; break; } @@ -597,10 +679,10 @@ printf("\\v"); break; default: - if (isprint((u_char)string[i])) - putchar((u_char)string[i]); + if (isprint((unsigned char)string[i])) + putchar((unsigned char)string[i]); else - printf("\\x%02x", (u_char)string[i]); + printf("\\x%02x", (unsigned char)string[i]); break; } } @@ -611,9 +693,9 @@ static char bash_name_safe(char ch, int first) { - if (isupper((u_char)ch) || islower((u_char)ch) || ch == '_') + if (isupper((unsigned char)ch) || islower((unsigned char)ch) || ch == '_') return ch; - if (!first && isdigit((u_char)ch)) + if (!first && isdigit((unsigned char)ch)) return ch; return '_'; } @@ -625,7 +707,6 @@ int uchar; int uclen; - putchar('"'); while (*string != '\0') { uchar = decode_utf8(string, strlen(string), &uclen, linenum); switch (uchar) { @@ -659,7 +740,6 @@ } string += uclen; } - putchar('"'); } // Convert row columns to UTF-8 encoding @@ -869,9 +949,9 @@ { size_t skip; - while (col->len > 0 && isspace((u_char)col->buf[col->len - 1])) + while (col->len > 0 && isspace((unsigned char)col->buf[col->len - 1])) col->len--; - for (skip = 0; skip < col->len && isspace((u_char)col->buf[skip]); skip++) + for (skip = 0; skip < col->len && isspace((unsigned char)col->buf[skip]); skip++) ; col->len -= skip; memmove(col->buf, col->buf + skip, col->len); @@ -902,20 +982,10 @@ static void addcolumn(struct row *row, const struct col *col) { - if (row->alloc <= row->num) { - int new_alloc; - char **new_fields; - - new_alloc = row->alloc == 0 ? 32 : row->alloc * 2; - if ((new_fields = realloc(row->fields, new_alloc * sizeof(*row->fields))) == NULL) - err(1, "realloc"); - row->fields = new_fields; - row->alloc = new_alloc; - } + growrow(row); if (col->alloc >= col->len + 1) { col->buf[col->len] = '\0'; row->fields[row->num] = col->buf; - memset(&col, 0, sizeof(col)); } else { if ((row->fields[row->num] = malloc(col->len + 1)) == NULL) err(1, "malloc"); @@ -927,6 +997,49 @@ row->num++; } +// Copy given string and add to row +static void +addstring(struct row *row, const char *const string) +{ + growrow(row); + if ((row->fields[row->num++] = strdup(string)) == NULL) + err(1, "strdup"); +} + +static int +findstring(struct row *row, const char *const string) +{ + return findstring2((const char *const *)row->fields, row->num, string); +} + +static int +findstring2(const char *const *list, size_t num, const char *const string) +{ + size_t i; + + for (i = 0; i < num; i++) { + if (strcmp(list[i], string) == 0) + return 1; + } + return 0; +} + +static void +growrow(struct row *row) +{ + size_t new_alloc; + char **new_fields; + + if (row->alloc > row->num) + return; + new_alloc = row->alloc == 0 ? 32 : row->alloc * 2; + if ((new_fields = realloc(row->fields, new_alloc * sizeof(*row->fields))) == NULL) + err(1, "realloc"); + row->fields = new_fields; + row->alloc = new_alloc; + memset(row->fields + row->num, 0, (row->alloc - row->num) * sizeof(*row->fields)); +} + static int parsefmt(char *fmt, const struct row *column_names, unsigned int **argsp) { @@ -1034,7 +1147,7 @@ { if (*s == '*') return eataccessor(fspec, desc, column_names, s + 1, nargs, args); - while (isdigit((u_char)*s)) // eat up numerical field width or precision + while (isdigit((unsigned char)*s)) // eat up numerical field width or precision s++; return s; } @@ -1073,7 +1186,7 @@ } args[(*nargs)++] = argnum; } else { - while (isdigit((u_char)*s)) + while (isdigit((unsigned char)*s)) s++; if (s == start || *s++ != '$') errx(1, "missing required column accessor in %s starting at \"%.20s...\"", desc, fspec); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/tests/run2.sh new/csvprintf-1.3.1/tests/run2.sh --- old/csvprintf-1.3.0/tests/run2.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/csvprintf-1.3.1/tests/run2.sh 2021-12-14 22:11:31.000000000 +0100 @@ -0,0 +1,125 @@ +#!/bin/bash + +# Bail on error +set -e + +# Setup temporary files +TMP_STDOUT_EXPECTED='csvprintf-test-out-expected.tmp' +TMP_STDERR_EXPECTED='csvprintf-test-err-expected.tmp' +TMP_STDOUT_ACTUAL='csvprintf-test-out-actual.tmp' +TMP_STDERR_ACTUAL='csvprintf-test-err-actual.tmp' +TMP_SWAP_FILE=''csvprintf-test-hexdump.tmp +trap "rm -f \ + ${TMP_STDOUT_EXPECTED} \ + ${TMP_STDERR_EXPECTED} \ + ${TMP_STDOUT_ACTUAL} \ + ${TMP_STDERR_ACTUAL} \ + ${TMP_SWAP_FILE}" 0 2 3 5 10 13 15 + +# Convert a file to hexdump version +hexdumpify() +{ + FILE="${1}" + hexdump -C < "${FILE}" > "${TMP_SWAP_FILE}" + mv "${TMP_SWAP_FILE}" "${FILE}" +} + +# Compare files, on failure set ${DIFF_FAIL} +checkdiff() +{ + if [ "${1}" = '-h' ]; then + HEXDUMPIFY='true' + shift + else + HEXDUMPIFY='false' + fi + TESTFILE="${1}" + WHAT="${2}" + EXPECTED="${3}" + ACTUAL="${4}" + if diff -q "${EXPECTED}" "${ACTUAL}" >/dev/null; then + return 0 + fi + echo "test: ${TESTFILE}: ${WHAT} mismatch" + echo '------------------------------------------------------' + if [ "${HEXDUMPIFY}" = 'true' ]; then + hexdumpify "${EXPECTED}" + hexdumpify "${ACTUAL}" + fi + diff -u "${EXPECTED}" "${ACTUAL}" || true + echo '------------------------------------------------------' + DIFF_FAIL='true' +} + +# Execute one test, on failure set ${TEST_FAIL} +runtest() +{ + # Read test data + unset FLAGS + unset STDIN + unset STDOUT + unset STDERR + unset EXITVAL + . "${TESTFILE}" + if [ -z "${FLAGS+x}" \ + -o -z "${STDIN+x}" \ + -o -z "${STDOUT+x}" \ + -o -z "${STDERR+x}" \ + -o -z "${EXITVAL+x}" ]; then + echo "test: ${TESTFILE}: invalid test file" + exit 1 + fi + + # Set up files + echo -en "${STDOUT}" > "${TMP_STDOUT_EXPECTED}" + echo -en "${STDERR}" > "${TMP_STDERR_EXPECTED}" + set +e + echo -en "${STDIN}" | ../csvprintf ${FLAGS} >"${TMP_STDOUT_ACTUAL}" 2>"${TMP_STDERR_ACTUAL}" + ACTUAL_EXITVAL="$?" + set -e + + # Special hacks + if [ "${STDERR}" = '!USAGE!' ]; then + ../csvprintf --help 2>"${TMP_STDERR_EXPECTED}" + fi + + # Check result + DIFF_FAIL='false' + if [ "${STDOUT}" != '!IGNORE!' ]; then + checkdiff -h "${TESTFILE}" "standard output" "${TMP_STDOUT_EXPECTED}" "${TMP_STDOUT_ACTUAL}" + fi + checkdiff "${TESTFILE}" "standard error" "${TMP_STDERR_EXPECTED}" "${TMP_STDERR_ACTUAL}" + if [ "${DIFF_FAIL}" != 'false' ]; then + TEST_FAIL='true' + fi + if [ "${ACTUAL_EXITVAL}" -ne "${EXITVAL}" ]; then + echo "test: ${TESTFILE}: exit value ${ACTUAL_EXITVAL} != ${EXITVAL}" + TEST_FAIL='true' + fi + + # Print success or if failure show params + if [ "${TEST_FAIL}" = 'false' ]; then + echo "test: ${TESTFILE}: success" + else + echo "******************************************************" + echo "test: ${TESTFILE} FAILED with:" + echo " FLAGS='${FLAGS}'" + echo " STDIN='${STDIN}'" + echo "******************************************************" + fi +} + +# Find all tests and run them +ANY_FAIL='false' +for TESTFILE in `find . -maxdepth 1 -type f -name 'test-*.tst' | sort | sed 's|^./||g'`; do + TEST_FAIL='false' + runtest "${TESTFILE}" + if [ "${TEST_FAIL}" != 'false' ]; then + ANY_FAIL='true' + fi +done + +# Exit with error if any test failed +if [ "${ANY_FAIL}" != 'false' ]; then + exit 1 +fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/tests/test-bash-omit1.tst new/csvprintf-1.3.1/tests/test-bash-omit1.tst --- old/csvprintf-1.3.0/tests/test-bash-omit1.tst 1970-01-01 01:00:00.000000000 +0100 +++ new/csvprintf-1.3.1/tests/test-bash-omit1.tst 2021-12-14 22:11:31.000000000 +0100 @@ -0,0 +1,5 @@ +FLAGS='-bi' +STDIN='aaa,PATH,ccc\n"a1","b1","c1"\n"a2","b2","c2"\n' +STDOUT=$'aaa=\'a1\'; ccc=\'c1\';\naaa=\'a2\'; ccc=\'c2\';\n' +STDERR='' +EXITVAL='0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/tests/test-bash-prefix1.tst new/csvprintf-1.3.1/tests/test-bash-prefix1.tst --- old/csvprintf-1.3.0/tests/test-bash-prefix1.tst 1970-01-01 01:00:00.000000000 +0100 +++ new/csvprintf-1.3.1/tests/test-bash-prefix1.tst 2021-12-14 22:11:31.000000000 +0100 @@ -0,0 +1,5 @@ +FLAGS='-bi -p FOO_' +STDIN='aaa,PATH,ccc\n"a1","b1","c1"\n"a2","b2","c2"\n' +STDOUT=$'FOO_aaa=\'a1\'; FOO_PATH=\'b1\'; FOO_ccc=\'c1\';\nFOO_aaa=\'a2\'; FOO_PATH=\'b2\'; FOO_ccc=\'c2\';\n' +STDERR='' +EXITVAL='0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/tests/test-bash-prefix2.tst new/csvprintf-1.3.1/tests/test-bash-prefix2.tst --- old/csvprintf-1.3.0/tests/test-bash-prefix2.tst 1970-01-01 01:00:00.000000000 +0100 +++ new/csvprintf-1.3.1/tests/test-bash-prefix2.tst 2021-12-14 22:11:31.000000000 +0100 @@ -0,0 +1,5 @@ +FLAGS='-bi -p PA' +STDIN='aaa,TH,ccc\n"a1","b1","c1"\n"a2","b2","c2"\n' +STDOUT=$'PAaaa=\'a1\'; PAccc=\'c1\';\nPAaaa=\'a2\'; PAccc=\'c2\';\n' +STDERR='' +EXITVAL='0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/tests/test-cflag-not-found.tst new/csvprintf-1.3.1/tests/test-cflag-not-found.tst --- old/csvprintf-1.3.0/tests/test-cflag-not-found.tst 1970-01-01 01:00:00.000000000 +0100 +++ new/csvprintf-1.3.1/tests/test-cflag-not-found.tst 2021-12-14 22:11:31.000000000 +0100 @@ -0,0 +1,5 @@ +FLAGS='-X -c bbb -c zzz' +STDIN='aaa,bbb,ccc\n"a1","b1","c1"\n"a2","b2","c2"\n' +STDOUT='!IGNORE!' +STDERR='csvprintf: column "zzz" not found\n' +EXITVAL='1' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/tests/test-cflag-xml.tst new/csvprintf-1.3.1/tests/test-cflag-xml.tst --- old/csvprintf-1.3.0/tests/test-cflag-xml.tst 1970-01-01 01:00:00.000000000 +0100 +++ new/csvprintf-1.3.1/tests/test-cflag-xml.tst 2021-12-14 22:11:31.000000000 +0100 @@ -0,0 +1,5 @@ +FLAGS='-X -c bbb' +STDIN='aaa,bbb,ccc\n"a1","b1","c1"\n"a2","b2","c2"\n' +STDOUT='<?xml version="1.0" encoding="UTF-8"?>\n<csv>\n <row>\n <bbb>b1</bbb>\n </row>\n <row>\n <bbb>b2</bbb>\n </row>\n</csv>\n' +STDERR='' +EXITVAL='0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/csvprintf-1.3.0/tests/test-json-skip1.tst new/csvprintf-1.3.1/tests/test-json-skip1.tst --- old/csvprintf-1.3.0/tests/test-json-skip1.tst 1970-01-01 01:00:00.000000000 +0100 +++ new/csvprintf-1.3.1/tests/test-json-skip1.tst 2021-12-14 22:11:31.000000000 +0100 @@ -0,0 +1,5 @@ +FLAGS='-jn' +STDIN='aaa,bbb\n"a1","b1"\n"a2","b2"\n' +STDOUT='\x1e["a1","b1"]\n\x1e["a2","b2"]\n' +STDERR='' +EXITVAL='0' ++++++ csvprintf.obsinfo ++++++ --- /var/tmp/diff_new_pack.89w0Za/_old 2021-12-22 20:19:06.947876557 +0100 +++ /var/tmp/diff_new_pack.89w0Za/_new 2021-12-22 20:19:06.947876557 +0100 @@ -1,6 +1,6 @@ name: csvprintf -version: 1.3.0 -mtime: 1639079256 -commit: 57eae72333bf69be54b884e56bd8136d6abce19d +version: 1.3.1 +mtime: 1639516291 +commit: 5d84b997c8ce5f1946a7df6c29cc4bc799e53f1b