THIS IS A TEST INSTANCE ONLY! REPOSITORIES CAN BE DELETED AT ANY TIME!

Git Source Code Mirror - This is a publish-only repository and all pull requests are ignored. Please follow Documentation/SubmittingPatches procedure for any of your improvements.
git
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

428 lines
10KB

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