xref: /netbsd-src/external/gpl2/gettext/dist/gettext-runtime/src/gettext.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
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 "gettext.h"
39 
40 #define _(str) gettext (str)
41 
42 /* If true, add newline after last string.  This makes only sense in
43    the `echo' emulation mode.  */
44 static bool add_newline;
45 
46 /* If true, expand escape sequences in strings before looking in the
47    message catalog.  */
48 static bool do_expand;
49 
50 /* Long options.  */
51 static const struct option long_options[] =
52 {
53   { "domain", required_argument, NULL, 'd' },
54   { "help", no_argument, NULL, 'h' },
55   { "shell-script", no_argument, NULL, 's' },
56   { "version", no_argument, NULL, 'V' },
57   { NULL, 0, NULL, 0 }
58 };
59 
60 /* Forward declaration of local functions.  */
61 static void usage (int status)
62 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
63      __attribute__ ((noreturn))
64 #endif
65 ;
66 static const char *expand_escape (const char *str);
67 
68 int
main(int argc,char * argv[])69 main (int argc, char *argv[])
70 {
71   int optchar;
72   const char *msgid;
73 
74   /* Default values for command line options.  */
75   bool do_help = false;
76   bool do_shell = false;
77   bool do_version = false;
78   const char *domain = getenv ("TEXTDOMAIN");
79   const char *domaindir = getenv ("TEXTDOMAINDIR");
80   add_newline = true;
81   do_expand = false;
82 
83   /* Set program name for message texts.  */
84   set_program_name (argv[0]);
85 
86 #ifdef HAVE_SETLOCALE
87   /* Set locale via LC_ALL.  */
88   setlocale (LC_ALL, "");
89 #endif
90 
91   /* Set the text message domain.  */
92   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
93   textdomain (PACKAGE);
94 
95   /* Ensure that write errors on stdout are detected.  */
96   atexit (close_stdout);
97 
98   /* Parse command line options.  */
99   while ((optchar = getopt_long (argc, argv, "+d:eEhnsV", long_options, NULL))
100 	 != EOF)
101     switch (optchar)
102     {
103     case '\0':		/* Long option.  */
104       break;
105     case 'd':
106       domain = optarg;
107       break;
108     case 'e':
109       do_expand = true;
110       break;
111     case 'E':
112       /* Ignore.  Just for compatibility.  */
113       break;
114     case 'h':
115       do_help = true;
116       break;
117     case 'n':
118       add_newline = false;
119       break;
120     case 's':
121       do_shell = true;
122       break;
123     case 'V':
124       do_version = true;
125       break;
126     default:
127       usage (EXIT_FAILURE);
128     }
129 
130   /* Version information is requested.  */
131   if (do_version)
132     {
133       printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
134       /* xgettext: no-wrap */
135       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
136 This is free software; see the source for copying conditions.  There is NO\n\
137 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
138 "),
139 	      "1995-1997, 2000-2006");
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
usage(int status)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       fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
277     }
278 
279   exit (status);
280 }
281 
282 
283 /* Expand some escape sequences found in the argument string.  */
284 static const char *
expand_escape(const char * str)285 expand_escape (const char *str)
286 {
287   char *retval, *rp;
288   const char *cp = str;
289 
290   for (;;)
291     {
292       while (cp[0] != '\0' && cp[0] != '\\')
293 	++cp;
294       if (cp[0] == '\0')
295 	return str;
296       /* Found a backslash.  */
297       if (cp[1] == '\0')
298 	return str;
299       if (strchr ("abcfnrtv\\01234567", cp[1]) != NULL)
300 	break;
301       ++cp;
302     }
303 
304   retval = (char *) xmalloc (strlen (str));
305 
306   rp = retval + (cp - str);
307   memcpy (retval, str, cp - str);
308 
309   do
310     {
311       /* Here cp[0] == '\\'.  */
312       switch (*++cp)
313 	{
314 	case 'a':		/* alert */
315 	  *rp++ = '\a';
316 	  ++cp;
317 	  break;
318 	case 'b':		/* backspace */
319 	  *rp++ = '\b';
320 	  ++cp;
321 	  break;
322 	case 'c':		/* suppress trailing newline */
323 	  add_newline = false;
324 	  ++cp;
325 	  break;
326 	case 'f':		/* form feed */
327 	  *rp++ = '\f';
328 	  ++cp;
329 	  break;
330 	case 'n':		/* new line */
331 	  *rp++ = '\n';
332 	  ++cp;
333 	  break;
334 	case 'r':		/* carriage return */
335 	  *rp++ = '\r';
336 	  ++cp;
337 	  break;
338 	case 't':		/* horizontal tab */
339 	  *rp++ = '\t';
340 	  ++cp;
341 	  break;
342 	case 'v':		/* vertical tab */
343 	  *rp++ = '\v';
344 	  ++cp;
345 	  break;
346 	case '\\':
347 	  *rp = '\\';
348 	  ++cp;
349 	  break;
350 	case '0': case '1': case '2': case '3':
351 	case '4': case '5': case '6': case '7':
352 	  {
353 	    int ch = *cp++ - '0';
354 
355 	    if (*cp >= '0' && *cp <= '7')
356 	      {
357 		ch *= 8;
358 		ch += *cp++ - '0';
359 
360 		if (*cp >= '0' && *cp <= '7')
361 		  {
362 		    ch *= 8;
363 		    ch += *cp++ - '0';
364 		  }
365 	      }
366 	    *rp = ch;
367 	  }
368 	  break;
369 	default:
370 	  *rp = '\\';
371 	  break;
372 	}
373 
374       while (cp[0] != '\0' && cp[0] != '\\')
375 	*rp++ = *cp++;
376     }
377   while (cp[0] != '\0');
378 
379   /* Terminate string.  */
380   *rp = '\0';
381 
382   return (const char *) retval;
383 }
384