1 /* Pass translations to a subprocess.
2 Copyright (C) 2001-2015 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
31 #include <sys/types.h>
37 #include "xvasprintf.h"
38 #include "error-progname.h"
40 #include "relocatable.h"
43 #include "read-catalog.h"
45 #include "read-properties.h"
46 #include "read-stringtable.h"
47 #include "msgl-charset.h"
49 #include "full-write.h"
51 #include "spawn-pipe.h"
52 #include "wait-process.h"
54 #include "propername.h"
57 #define _(str) gettext (str)
60 # define STDOUT_FILENO 1
64 /* Name of the subprogram. */
65 static const char *sub_name;
67 /* Pathname of the subprogram. */
68 static const char *sub_path;
70 /* Argument list for the subprogram. */
71 static char **sub_argv;
76 /* Maximum exit code encountered. */
80 static const struct option long_options[] =
82 { "directory", required_argument, NULL, 'D' },
83 { "help", no_argument, NULL, 'h' },
84 { "input", required_argument, NULL, 'i' },
85 { "newline", no_argument, NULL, CHAR_MAX + 2 },
86 { "properties-input", no_argument, NULL, 'P' },
87 { "stringtable-input", no_argument, NULL, CHAR_MAX + 1 },
88 { "version", no_argument, NULL, 'V' },
93 /* Forward declaration of local functions. */
94 static void usage (int status)
95 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
96 __attribute__ ((noreturn))
99 static void process_msgdomain_list (const msgdomain_list_ty *mdlp);
103 main (int argc, char **argv)
108 const char *input_file;
109 msgdomain_list_ty *result;
110 catalog_input_format_ty input_syntax = &input_format_po;
113 /* Set program name for messages. */
114 set_program_name (argv[0]);
115 error_print_progname = maybe_print_progname;
117 #ifdef HAVE_SETLOCALE
118 /* Set locale via LC_ALL. */
119 setlocale (LC_ALL, "");
122 /* Set the text message domain. */
123 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
124 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
125 textdomain (PACKAGE);
127 /* Ensure that write errors on stdout are detected. */
128 atexit (close_stdout);
130 /* Set default values for variables. */
135 /* The '+' in the options string causes option parsing to terminate when
136 the first non-option, i.e. the subprogram name, is encountered. */
137 while ((opt = getopt_long (argc, argv, "+D:hi:PV", long_options, NULL))
141 case '\0': /* Long option. */
145 dir_list_append (optarg);
153 if (input_file != NULL)
155 error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
156 usage (EXIT_FAILURE);
162 input_syntax = &input_format_properties;
169 case CHAR_MAX + 1: /* --stringtable-input */
170 input_syntax = &input_format_stringtable;
173 case CHAR_MAX + 2: /* --newline */
178 usage (EXIT_FAILURE);
182 /* Version information is requested. */
185 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
186 /* xgettext: no-wrap */
187 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
188 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
189 This is free software: you are free to change and redistribute it.\n\
190 There is NO WARRANTY, to the extent permitted by law.\n\
193 printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
197 /* Help is requested. */
199 usage (EXIT_SUCCESS);
201 /* Test for the subprogram name. */
203 error (EXIT_FAILURE, 0, _("missing command name"));
204 sub_name = argv[optind];
206 /* Build argument list for the program. */
207 sub_argc = argc - optind;
208 sub_argv = XNMALLOC (sub_argc + 1, char *);
209 for (i = 0; i < sub_argc; i++)
210 sub_argv[i] = argv[optind + i];
213 /* By default, input comes from standard input. */
214 if (input_file == NULL)
217 /* Read input file. */
218 result = read_catalog_file (input_file, input_syntax);
220 if (strcmp (sub_name, "0") != 0)
222 /* Warn if the current locale is not suitable for this PO file. */
223 compare_po_locale_charsets (result);
225 /* Block SIGPIPE for this process and for the subprocesses.
226 The subprogram may have side effects (additionally to producing some
227 output), therefore if there are no readers on stdout, processing of the
228 strings must continue nevertheless. */
230 sigset_t sigpipe_set;
232 sigemptyset (&sigpipe_set);
233 sigaddset (&sigpipe_set, SIGPIPE);
234 sigprocmask (SIG_UNBLOCK, &sigpipe_set, NULL);
237 /* Attempt to locate the program.
238 This is an optimization, to avoid that spawn/exec searches the PATH
240 sub_path = find_in_path (sub_name);
242 /* Finish argument list for the program. */
243 sub_argv[0] = (char *) sub_path;
246 exitcode = 0; /* = EXIT_SUCCESS */
248 /* Apply the subprogram. */
249 process_msgdomain_list (result);
255 /* Display usage information and exit. */
259 if (status != EXIT_SUCCESS)
260 fprintf (stderr, _("Try '%s --help' for more information.\n"),
265 Usage: %s [OPTION] COMMAND [COMMAND-OPTION]\n\
268 /* xgettext: no-wrap */
270 Applies a command to all translations of a translation catalog.\n\
271 The COMMAND can be any program that reads a translation from standard\n\
272 input. It is invoked once for each translation. Its output becomes\n\
273 msgexec's output. msgexec's return code is the maximum return code\n\
274 across all invocations.\n\
277 /* xgettext: no-wrap */
279 A special builtin command called '0' outputs the translation, followed by a\n\
280 null byte. The output of \"msgexec 0\" is suitable as input for \"xargs -0\".\n\
286 --newline add newline at the end of input\n"));
289 Mandatory arguments to long options are mandatory for short options too.\n"));
292 Input file location:\n"));
294 -i, --input=INPUTFILE input PO file\n"));
296 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
298 If no input file is given or if it is -, standard input is read.\n"));
301 Input file syntax:\n"));
303 -P, --properties-input input file is in Java .properties syntax\n"));
305 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
308 Informative output:\n"));
310 -h, --help display this help and exit\n"));
312 -V, --version output version information and exit\n"));
314 /* TRANSLATORS: The placeholder indicates the bug-reporting address
315 for this package. Please add _another line_ saying
316 "Report translation bugs to <...>\n" with the address for translation
317 bugs (typically your translation team's web or email address). */
318 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
328 /* EINTR handling for close().
329 These functions can return -1/EINTR even though we don't have any
330 signal handlers set up, namely when we get interrupted via SIGSTOP. */
333 nonintr_close (int fd)
339 while (retval < 0 && errno == EINTR);
343 #define close nonintr_close
348 /* Pipe a string STR of size LEN bytes to the subprogram.
349 The byte after STR is known to be a '\0' byte. */
351 process_string (const message_ty *mp, const char *str, size_t len)
353 if (strcmp (sub_name, "0") == 0)
355 /* Built-in command "0". */
356 if (full_write (STDOUT_FILENO, str, len + 1) < len + 1)
357 error (EXIT_FAILURE, errno, _("write to stdout failed"));
361 /* General command. */
365 void (*orig_sigpipe_handler)(int);
369 /* Set environment variables for the subprocess.
370 Note: These environment variables, especially MSGEXEC_MSGCTXT and
371 MSGEXEC_MSGID, may contain non-ASCII characters. The subprocess
372 may not interpret these values correctly if the locale encoding is
373 different from the PO file's encoding. We want about this situation,
375 On Unix, this problem is often harmless. On Windows, however, - both
376 native Windows and Cygwin - the values of environment variables *must*
377 be in the encoding that is the value of GetACP(), because the system
378 may convert the environment from char** to wchar_t** before spawning
379 the subprocess and back from wchar_t** to char** in the subprocess,
380 and it does so using the GetACP() codepage. */
381 if (mp->msgctxt != NULL)
382 xsetenv ("MSGEXEC_MSGCTXT", mp->msgctxt, 1);
384 unsetenv ("MSGEXEC_MSGCTXT");
385 xsetenv ("MSGEXEC_MSGID", mp->msgid, 1);
386 if (mp->msgid_plural != NULL)
387 xsetenv ("MSGEXEC_MSGID_PLURAL", mp->msgid_plural, 1);
389 unsetenv ("MSGEXEC_MSGID_PLURAL");
390 location = xasprintf ("%s:%ld", mp->pos.file_name,
391 (long) mp->pos.line_number);
392 xsetenv ("MSGEXEC_LOCATION", location, 1);
394 if (mp->prev_msgctxt != NULL)
395 xsetenv ("MSGEXEC_PREV_MSGCTXT", mp->prev_msgctxt, 1);
397 unsetenv ("MSGEXEC_PREV_MSGCTXT");
398 if (mp->prev_msgid != NULL)
399 xsetenv ("MSGEXEC_PREV_MSGID", mp->prev_msgid, 1);
401 unsetenv ("MSGEXEC_PREV_MSGID");
402 if (mp->prev_msgid_plural != NULL)
403 xsetenv ("MSGEXEC_PREV_MSGID_PLURAL", mp->prev_msgid_plural, 1);
405 unsetenv ("MSGEXEC_PREV_MSGID_PLURAL");
407 /* Open a pipe to a subprocess. */
408 child = create_pipe_out (sub_name, sub_path, sub_argv, NULL, false, true,
411 /* Ignore SIGPIPE here. We don't care if the subprocesses terminates
412 successfully without having read all of the input that we feed it. */
413 orig_sigpipe_handler = signal (SIGPIPE, SIG_IGN);
417 newstr = XNMALLOC (len + 1, char);
418 memcpy (newstr, str, len);
419 newstr[len++] = '\n';
422 newstr = (char *) str;
424 if (full_write (fd[0], newstr, len) < len)
426 error (EXIT_FAILURE, errno,
427 _("write to %s subprocess failed"), sub_name);
434 signal (SIGPIPE, orig_sigpipe_handler);
436 /* Remove zombie process from process list, and retrieve exit status. */
437 /* FIXME: Should ignore_sigpipe be set to true here? It depends on the
438 semantics of the subprogram... */
440 wait_subprocess (child, sub_name, false, false, true, true, NULL);
441 if (exitcode < exitstatus)
442 exitcode = exitstatus;
448 process_message (const message_ty *mp)
450 const char *msgstr = mp->msgstr;
451 size_t msgstr_len = mp->msgstr_len;
455 /* Process each NUL delimited substring separately. */
456 for (p = msgstr, k = 0; p < msgstr + msgstr_len; k++)
458 size_t length = strlen (p);
460 if (mp->msgid_plural != NULL)
462 char *plural_form_string = xasprintf ("%zu", k);
464 xsetenv ("MSGEXEC_PLURAL_FORM", plural_form_string, 1);
465 free (plural_form_string);
468 unsetenv ("MSGEXEC_PLURAL_FORM");
469 process_string (mp, p, length);
477 process_message_list (const message_list_ty *mlp)
481 for (j = 0; j < mlp->nitems; j++)
482 process_message (mlp->item[j]);
487 process_msgdomain_list (const msgdomain_list_ty *mdlp)
491 for (k = 0; k < mdlp->nitems; k++)
492 process_message_list (mdlp->item[k]->messages);