sh-i18n--envsubst.con commit Windows: teach getenv to do a case-sensitive search (df599e9)
   1/*
   2 * sh-i18n--envsubst.c - a stripped-down version of gettext's envsubst(1)
   3 *
   4 * Copyright (C) 2010 Ævar Arnfjörð Bjarmason
   5 *
   6 * This is a modified version of
   7 * 67d0871a8c:gettext-runtime/src/envsubst.c from the gettext.git
   8 * repository. It has been stripped down to only implement the
   9 * envsubst(1) features that we need in the git-sh-i18n fallbacks.
  10 *
  11 * The "Close standard error" part in main() is from
  12 * 8dac033df0:gnulib-local/lib/closeout.c. The copyright notices for
  13 * both files are reproduced immediately below.
  14 */
  15
  16#include "git-compat-util.h"
  17
  18/* Substitution of environment variables in shell format strings.
  19   Copyright (C) 2003-2007 Free Software Foundation, Inc.
  20   Written by Bruno Haible <bruno@clisp.org>, 2003.
  21
  22   This program is free software; you can redistribute it and/or modify
  23   it under the terms of the GNU General Public License as published by
  24   the Free Software Foundation; either version 2, or (at your option)
  25   any later version.
  26
  27   This program is distributed in the hope that it will be useful,
  28   but WITHOUT ANY WARRANTY; without even the implied warranty of
  29   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  30   GNU General Public License for more details.
  31
  32   You should have received a copy of the GNU General Public License
  33   along with this program; if not, write to the Free Software Foundation,
  34   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
  35
  36/* closeout.c - close standard output and standard error
  37   Copyright (C) 1998-2007 Free Software Foundation, Inc.
  38
  39   This program is free software; you can redistribute it and/or modify
  40   it under the terms of the GNU General Public License as published by
  41   the Free Software Foundation; either version 2, or (at your option)
  42   any later version.
  43
  44   This program is distributed in the hope that it will be useful,
  45   but WITHOUT ANY WARRANTY; without even the implied warranty of
  46   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  47   GNU General Public License for more details.
  48
  49   You should have received a copy of the GNU General Public License
  50   along with this program; if not, write to the Free Software Foundation,
  51   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
  52
  53#include <errno.h>
  54#include <getopt.h>
  55#include <stdio.h>
  56#include <stdlib.h>
  57#include <string.h>
  58
  59/* If true, substitution shall be performed on all variables.  */
  60static unsigned short int all_variables;
  61
  62/* Forward declaration of local functions.  */
  63static void print_variables (const char *string);
  64static void note_variables (const char *string);
  65static void subst_from_stdin (void);
  66
  67int
  68main (int argc, char *argv[])
  69{
  70  /* Default values for command line options.  */
  71  unsigned short int show_variables = 0;
  72
  73  switch (argc)
  74        {
  75        case 1:
  76          error ("we won't substitute all variables on stdin for you");
  77          break;
  78          /*
  79          all_variables = 1;
  80      subst_from_stdin ();
  81          */
  82        case 2:
  83          /* echo '$foo and $bar' | git sh-i18n--envsubst --variables '$foo and $bar' */
  84          all_variables = 0;
  85          note_variables (argv[1]);
  86      subst_from_stdin ();
  87          break;
  88        case 3:
  89          /* git sh-i18n--envsubst --variables '$foo and $bar' */
  90          if (strcmp(argv[1], "--variables"))
  91                error ("first argument must be --variables when two are given");
  92          show_variables = 1;
  93      print_variables (argv[2]);
  94          break;
  95        default:
  96          error ("too many arguments");
  97          break;
  98        }
  99
 100  /* Close standard error.  This is simpler than fwriteerror_no_ebadf, because
 101     upon failure we don't need an errno - all we can do at this point is to
 102     set an exit status.  */
 103  errno = 0;
 104  if (ferror (stderr) || fflush (stderr))
 105    {
 106      fclose (stderr);
 107      exit (EXIT_FAILURE);
 108    }
 109  if (fclose (stderr) && errno != EBADF)
 110    exit (EXIT_FAILURE);
 111
 112  exit (EXIT_SUCCESS);
 113}
 114
 115/* Parse the string and invoke the callback each time a $VARIABLE or
 116   ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence
 117   of ASCII alphanumeric/underscore characters, starting with an ASCII
 118   alphabetic/underscore character.
 119   We allow only ASCII characters, to avoid dependencies w.r.t. the current
 120   encoding: While "${\xe0}" looks like a variable access in ISO-8859-1
 121   encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030,
 122   SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these
 123   encodings.  */
 124static void
 125find_variables (const char *string,
 126                void (*callback) (const char *var_ptr, size_t var_len))
 127{
 128  for (; *string != '\0';)
 129    if (*string++ == '$')
 130      {
 131        const char *variable_start;
 132        const char *variable_end;
 133        unsigned short int valid;
 134        char c;
 135
 136        if (*string == '{')
 137          string++;
 138
 139        variable_start = string;
 140        c = *string;
 141        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
 142          {
 143            do
 144              c = *++string;
 145            while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
 146                   || (c >= '0' && c <= '9') || c == '_');
 147            variable_end = string;
 148
 149            if (variable_start[-1] == '{')
 150              {
 151                if (*string == '}')
 152                  {
 153                    string++;
 154                    valid = 1;
 155                  }
 156                else
 157                  valid = 0;
 158              }
 159            else
 160              valid = 1;
 161
 162            if (valid)
 163              callback (variable_start, variable_end - variable_start);
 164          }
 165      }
 166}
 167
 168
 169/* Print a variable to stdout, followed by a newline.  */
 170static void
 171print_variable (const char *var_ptr, size_t var_len)
 172{
 173  fwrite (var_ptr, var_len, 1, stdout);
 174  putchar ('\n');
 175}
 176
 177/* Print the variables contained in STRING to stdout, each one followed by a
 178   newline.  */
 179static void
 180print_variables (const char *string)
 181{
 182  find_variables (string, &print_variable);
 183}
 184
 185
 186/* Type describing list of immutable strings,
 187   implemented using a dynamic array.  */
 188typedef struct string_list_ty string_list_ty;
 189struct string_list_ty
 190{
 191  const char **item;
 192  size_t nitems;
 193  size_t nitems_max;
 194};
 195
 196/* Initialize an empty list of strings.  */
 197static inline void
 198string_list_init (string_list_ty *slp)
 199{
 200  slp->item = NULL;
 201  slp->nitems = 0;
 202  slp->nitems_max = 0;
 203}
 204
 205/* Append a single string to the end of a list of strings.  */
 206static inline void
 207string_list_append (string_list_ty *slp, const char *s)
 208{
 209  /* Grow the list.  */
 210  if (slp->nitems >= slp->nitems_max)
 211    {
 212      size_t nbytes;
 213
 214      slp->nitems_max = slp->nitems_max * 2 + 4;
 215      nbytes = slp->nitems_max * sizeof (slp->item[0]);
 216      slp->item = (const char **) xrealloc (slp->item, nbytes);
 217    }
 218
 219  /* Add the string to the end of the list.  */
 220  slp->item[slp->nitems++] = s;
 221}
 222
 223/* Compare two strings given by reference.  */
 224static int
 225cmp_string (const void *pstr1, const void *pstr2)
 226{
 227  const char *str1 = *(const char **)pstr1;
 228  const char *str2 = *(const char **)pstr2;
 229
 230  return strcmp (str1, str2);
 231}
 232
 233/* Sort a list of strings.  */
 234static inline void
 235string_list_sort (string_list_ty *slp)
 236{
 237  if (slp->nitems > 0)
 238    qsort (slp->item, slp->nitems, sizeof (slp->item[0]), cmp_string);
 239}
 240
 241/* Test whether a string list contains a given string.  */
 242static inline int
 243string_list_member (const string_list_ty *slp, const char *s)
 244{
 245  size_t j;
 246
 247  for (j = 0; j < slp->nitems; ++j)
 248    if (strcmp (slp->item[j], s) == 0)
 249      return 1;
 250  return 0;
 251}
 252
 253/* Test whether a sorted string list contains a given string.  */
 254static int
 255sorted_string_list_member (const string_list_ty *slp, const char *s)
 256{
 257  size_t j1, j2;
 258
 259  j1 = 0;
 260  j2 = slp->nitems;
 261  if (j2 > 0)
 262    {
 263      /* Binary search.  */
 264      while (j2 - j1 > 1)
 265        {
 266          /* Here we know that if s is in the list, it is at an index j
 267             with j1 <= j < j2.  */
 268          size_t j = (j1 + j2) >> 1;
 269          int result = strcmp (slp->item[j], s);
 270
 271          if (result > 0)
 272            j2 = j;
 273          else if (result == 0)
 274            return 1;
 275          else
 276            j1 = j + 1;
 277        }
 278      if (j2 > j1)
 279        if (strcmp (slp->item[j1], s) == 0)
 280          return 1;
 281    }
 282  return 0;
 283}
 284
 285
 286/* Set of variables on which to perform substitution.
 287   Used only if !all_variables.  */
 288static string_list_ty variables_set;
 289
 290/* Adds a variable to variables_set.  */
 291static void
 292note_variable (const char *var_ptr, size_t var_len)
 293{
 294  char *string = xmalloc (var_len + 1);
 295  memcpy (string, var_ptr, var_len);
 296  string[var_len] = '\0';
 297
 298  string_list_append (&variables_set, string);
 299}
 300
 301/* Stores the variables occurring in the string in variables_set.  */
 302static void
 303note_variables (const char *string)
 304{
 305  string_list_init (&variables_set);
 306  find_variables (string, &note_variable);
 307  string_list_sort (&variables_set);
 308}
 309
 310
 311static int
 312do_getc (void)
 313{
 314  int c = getc (stdin);
 315
 316  if (c == EOF)
 317    {
 318      if (ferror (stdin))
 319        error ("error while reading standard input");
 320    }
 321
 322  return c;
 323}
 324
 325static inline void
 326do_ungetc (int c)
 327{
 328  if (c != EOF)
 329    ungetc (c, stdin);
 330}
 331
 332/* Copies stdin to stdout, performing substitutions.  */
 333static void
 334subst_from_stdin (void)
 335{
 336  static char *buffer;
 337  static size_t bufmax;
 338  static size_t buflen;
 339  int c;
 340
 341  for (;;)
 342    {
 343      c = do_getc ();
 344      if (c == EOF)
 345        break;
 346      /* Look for $VARIABLE or ${VARIABLE}.  */
 347      if (c == '$')
 348        {
 349          unsigned short int opening_brace = 0;
 350          unsigned short int closing_brace = 0;
 351
 352          c = do_getc ();
 353          if (c == '{')
 354            {
 355              opening_brace = 1;
 356              c = do_getc ();
 357            }
 358          if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
 359            {
 360              unsigned short int valid;
 361
 362              /* Accumulate the VARIABLE in buffer.  */
 363              buflen = 0;
 364              do
 365                {
 366                  if (buflen >= bufmax)
 367                    {
 368                      bufmax = 2 * bufmax + 10;
 369                      buffer = xrealloc (buffer, bufmax);
 370                    }
 371                  buffer[buflen++] = c;
 372
 373                  c = do_getc ();
 374                }
 375              while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
 376                     || (c >= '0' && c <= '9') || c == '_');
 377
 378              if (opening_brace)
 379                {
 380                  if (c == '}')
 381                    {
 382                      closing_brace = 1;
 383                      valid = 1;
 384                    }
 385                  else
 386                    {
 387                      valid = 0;
 388                      do_ungetc (c);
 389                    }
 390                }
 391              else
 392                {
 393                  valid = 1;
 394                  do_ungetc (c);
 395                }
 396
 397              if (valid)
 398                {
 399                  /* Terminate the variable in the buffer.  */
 400                  if (buflen >= bufmax)
 401                    {
 402                      bufmax = 2 * bufmax + 10;
 403                      buffer = xrealloc (buffer, bufmax);
 404                    }
 405                  buffer[buflen] = '\0';
 406
 407                  /* Test whether the variable shall be substituted.  */
 408                  if (!all_variables
 409                      && !sorted_string_list_member (&variables_set, buffer))
 410                    valid = 0;
 411                }
 412
 413              if (valid)
 414                {
 415                  /* Substitute the variable's value from the environment.  */
 416                  const char *env_value = getenv (buffer);
 417
 418                  if (env_value != NULL)
 419                    fputs (env_value, stdout);
 420                }
 421              else
 422                {
 423                  /* Perform no substitution at all.  Since the buffered input
 424                     contains no other '$' than at the start, we can just
 425                     output all the buffered contents.  */
 426                  putchar ('$');
 427                  if (opening_brace)
 428                    putchar ('{');
 429                  fwrite (buffer, buflen, 1, stdout);
 430                  if (closing_brace)
 431                    putchar ('}');
 432                }
 433            }
 434          else
 435            {
 436              do_ungetc (c);
 437              putchar ('$');
 438              if (opening_brace)
 439                putchar ('{');
 440            }
 441        }
 442      else
 443        putchar (c);
 444    }
 445}