/* * sh-i18n--envsubst.c - a stripped-down version of gettext's envsubst(1) * * Copyright (C) 2010 Ævar Arnfjörð Bjarmason * * This is a modified version of * 67d0871a8c:gettext-runtime/src/envsubst.c from the gettext.git * repository. It has been stripped down to only implement the * envsubst(1) features that we need in the git-sh-i18n fallbacks. * * The "Close standard error" part in main() is from * 8dac033df0:gnulib-local/lib/closeout.c. The copyright notices for * both files are reproduced immediately below. */ #include "git-compat-util.h" #include "trace2.h" /* Substitution of environment variables in shell format strings. Copyright (C) 2003-2007 Free Software Foundation, Inc. Written by Bruno Haible <bruno@clisp.org>, 2003. 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 2, 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/>. */ /* closeout.c - close standard output and standard error Copyright (C) 1998-2007 Free Software Foundation, Inc. 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 2, 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 <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> /* If true, substitution shall be performed on all variables. */ static unsigned short int all_variables; /* Forward declaration of local functions. */ static void print_variables (const char *string); static void note_variables (const char *string); static void subst_from_stdin (void); int cmd_main (int argc, const char *argv[]) { /* Default values for command line options. */ /* unsigned short int show_variables = 0; */ trace2_cmd_name("sh-i18n--envsubst"); switch (argc) { case 1: error ("we won't substitute all variables on stdin for you"); break; /* all_variables = 1; subst_from_stdin (); */ case 2: /* echo '$foo and $bar' | git sh-i18n--envsubst --variables '$foo and $bar' */ all_variables = 0; note_variables (argv[1]); subst_from_stdin (); break; case 3: /* git sh-i18n--envsubst --variables '$foo and $bar' */ if (strcmp(argv[1], "--variables")) error ("first argument must be --variables when two are given"); /* show_variables = 1; */ print_variables (argv[2]); break; default: error ("too many arguments"); break; } /* Close standard error. This is simpler than fwriteerror_no_ebadf, because upon failure we don't need an errno - all we can do at this point is to set an exit status. */ errno = 0; if (ferror (stderr) || fflush (stderr)) { fclose (stderr); exit (EXIT_FAILURE); } if (fclose (stderr) && errno != EBADF) exit (EXIT_FAILURE); exit (EXIT_SUCCESS); } /* Parse the string and invoke the callback each time a $VARIABLE or ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence of ASCII alphanumeric/underscore characters, starting with an ASCII alphabetic/underscore character. We allow only ASCII characters, to avoid dependencies w.r.t. the current encoding: While "${\xe0}" looks like a variable access in ISO-8859-1 encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030, SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these encodings. */ static void find_variables (const char *string, void (*callback) (const char *var_ptr, size_t var_len)) { for (; *string != '\0';) if (*string++ == '$') { const char *variable_start; const char *variable_end; unsigned short int valid; char c; if (*string == '{') string++; variable_start = string; c = *string; if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') { do c = *++string; while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'); variable_end = string; if (variable_start[-1] == '{') { if (*string == '}') { string++; valid = 1; } else valid = 0; } else valid = 1; if (valid) callback (variable_start, variable_end - variable_start); } } } /* Print a variable to stdout, followed by a newline. */ static void print_variable (const char *var_ptr, size_t var_len) { fwrite (var_ptr, var_len, 1, stdout); putchar ('\n'); } /* Print the variables contained in STRING to stdout, each one followed by a newline. */ static void print_variables (const char *string) { find_variables (string, &print_variable); } /* Type describing list of immutable strings, implemented using a dynamic array. */ typedef struct string_list_ty string_list_ty; struct string_list_ty { const char **item; size_t nitems; size_t nitems_max; }; /* Initialize an empty list of strings. */ static inline void string_list_init (string_list_ty *slp) { slp->item = NULL; slp->nitems = 0; slp->nitems_max = 0; } /* Append a single string to the end of a list of strings. */ static inline void string_list_append (string_list_ty *slp, const char *s) { /* Grow the list. */ if (slp->nitems >= slp->nitems_max) { slp->nitems_max = slp->nitems_max * 2 + 4; REALLOC_ARRAY(slp->item, slp->nitems_max); } /* Add the string to the end of the list. */ slp->item[slp->nitems++] = s; } /* Compare two strings given by reference. */ static int cmp_string (const void *pstr1, const void *pstr2) { const char *str1 = *(const char **)pstr1; const char *str2 = *(const char **)pstr2; return strcmp (str1, str2); } /* Sort a list of strings. */ static inline void string_list_sort (string_list_ty *slp) { QSORT(slp->item, slp->nitems, cmp_string); } /* Test whether a sorted string list contains a given string. */ static int sorted_string_list_member (const string_list_ty *slp, const char *s) { size_t j1, j2; j1 = 0; j2 = slp->nitems; if (j2 > 0) { /* Binary search. */ while (j2 - j1 > 1) { /* Here we know that if s is in the list, it is at an index j with j1 <= j < j2. */ size_t j = j1 + ((j2 - j1) >> 1); int result = strcmp (slp->item[j], s); if (result > 0) j2 = j; else if (result == 0) return 1; else j1 = j + 1; } if (j2 > j1) if (strcmp (slp->item[j1], s) == 0) return 1; } return 0; } /* Set of variables on which to perform substitution. Used only if !all_variables. */ static string_list_ty variables_set; /* Adds a variable to variables_set. */ static void note_variable (const char *var_ptr, size_t var_len) { char *string = xmemdupz (var_ptr, var_len); string_list_append (&variables_set, string); } /* Stores the variables occurring in the string in variables_set. */ static void note_variables (const char *string) { string_list_init (&variables_set); find_variables (string, ¬e_variable); string_list_sort (&variables_set); } static int do_getc (void) { int c = getc (stdin); if (c == EOF) { if (ferror (stdin)) error ("error while reading standard input"); } return c; } static inline void do_ungetc (int c) { if (c != EOF) ungetc (c, stdin); } /* Copies stdin to stdout, performing substitutions. */ static void subst_from_stdin (void) { static char *buffer; static size_t bufmax; static size_t buflen; int c; for (;;) { c = do_getc (); if (c == EOF) break; /* Look for $VARIABLE or ${VARIABLE}. */ if (c == '$') { unsigned short int opening_brace = 0; unsigned short int closing_brace = 0; c = do_getc (); if (c == '{') { opening_brace = 1; c = do_getc (); } if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') { unsigned short int valid; /* Accumulate the VARIABLE in buffer. */ buflen = 0; do { if (buflen >= bufmax) { bufmax = 2 * bufmax + 10; buffer = xrealloc (buffer, bufmax); } buffer[buflen++] = c; c = do_getc (); } while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'); if (opening_brace) { if (c == '}') { closing_brace = 1; valid = 1; } else { valid = 0; do_ungetc (c); } } else { valid = 1; do_ungetc (c); } if (valid) { /* Terminate the variable in the buffer. */ if (buflen >= bufmax) { bufmax = 2 * bufmax + 10; buffer = xrealloc (buffer, bufmax); } buffer[buflen] = '\0'; /* Test whether the variable shall be substituted. */ if (!all_variables && !sorted_string_list_member (&variables_set, buffer)) valid = 0; } if (valid) { /* Substitute the variable's value from the environment. */ const char *env_value = getenv (buffer); if (env_value != NULL) fputs (env_value, stdout); } else { /* Perform no substitution at all. Since the buffered input contains no other '$' than at the start, we can just output all the buffered contents. */ putchar ('$'); if (opening_brace) putchar ('{'); fwrite (buffer, buflen, 1, stdout); if (closing_brace) putchar ('}'); } } else { do_ungetc (c); putchar ('$'); if (opening_brace) putchar ('{'); } } else putchar (c); } }