{"thread":{"id":"26978","subject":"[PATCH 0/2] i18n: Shell script translation infrastructure","startedAt":"2011-04-03T16:42:31Z","lastAt":"2011-04-10T22:43:52Z","messageCount":5,"participants":["Ævar Arnfjörð Bjarmason","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"165004","messageId":"1301848953-31525-1-git-send-email-avarab@gmail.com","threadId":"26978","inReplyTo":null,"subject":"[PATCH 0/2] i18n: Shell script translation infrastructure","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2011-04-03T16:42:31Z","receivedAt":"2011-04-03T16:42:31Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Now that the C gettext infrastructure patches have made it to master\nI'm submitting a series to do the same for our shell scripts. These\nare the first two patches from:\n\n    git://github.com/avar/git.git ab/i18n-sh-only\n\nThe rest (to be submitted soon) are the patches marking up the\nshellscripts themselves for translation.\n\nÆvar Arnfjörð Bjarmason (2):\n  git-sh-i18n--envsubst: our own envsubst(1) for eval_gettext()\n  git-sh-i18n.sh: add no-op gettext() and eval_gettext() wrappers\n\n .gitignore                              |    3 +\n Documentation/git-sh-i18n--envsubst.txt |   36 +++\n Documentation/git-sh-i18n.txt           |   57 ++++\n Makefile                                |    2 +\n git-sh-i18n.sh                          |   17 ++\n sh-i18n--envsubst.c                     |  444 +++++++++++++++++++++++++++++++\n t/t0201-gettext-fallbacks.sh            |   51 ++++\n 7 files changed, 610 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-sh-i18n--envsubst.txt\n create mode 100644 Documentation/git-sh-i18n.txt\n create mode 100644 git-sh-i18n.sh\n create mode 100644 sh-i18n--envsubst.c\n create mode 100755 t/t0201-gettext-fallbacks.sh\n\n-- \n1.7.4.1\n"},{"id":"165005","messageId":"1301848953-31525-2-git-send-email-avarab@gmail.com","threadId":"26978","inReplyTo":"1301848953-31525-1-git-send-email-avarab@gmail.com","subject":"[PATCH 1/2] git-sh-i18n--envsubst: our own envsubst(1) for eval_gettext()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2011-04-03T16:42:32Z","receivedAt":"2011-04-03T16:42:32Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a git-sh-i18n--envsubst program which is a stripped-down version\nof the GNU envsubst(1) program that comes with GNU gettext for use in\nthe eval_gettext() fallback, instead of using a clever (but broken)\nprintf + eval + printf trick.\n\nIn a previous incarnation of the gettext series I implemented the\neval_gettext() fallback like this:\n\n    eval_gettext() {\n        gettext_out=$(gettext \"$1\")\n        gettext_eval=\"printf '%s' \\\"$gettext_out\\\"\"\n        printf \"%s\" \"`eval \\\"$gettext_eval\\\"`\"\n    }\n\nThis was clever, but would incorrectly handle cases where the variable\nbeing interpolated contained spaces. E.g.:\n\n    cmd=\"git foo\"; eval_gettext \"command: \\$cmd\"\n\nWould emit \"command: gitfoo\", instead of the correct \"command: git\nfoo\". This happened with a message in git-am.sh that used the $cmdline\nvariable. An alternate version suggested by Junio is able to handle\nthis case:\n\n   eval_gettext() {\n           gettext_out=$(gettext \"$1\") &&\n           gettext_eval=\"printf '%s' \\\"$gettext_out\\\"\" &&\n           gettext_cmd=$(eval \"$gettext_eval\") &&\n           printf \"%s\" \"$gettext_cmd\"\n   }\n\nBut it strips out double-quotes when they're included in the message\nbeing passed to eval_gettext. E.g. the message in git-am.sh:\n\n    \"When you have resolved this problem run \\\"$cmdline --resolved\\\".\"\n\nWill be emitted as:\n\n    When you have resolved this problem run git am --resolved.\n\nInstead of the correct:\n\n    When you have resolved this problem run \"git am --resolved\".\n\nTo work around this, and to improve our variable expansion behavior\n(eval has security issues) I've imported a stripped-down version of\ngettext's envsubst(1) program. With it eval_gettext() is implemented\nlike this:\n\n    eval_gettext() {\n        printf \"%s\" \"$1\" | (\n            export PATH $(git sh-i18n--envsubst --variables \"$1\");\n            git sh-i18n--envsubst \"$1\"\n        )\n    }\n\nThese are the modifications I made to envsubst.c as I turned it into\nsh-i18n--envsubst.c:\n\n * Added our git-compat-util.h header for xrealloc() and friends.\n\n * Removed inclusion of gettext-specific headers.\n\n * Removed most of main() and replaced it with my own. The modified\n   version only does option parsing for --variables. That's all it\n   needs.\n\n * Modified error() invocations to use our error() instead of\n   error(3).\n\n * Replaced the gettext XNMALLOC(n, size) macro with just\n   xmalloc(n). Since XNMALLOC() only allocated char's.\n\n * Removed the string_list_destroy function. It's redundant (also in\n   the upstream code).\n\n * Replaced the use of stdbool.h (a C99 header) by doing the following\n   replacements on the code:\n\n    * s/bool/unsigned short int/g\n    * s/true/1/g\n    * s/false/0/g\n\nReported-by: Johannes Sixt <j.sixt@viscovery.net>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n .gitignore                              |    2 +\n Documentation/git-sh-i18n--envsubst.txt |   36 +++\n Makefile                                |    1 +\n sh-i18n--envsubst.c                     |  444 +++++++++++++++++++++++++++++++\n t/t0201-gettext-fallbacks.sh            |   51 ++++\n 5 files changed, 534 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-sh-i18n--envsubst.txt\n create mode 100644 sh-i18n--envsubst.c\n create mode 100755 t/t0201-gettext-fallbacks.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex c460c66..dad653d 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -127,6 +127,8 @@\n /git-rm\n /git-send-email\n /git-send-pack\n+/git-sh-i18n\n+/git-sh-i18n--envsubst\n /git-sh-setup\n /git-shell\n /git-shortlog\ndiff --git a/Documentation/git-sh-i18n--envsubst.txt b/Documentation/git-sh-i18n--envsubst.txt\nnew file mode 100644\nindex 0000000..e146a2c\n--- /dev/null\n+++ b/Documentation/git-sh-i18n--envsubst.txt\n@@ -0,0 +1,36 @@\n+git-sh-i18n--envsubst(1)\n+========================\n+\n+NAME\n+----\n+git-sh-i18n--envsubst - Git's own envsubst(1) for i18n fallbacks\n+\n+DESCRIPTION\n+-----------\n+\n+This is not a command the end user would want to run.  Ever.\n+This documentation is meant for people who are studying the\n+plumbing scripts and/or are writing new ones.\n+\n+git-sh-i18n--envsubst is Git's stripped-down copy of the GNU\n+`envsubst(1)` program that comes with the GNU gettext\n+package. It's used internally by linkgit:git-sh-i18n[1] to\n+provide the `eval_gettext` fallback on Solaris and systems\n+that don't have a gettext implementation.\n+\n+No promises are made about the interface, or that this\n+program won't disappear without warning in the next version\n+of Git. Don't use it.\n+\n+Author\n+------\n+Written by Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n+\n+Documentation\n+--------------\n+Documentation by Ævar Arnfjörð Bjarmason and the git-list\n+<git@vger.kernel.org>.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 92c1c5e..1f41812 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -414,6 +414,7 @@ PROGRAM_OBJS += shell.o\n PROGRAM_OBJS += show-index.o\n PROGRAM_OBJS += upload-pack.o\n PROGRAM_OBJS += http-backend.o\n+PROGRAM_OBJS += sh-i18n--envsubst.o\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \ndiff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c\nnew file mode 100644\nindex 0000000..8db71b1\n--- /dev/null\n+++ b/sh-i18n--envsubst.c\n@@ -0,0 +1,444 @@\n+/*\n+ * sh-i18n--envsubst.c - a stripped-down version of gettext's envsubst(1)\n+ *\n+ * Copyright (C) 2010 Ævar Arnfjörð Bjarmason\n+ *\n+ * This is a modified version of\n+ * 67d0871a8c:gettext-runtime/src/envsubst.c from the gettext.git\n+ * repository. It has been stripped down to only implement the\n+ * envsubst(1) features that we need in the git-sh-i18n fallbacks.\n+ *\n+ * The \"Close standard error\" part in main() is from\n+ * 8dac033df0:gnulib-local/lib/closeout.c. The copyright notices for\n+ * both files are reproduced immediately below.\n+ */\n+\n+#include \"git-compat-util.h\"\n+\n+/* Substitution of environment variables in shell format strings.\n+   Copyright (C) 2003-2007 Free Software Foundation, Inc.\n+   Written by Bruno Haible <bruno@clisp.org>, 2003.\n+\n+   This program is free software; you can redistribute it and/or modify\n+   it under the terms of the GNU General Public License as published by\n+   the Free Software Foundation; either version 2, or (at your option)\n+   any later version.\n+\n+   This program is distributed in the hope that it will be useful,\n+   but WITHOUT ANY WARRANTY; without even the implied warranty of\n+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+   GNU General Public License for more details.\n+\n+   You should have received a copy of the GNU General Public License\n+   along with this program; if not, write to the Free Software Foundation,\n+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */\n+\n+/* closeout.c - close standard output and standard error\n+   Copyright (C) 1998-2007 Free Software Foundation, Inc.\n+\n+   This program is free software; you can redistribute it and/or modify\n+   it under the terms of the GNU General Public License as published by\n+   the Free Software Foundation; either version 2, or (at your option)\n+   any later version.\n+\n+   This program is distributed in the hope that it will be useful,\n+   but WITHOUT ANY WARRANTY; without even the implied warranty of\n+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+   GNU General Public License for more details.\n+\n+   You should have received a copy of the GNU General Public License\n+   along with this program; if not, write to the Free Software Foundation,\n+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */\n+\n+#include <errno.h>\n+#include <getopt.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+\n+/* If true, substitution shall be performed on all variables.  */\n+static unsigned short int all_variables;\n+\n+/* Forward declaration of local functions.  */\n+static void print_variables (const char *string);\n+static void note_variables (const char *string);\n+static void subst_from_stdin (void);\n+\n+int\n+main (int argc, char *argv[])\n+{\n+  /* Default values for command line options.  */\n+  unsigned short int show_variables = 0;\n+\n+  switch (argc)\n+\t{\n+\tcase 1:\n+\t  error (\"we won't substitute all variables on stdin for you\");\n+\t  /*\n+\t  all_variables = 1;\n+      subst_from_stdin ();\n+\t  */\n+\tcase 2:\n+\t  /* echo '$foo and $bar' | git sh-i18n--envsubst --variables '$foo and $bar' */\n+\t  all_variables = 0;\n+\t  note_variables (argv[1]);\n+      subst_from_stdin ();\n+\t  break;\n+\tcase 3:\n+\t  /* git sh-i18n--envsubst --variables '$foo and $bar' */\n+\t  if (strcmp(argv[1], \"--variables\"))\n+\t\terror (\"first argument must be --variables when two are given\");\n+\t  show_variables = 1;\n+      print_variables (argv[2]);\n+\t  break;\n+\tdefault:\n+\t  error (\"too many arguments\");\n+\t  break;\n+\t}\n+\n+  /* Close standard error.  This is simpler than fwriteerror_no_ebadf, because\n+     upon failure we don't need an errno - all we can do at this point is to\n+     set an exit status.  */\n+  errno = 0;\n+  if (ferror (stderr) || fflush (stderr))\n+    { \n+      fclose (stderr);\n+      exit (EXIT_FAILURE);\n+    }\n+  if (fclose (stderr) && errno != EBADF)\n+    exit (EXIT_FAILURE);\n+\n+  exit (EXIT_SUCCESS);\n+}\n+\n+/* Parse the string and invoke the callback each time a $VARIABLE or\n+   ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence\n+   of ASCII alphanumeric/underscore characters, starting with an ASCII\n+   alphabetic/underscore character.\n+   We allow only ASCII characters, to avoid dependencies w.r.t. the current\n+   encoding: While \"${\\xe0}\" looks like a variable access in ISO-8859-1\n+   encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030,\n+   SHIFT_JIS, JOHAB encodings, because \\xe0\\x7d is a single character in these\n+   encodings.  */\n+static void\n+find_variables (const char *string,\n+\t\tvoid (*callback) (const char *var_ptr, size_t var_len))\n+{\n+  for (; *string != '\\0';)\n+    if (*string++ == '$')\n+      {\n+\tconst char *variable_start;\n+\tconst char *variable_end;\n+\tunsigned short int valid;\n+\tchar c;\n+\n+\tif (*string == '{')\n+\t  string++;\n+\n+\tvariable_start = string;\n+\tc = *string;\n+\tif ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')\n+\t  {\n+\t    do\n+\t      c = *++string;\n+\t    while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')\n+\t\t   || (c >= '0' && c <= '9') || c == '_');\n+\t    variable_end = string;\n+\n+\t    if (variable_start[-1] == '{')\n+\t      {\n+\t\tif (*string == '}')\n+\t\t  {\n+\t\t    string++;\n+\t\t    valid = 1;\n+\t\t  }\n+\t\telse\n+\t\t  valid = 0;\n+\t      }\n+\t    else\n+\t      valid = 1;\n+\n+\t    if (valid)\n+\t      callback (variable_start, variable_end - variable_start);\n+\t  }\n+      }\n+}\n+\n+\n+/* Print a variable to stdout, followed by a newline.  */\n+static void\n+print_variable (const char *var_ptr, size_t var_len)\n+{\n+  fwrite (var_ptr, var_len, 1, stdout);\n+  putchar ('\\n');\n+}\n+\n+/* Print the variables contained in STRING to stdout, each one followed by a\n+   newline.  */\n+static void\n+print_variables (const char *string)\n+{\n+  find_variables (string, &print_variable);\n+}\n+\n+\n+/* Type describing list of immutable strings,\n+   implemented using a dynamic array.  */\n+typedef struct string_list_ty string_list_ty;\n+struct string_list_ty\n+{\n+  const char **item;\n+  size_t nitems;\n+  size_t nitems_max;\n+};\n+\n+/* Initialize an empty list of strings.  */\n+static inline void\n+string_list_init (string_list_ty *slp)\n+{\n+  slp->item = NULL;\n+  slp->nitems = 0;\n+  slp->nitems_max = 0;\n+}\n+\n+/* Append a single string to the end of a list of strings.  */\n+static inline void\n+string_list_append (string_list_ty *slp, const char *s)\n+{\n+  /* Grow the list.  */\n+  if (slp->nitems >= slp->nitems_max)\n+    {\n+      size_t nbytes;\n+\n+      slp->nitems_max = slp->nitems_max * 2 + 4;\n+      nbytes = slp->nitems_max * sizeof (slp->item[0]);\n+      slp->item = (const char **) xrealloc (slp->item, nbytes);\n+    }\n+\n+  /* Add the string to the end of the list.  */\n+  slp->item[slp->nitems++] = s;\n+}\n+\n+/* Compare two strings given by reference.  */\n+static int\n+cmp_string (const void *pstr1, const void *pstr2)\n+{\n+  const char *str1 = *(const char **)pstr1;\n+  const char *str2 = *(const char **)pstr2;\n+\n+  return strcmp (str1, str2);\n+}\n+\n+/* Sort a list of strings.  */\n+static inline void\n+string_list_sort (string_list_ty *slp)\n+{\n+  if (slp->nitems > 0)\n+    qsort (slp->item, slp->nitems, sizeof (slp->item[0]), cmp_string);\n+}\n+\n+/* Test whether a string list contains a given string.  */\n+static inline int\n+string_list_member (const string_list_ty *slp, const char *s)\n+{\n+  size_t j;\n+\n+  for (j = 0; j < slp->nitems; ++j)\n+    if (strcmp (slp->item[j], s) == 0)\n+      return 1;\n+  return 0;\n+}\n+\n+/* Test whether a sorted string list contains a given string.  */\n+static int\n+sorted_string_list_member (const string_list_ty *slp, const char *s)\n+{\n+  size_t j1, j2;\n+\n+  j1 = 0;\n+  j2 = slp->nitems;\n+  if (j2 > 0)\n+    {\n+      /* Binary search.  */\n+      while (j2 - j1 > 1)\n+\t{\n+\t  /* Here we know that if s is in the list, it is at an index j\n+\t     with j1 <= j < j2.  */\n+\t  size_t j = (j1 + j2) >> 1;\n+\t  int result = strcmp (slp->item[j], s);\n+\n+\t  if (result > 0)\n+\t    j2 = j;\n+\t  else if (result == 0)\n+\t    return 1;\n+\t  else\n+\t    j1 = j + 1;\n+\t}\n+      if (j2 > j1)\n+\tif (strcmp (slp->item[j1], s) == 0)\n+\t  return 1;\n+    }\n+  return 0;\n+}\n+\n+\n+/* Set of variables on which to perform substitution.\n+   Used only if !all_variables.  */\n+static string_list_ty variables_set;\n+\n+/* Adds a variable to variables_set.  */\n+static void\n+note_variable (const char *var_ptr, size_t var_len)\n+{\n+  char *string = xmalloc (var_len + 1);\n+  memcpy (string, var_ptr, var_len);\n+  string[var_len] = '\\0';\n+\n+  string_list_append (&variables_set, string);\n+}\n+\n+/* Stores the variables occurring in the string in variables_set.  */\n+static void\n+note_variables (const char *string)\n+{\n+  string_list_init (&variables_set);\n+  find_variables (string, &note_variable);\n+  string_list_sort (&variables_set);\n+}\n+\n+\n+static int\n+do_getc ()\n+{\n+  int c = getc (stdin);\n+\n+  if (c == EOF)\n+    {\n+      if (ferror (stdin))\n+\terror (\"error while reading standard input\");\n+    }\n+\n+  return c;\n+}\n+\n+static inline void\n+do_ungetc (int c)\n+{\n+  if (c != EOF)\n+    ungetc (c, stdin);\n+}\n+\n+/* Copies stdin to stdout, performing substitutions.  */\n+static void\n+subst_from_stdin ()\n+{\n+  static char *buffer;\n+  static size_t bufmax;\n+  static size_t buflen;\n+  int c;\n+\n+  for (;;)\n+    {\n+      c = do_getc ();\n+      if (c == EOF)\n+\tbreak;\n+      /* Look for $VARIABLE or ${VARIABLE}.  */\n+      if (c == '$')\n+\t{\n+\t  unsigned short int opening_brace = 0;\n+\t  unsigned short int closing_brace = 0;\n+\n+\t  c = do_getc ();\n+\t  if (c == '{')\n+\t    {\n+\t      opening_brace = 1;\n+\t      c = do_getc ();\n+\t    }\n+\t  if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')\n+\t    {\n+\t      unsigned short int valid;\n+\n+\t      /* Accumulate the VARIABLE in buffer.  */\n+\t      buflen = 0;\n+\t      do\n+\t\t{\n+\t\t  if (buflen >= bufmax)\n+\t\t    {\n+\t\t      bufmax = 2 * bufmax + 10;\n+\t\t      buffer = xrealloc (buffer, bufmax);\n+\t\t    }\n+\t\t  buffer[buflen++] = c;\n+\n+\t\t  c = do_getc ();\n+\t\t}\n+\t      while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')\n+\t\t     || (c >= '0' && c <= '9') || c == '_');\n+\n+\t      if (opening_brace)\n+\t\t{\n+\t\t  if (c == '}')\n+\t\t    {\n+\t\t      closing_brace = 1;\n+\t\t      valid = 1;\n+\t\t    }\n+\t\t  else\n+\t\t    {\n+\t\t      valid = 0;\n+\t\t      do_ungetc (c);\n+\t\t    }\n+\t\t}\n+\t      else\n+\t\t{\n+\t\t  valid = 1;\n+\t\t  do_ungetc (c);\n+\t\t}\n+\n+\t      if (valid)\n+\t\t{\n+\t\t  /* Terminate the variable in the buffer.  */\n+\t\t  if (buflen >= bufmax)\n+\t\t    {\n+\t\t      bufmax = 2 * bufmax + 10;\n+\t\t      buffer = xrealloc (buffer, bufmax);\n+\t\t    }\n+\t\t  buffer[buflen] = '\\0';\n+\n+\t\t  /* Test whether the variable shall be substituted.  */\n+\t\t  if (!all_variables\n+\t\t      && !sorted_string_list_member (&variables_set, buffer))\n+\t\t    valid = 0;\n+\t\t}\n+\n+\t      if (valid)\n+\t\t{\n+\t\t  /* Substitute the variable's value from the environment.  */\n+\t\t  const char *env_value = getenv (buffer);\n+\n+\t\t  if (env_value != NULL)\n+\t\t    fputs (env_value, stdout);\n+\t\t}\n+\t      else\n+\t\t{\n+\t\t  /* Perform no substitution at all.  Since the buffered input\n+\t\t     contains no other '$' than at the start, we can just\n+\t\t     output all the buffered contents.  */\n+\t\t  putchar ('$');\n+\t\t  if (opening_brace)\n+\t\t    putchar ('{');\n+\t\t  fwrite (buffer, buflen, 1, stdout);\n+\t\t  if (closing_brace)\n+\t\t    putchar ('}');\n+\t\t}\n+\t    }\n+\t  else\n+\t    {\n+\t      do_ungetc (c);\n+\t      putchar ('$');\n+\t      if (opening_brace)\n+\t\tputchar ('{');\n+\t    }\n+\t}\n+      else\n+\tputchar (c);\n+    }\n+}\ndiff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh\nnew file mode 100755\nindex 0000000..c22409c\n--- /dev/null\n+++ b/t/t0201-gettext-fallbacks.sh\n@@ -0,0 +1,51 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason\n+#\n+\n+test_description='Gettext Shell fallbacks'\n+\n+. ./test-lib.sh\n+. \"$GIT_BUILD_DIR\"/git-sh-i18n\n+\n+test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '\n+    printf \"test\" >expect &&\n+    gettext \"test\" >actual &&\n+    test_cmp expect actual &&\n+    printf \"test more words\" >expect &&\n+    gettext \"test more words\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'eval_gettext: our eval_gettext() fallback has pass-through semantics' '\n+    printf \"test\" >expect &&\n+    eval_gettext \"test\" >actual &&\n+    test_cmp expect actual &&\n+    printf \"test more words\" >expect &&\n+    eval_gettext \"test more words\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables' '\n+    printf \"test YesPlease\" >expect &&\n+    GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease eval_gettext \"test \\$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables with spaces' '\n+    cmdline=\"git am\" &&\n+    export cmdline;\n+    printf \"When you have resolved this problem run git am --resolved.\" >expect &&\n+    eval_gettext \"When you have resolved this problem run \\$cmdline --resolved.\" >actual\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables with spaces and quotes' '\n+    cmdline=\"git am\" &&\n+    export cmdline;\n+    printf \"When you have resolved this problem run \\\"git am --resolved\\\".\" >expect &&\n+    eval_gettext \"When you have resolved this problem run \\\"\\$cmdline --resolved\\\".\" >actual\n+    test_cmp expect actual\n+'\n+\n+test_done\n-- \n1.7.4.1\n"},{"id":"165006","messageId":"1301848953-31525-3-git-send-email-avarab@gmail.com","threadId":"26978","inReplyTo":"1301848953-31525-1-git-send-email-avarab@gmail.com","subject":"[PATCH 2/2] git-sh-i18n.sh: add no-op gettext() and eval_gettext() wrappers","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2011-04-03T16:42:33Z","receivedAt":"2011-04-03T16:42:33Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a no-op wrapper library for Git's shell scripts. To split up the\ngettext series I'm first submitting patches to gettextize the source\ntree before I add any of the Makefile and Shell library changes needed\nto actually use them.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n .gitignore                    |    1 +\n Documentation/git-sh-i18n.txt |   57 +++++++++++++++++++++++++++++++++++++++++\n Makefile                      |    1 +\n git-sh-i18n.sh                |   17 ++++++++++++\n 4 files changed, 76 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-sh-i18n.txt\n create mode 100644 git-sh-i18n.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex dad653d..e9ab0af 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -130,6 +130,7 @@\n /git-sh-i18n\n /git-sh-i18n--envsubst\n /git-sh-setup\n+/git-sh-i18n\n /git-shell\n /git-shortlog\n /git-show\ndiff --git a/Documentation/git-sh-i18n.txt b/Documentation/git-sh-i18n.txt\nnew file mode 100644\nindex 0000000..b41b3af\n--- /dev/null\n+++ b/Documentation/git-sh-i18n.txt\n@@ -0,0 +1,57 @@\n+git-sh-i18n(1)\n+==============\n+\n+NAME\n+----\n+git-sh-i18n - Git's i18n setup code for shell scripts\n+\n+SYNOPSIS\n+--------\n+'. \"$(git --exec-path)/git-sh-i18n\"'\n+\n+DESCRIPTION\n+-----------\n+\n+This is not a command the end user would want to run.  Ever.\n+This documentation is meant for people who are studying the\n+Porcelain-ish scripts and/or are writing new ones.\n+\n+The 'git sh-i18n scriptlet is designed to be sourced (using\n+`.`) by Git's porcelain programs implemented in shell\n+script. It provides wrappers for the GNU `gettext` and\n+`eval_gettext` functions accessible through the `gettext.sh`\n+script, and provides pass-through fallbacks on systems\n+without GNU gettext.\n+\n+FUNCTIONS\n+---------\n+\n+gettext::\n+\tOn GNU systems this will be the `gettext` function from\n+\t`gettext.sh`, on Solaris it`ll be the `gettext(1)`\n+\tcommand.\n+\n+\tIf neither of those implementations are available a\n+\tdummy fall-through function is provided.\n+    \n+eval_gettext::\n+\tOn GNU systems this will be the `eval_gettext` function\n+\tfrom `gettext.sh`, on Solaris we provide an\n+\t`eval_gettext` using the\n+\tlinkgit:git-sh-i18n--envsubst[1] helper.\n+\n+\tIf neither of those implementations are available a\n+\tdummy fall-through function is provided.\n+\n+Author\n+------\n+Written by Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n+\n+Documentation\n+--------------\n+Documentation by Ævar Arnfjörð Bjarmason and the git-list\n+<git@vger.kernel.org>.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 1f41812..e2dab69 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -381,6 +381,7 @@ SCRIPT_SH += git-web--browse.sh\n SCRIPT_LIB += git-mergetool--lib\n SCRIPT_LIB += git-parse-remote\n SCRIPT_LIB += git-sh-setup\n+SCRIPT_LIB += git-sh-i18n\n \n SCRIPT_PERL += git-add--interactive.perl\n SCRIPT_PERL += git-difftool.perl\ndiff --git a/git-sh-i18n.sh b/git-sh-i18n.sh\nnew file mode 100644\nindex 0000000..ea05e16\n--- /dev/null\n+++ b/git-sh-i18n.sh\n@@ -0,0 +1,17 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason\n+#\n+# This is a skeleton no-op implementation of gettext for Git. It'll be\n+# replaced by something that uses gettext.sh in a future patch series.\n+\n+gettext () {\n+\tprintf \"%s\" \"$1\"\n+}\n+\n+eval_gettext () {\n+\tprintf \"%s\" \"$1\" | (\n+\t\texport PATH $(git sh-i18n--envsubst --variables \"$1\");\n+\t\tgit sh-i18n--envsubst \"$1\"\n+\t)\n+}\n-- \n1.7.4.1\n"},{"id":"165071","messageId":"7voc4ncg0l.fsf@alter.siamese.dyndns.org","threadId":"26978","inReplyTo":"1301848953-31525-2-git-send-email-avarab@gmail.com","subject":"Re: [PATCH 1/2] git-sh-i18n--envsubst: our own envsubst(1) for eval_gettext()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-04-03T19:05:14Z","receivedAt":"2011-04-03T19:05:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> Add a git-sh-i18n--envsubst program which is a stripped-down version\n> of the GNU envsubst(1) program that comes with GNU gettext for use in\n> the eval_gettext() fallback, instead of using a clever (but broken)\n> printf + eval + printf trick.\n>\n> In a previous incarnation of the gettext series I implemented the\n> eval_gettext() fallback like this:\n>\n>     eval_gettext() {\n>         gettext_out=$(gettext \"$1\")\n>         gettext_eval=\"printf '%s' \\\"$gettext_out\\\"\"\n>         printf \"%s\" \"`eval \\\"$gettext_eval\\\"`\"\n>     }\n>\n> This was clever,...\n\nThe incoming string to this function is an arbitrary string and it is\nasked to interpolate variables $var in it, without any other restriction\nin the string.  If we want to interpolate $var using shell eval, we have\nto somehow put it in the dq context, but all other interpolation done\ninside dq context would also be applied to the piece of the string by the\neval, including backslashes, $(command substitution--horrors), etc., which\nwe would have needed to add more quotes to selected places in the string\n(read: parse and find what the shell would interpolate ourselves) to\nprevent them from happening.\n\nIn hindsight, it should have been clear to all of us from day one that\nshell eval based approach would have never worked.\n\nI suspect that the use of envsubst (especially the part that gives the\nstring itself to envsubst) is not quite right.  What if the message wanted\nto say something like this?\n\n        Look at $HOME/.gitconfig and find definition for $var variable.\n\nwhere we want to show $HOME _literally_, while substituting $var for the\nname of the variable?  The underlying envsubst allows you to do so by\ntelling it which the variables are to be substituted.\n\n\ts='Look at $HOME/.gitconfig and find definition of $var variable.'\n\tHOME=/home/junio\n        var=core.i18n\n        (\n        \texport HOME var ;# no need for HOME, but just an illustration\n\t\techo \"$s\" | envsubst '$var'\n\t)\n\nbut eval_gettext interface doesn't seem to be expressive enough to do this;\nyou would let it scan the whole \"$s\" and end up substituting $HOME, no?\n\nPerhaps it is not an issue in real life; after all eval_gettext interface\nis not something you invented in 5 minutes without thinking deeply, but is\nwhat you plan to use from outside codebase that presumably has seen a wide\nuse in the field, and it may be just that I am worried too much about an\nimplausible corner case, but I thought I should bring it up.\n\nThanks.\n"},{"id":"165585","messageId":"BANLkTimjkuvOc4mS4hfC7PiGWrRAb_2uog@mail.gmail.com","threadId":"26978","inReplyTo":"7voc4ncg0l.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 1/2] git-sh-i18n--envsubst: our own envsubst(1) for eval_gettext()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2011-04-10T22:43:52Z","receivedAt":"2011-04-10T22:43:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"On Sun, Apr 3, 2011 at 21:05, Junio C Hamano <gitster@pobox.com> wrote:\n\n> Perhaps it is not an issue in real life; after all eval_gettext interface\n> is not something you invented in 5 minutes without thinking deeply, but is\n> what you plan to use from outside codebase that presumably has seen a wide\n> use in the field, and it may be just that I am worried too much about an\n> implausible corner case, but I thought I should bring it up.\n\nIt's a corner case, but if we run into it we can easily solve it like\nthis:\n\n    #!/bin/sh\n    . gettext.sh\n\n    HOME=/home/junio\n    path='$HOME/.gitconfig'\n    var=core.i18n\n    (\n        export HOME var ;# no need for HOME, but just an illustration\n        # TRANSLATORS: $path will be \"$HOME/.gitconfig\"\n        eval_gettext 'Look at $path and find definition of $var variable.'; echo\n    )\n\nWhich results in:\n\n    $ sh /tmp/foo.sh\n    Look at $HOME/.gitconfig and find definition of core.i18n variable.\n"}]}