Blame sh-i18n--envsubst.c

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