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