Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-runtime / src / gettext.c
1 /* gettext - retrieve text string from message catalog and print it.
2    Copyright (C) 1995-1997, 2000-2007, 2012, 2015 Free Software
3    Foundation, Inc.
4    Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, May 1995.
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #include <getopt.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <locale.h>
29
30 #include "closeout.h"
31 #include "error.h"
32 #include "progname.h"
33 #include "relocatable.h"
34 #include "basename.h"
35 #include "xalloc.h"
36 #include "propername.h"
37 #include "gettext.h"
38
39 #define _(str) gettext (str)
40
41 /* If true, add newline after last string.  This makes only sense in
42    the 'echo' emulation mode.  */
43 static bool add_newline;
44
45 /* If true, expand escape sequences in strings before looking in the
46    message catalog.  */
47 static bool do_expand;
48
49 /* Long options.  */
50 static const struct option long_options[] =
51 {
52   { "domain", required_argument, NULL, 'd' },
53   { "help", no_argument, NULL, 'h' },
54   { "shell-script", no_argument, NULL, 's' },
55   { "version", no_argument, NULL, 'V' },
56   { NULL, 0, NULL, 0 }
57 };
58
59 /* Forward declaration of local functions.  */
60 static void usage (int status)
61 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
62      __attribute__ ((noreturn))
63 #endif
64 ;
65 static const char *expand_escape (const char *str);
66
67 int
68 main (int argc, char *argv[])
69 {
70   int optchar;
71   const char *msgid;
72
73   /* Default values for command line options.  */
74   bool do_help = false;
75   bool do_shell = false;
76   bool do_version = false;
77   const char *domain = getenv ("TEXTDOMAIN");
78   const char *domaindir = getenv ("TEXTDOMAINDIR");
79   add_newline = true;
80   do_expand = false;
81
82   /* Set program name for message texts.  */
83   set_program_name (argv[0]);
84
85 #ifdef HAVE_SETLOCALE
86   /* Set locale via LC_ALL.  */
87   setlocale (LC_ALL, "");
88 #endif
89
90   /* Set the text message domain.  */
91   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
92   textdomain (PACKAGE);
93
94   /* Ensure that write errors on stdout are detected.  */
95   atexit (close_stdout);
96
97   /* Parse command line options.  */
98   while ((optchar = getopt_long (argc, argv, "+d:eEhnsV", long_options, NULL))
99          != EOF)
100     switch (optchar)
101     {
102     case '\0':          /* Long option.  */
103       break;
104     case 'd':
105       domain = optarg;
106       break;
107     case 'e':
108       do_expand = true;
109       break;
110     case 'E':
111       /* Ignore.  Just for compatibility.  */
112       break;
113     case 'h':
114       do_help = true;
115       break;
116     case 'n':
117       add_newline = false;
118       break;
119     case 's':
120       do_shell = true;
121       break;
122     case 'V':
123       do_version = true;
124       break;
125     default:
126       usage (EXIT_FAILURE);
127     }
128
129   /* Version information is requested.  */
130   if (do_version)
131     {
132       printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
133       /* xgettext: no-wrap */
134       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
135 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
136 This is free software: you are free to change and redistribute it.\n\
137 There is NO WARRANTY, to the extent permitted by law.\n\
138 "),
139               "1995-1997, 2000-2007");
140       printf (_("Written by %s.\n"), proper_name ("Ulrich Drepper"));
141       exit (EXIT_SUCCESS);
142     }
143
144   /* Help is requested.  */
145   if (do_help)
146     usage (EXIT_SUCCESS);
147
148   /* We have two major modes: use following Uniforum spec and as
149      internationalized 'echo' program.  */
150   if (!do_shell)
151     {
152       /* We have to write a single strings translation to stdout.  */
153
154       /* Get arguments.  */
155       switch (argc - optind)
156         {
157           default:
158             error (EXIT_FAILURE, 0, _("too many arguments"));
159
160           case 2:
161             domain = argv[optind++];
162             /* FALLTHROUGH */
163
164           case 1:
165             break;
166
167           case 0:
168             error (EXIT_FAILURE, 0, _("missing arguments"));
169         }
170
171       msgid = argv[optind++];
172
173       /* Expand escape sequences if enabled.  */
174       if (do_expand)
175         msgid = expand_escape (msgid);
176
177       /* If no domain name is given we don't translate.  */
178       if (domain == NULL || domain[0] == '\0')
179         {
180           fputs (msgid, stdout);
181         }
182       else
183         {
184           /* Bind domain to appropriate directory.  */
185           if (domaindir != NULL && domaindir[0] != '\0')
186             bindtextdomain (domain, domaindir);
187
188           /* Write out the result.  */
189           fputs (dgettext (domain, msgid), stdout);
190         }
191     }
192   else
193     {
194       if (optind < argc)
195         {
196           /* If no domain name is given we print the original string.
197              We mark this assigning NULL to domain.  */
198           if (domain == NULL || domain[0] == '\0')
199             domain = NULL;
200           else
201             /* Bind domain to appropriate directory.  */
202             if (domaindir != NULL && domaindir[0] != '\0')
203               bindtextdomain (domain, domaindir);
204
205           /* We have to simulate 'echo'.  All arguments are strings.  */
206           do
207             {
208               msgid = argv[optind++];
209
210               /* Expand escape sequences if enabled.  */
211               if (do_expand)
212                 msgid = expand_escape (msgid);
213
214               /* Write out the result.  */
215               fputs (domain == NULL ? msgid : dgettext (domain, msgid),
216                      stdout);
217
218               /* We separate the arguments by a single ' '.  */
219               if (optind < argc)
220                 fputc (' ', stdout);
221             }
222           while (optind < argc);
223         }
224
225       /* If not otherwise told: add trailing newline.  */
226       if (add_newline)
227         fputc ('\n', stdout);
228     }
229
230   exit (EXIT_SUCCESS);
231 }
232
233
234 /* Display usage information and exit.  */
235 static void
236 usage (int status)
237 {
238   if (status != EXIT_SUCCESS)
239     fprintf (stderr, _("Try '%s --help' for more information.\n"),
240              program_name);
241   else
242     {
243       /* xgettext: no-wrap */
244       printf (_("\
245 Usage: %s [OPTION] [[TEXTDOMAIN] MSGID]\n\
246 or:    %s [OPTION] -s [MSGID]...\n\
247 "), program_name, program_name);
248       printf ("\n");
249       /* xgettext: no-wrap */
250       printf (_("\
251 Display native language translation of a textual message.\n"));
252       printf ("\n");
253       /* xgettext: no-wrap */
254       printf (_("\
255   -d, --domain=TEXTDOMAIN   retrieve translated messages from TEXTDOMAIN\n\
256   -e                        enable expansion of some escape sequences\n\
257   -E                        (ignored for compatibility)\n\
258   -h, --help                display this help and exit\n\
259   -n                        suppress trailing newline\n\
260   -V, --version             display version information and exit\n\
261   [TEXTDOMAIN] MSGID        retrieve translated message corresponding\n\
262                             to MSGID from TEXTDOMAIN\n"));
263       printf ("\n");
264       /* xgettext: no-wrap */
265       printf (_("\
266 If the TEXTDOMAIN parameter is not given, the domain is determined from the\n\
267 environment variable TEXTDOMAIN.  If the message catalog is not found in the\n\
268 regular directory, another location can be specified with the environment\n\
269 variable TEXTDOMAINDIR.\n\
270 When used with the -s option the program behaves like the 'echo' command.\n\
271 But it does not simply copy its arguments to stdout.  Instead those messages\n\
272 found in the selected catalog are translated.\n\
273 Standard search directory: %s\n"),
274               getenv ("IN_HELP2MAN") == NULL ? LOCALEDIR : "@localedir@");
275       printf ("\n");
276       /* TRANSLATORS: The placeholder indicates the bug-reporting address
277          for this package.  Please add _another line_ saying
278          "Report translation bugs to <...>\n" with the address for translation
279          bugs (typically your translation team's web or email address).  */
280       fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
281     }
282
283   exit (status);
284 }
285
286
287 /* Expand some escape sequences found in the argument string.  */
288 static const char *
289 expand_escape (const char *str)
290 {
291   char *retval, *rp;
292   const char *cp = str;
293
294   for (;;)
295     {
296       while (cp[0] != '\0' && cp[0] != '\\')
297         ++cp;
298       if (cp[0] == '\0')
299         return str;
300       /* Found a backslash.  */
301       if (cp[1] == '\0')
302         return str;
303       if (strchr ("abcfnrtv\\01234567", cp[1]) != NULL)
304         break;
305       ++cp;
306     }
307
308   retval = XNMALLOC (strlen (str), char);
309
310   rp = retval + (cp - str);
311   memcpy (retval, str, cp - str);
312
313   do
314     {
315       /* Here cp[0] == '\\'.  */
316       switch (*++cp)
317         {
318         case 'a':               /* alert */
319           *rp++ = '\a';
320           ++cp;
321           break;
322         case 'b':               /* backspace */
323           *rp++ = '\b';
324           ++cp;
325           break;
326         case 'c':               /* suppress trailing newline */
327           add_newline = false;
328           ++cp;
329           break;
330         case 'f':               /* form feed */
331           *rp++ = '\f';
332           ++cp;
333           break;
334         case 'n':               /* new line */
335           *rp++ = '\n';
336           ++cp;
337           break;
338         case 'r':               /* carriage return */
339           *rp++ = '\r';
340           ++cp;
341           break;
342         case 't':               /* horizontal tab */
343           *rp++ = '\t';
344           ++cp;
345           break;
346         case 'v':               /* vertical tab */
347           *rp++ = '\v';
348           ++cp;
349           break;
350         case '\\':
351           *rp = '\\';
352           ++cp;
353           break;
354         case '0': case '1': case '2': case '3':
355         case '4': case '5': case '6': case '7':
356           {
357             int ch = *cp++ - '0';
358
359             if (*cp >= '0' && *cp <= '7')
360               {
361                 ch *= 8;
362                 ch += *cp++ - '0';
363
364                 if (*cp >= '0' && *cp <= '7')
365                   {
366                     ch *= 8;
367                     ch += *cp++ - '0';
368                   }
369               }
370             *rp = ch;
371           }
372           break;
373         default:
374           *rp = '\\';
375           break;
376         }
377
378       while (cp[0] != '\0' && cp[0] != '\\')
379         *rp++ = *cp++;
380     }
381   while (cp[0] != '\0');
382
383   /* Terminate string.  */
384   *rp = '\0';
385
386   return (const char *) retval;
387 }