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