xref: /netbsd-src/external/gpl2/gettext/dist/gettext-runtime/src/ngettext.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1 /* ngettext - retrieve plural form string from message catalog and print it.
2    Copyright (C) 1995-1997, 2000-2006 Free Software Foundation, Inc.
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17 
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 
22 #include <getopt.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <locale.h>
28 #include <errno.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, expand escape sequences in strings before looking in the
43    message catalog.  */
44 static int do_expand;
45 
46 /* Long options.  */
47 static const struct option long_options[] =
48 {
49   { "domain", required_argument, NULL, 'd' },
50   { "help", no_argument, NULL, 'h' },
51   { "version", no_argument, NULL, 'V' },
52   { NULL, 0, NULL, 0 }
53 };
54 
55 /* Forward declaration of local functions.  */
56 static void usage (int status)
57 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
58      __attribute__ ((noreturn))
59 #endif
60 ;
61 static const char *expand_escape (const char *str);
62 
63 int
main(int argc,char * argv[])64 main (int argc, char *argv[])
65 {
66   int optchar;
67   const char *msgid;
68   const char *msgid_plural;
69   const char *count;
70   unsigned long n;
71 
72   /* Default values for command line options.  */
73   bool do_help = false;
74   bool do_version = false;
75   const char *domain = getenv ("TEXTDOMAIN");
76   const char *domaindir = getenv ("TEXTDOMAINDIR");
77   do_expand = false;
78 
79   /* Set program name for message texts.  */
80   set_program_name (argv[0]);
81 
82 #ifdef HAVE_SETLOCALE
83   /* Set locale via LC_ALL.  */
84   setlocale (LC_ALL, "");
85 #endif
86 
87   /* Set the text message domain.  */
88   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
89   textdomain (PACKAGE);
90 
91   /* Ensure that write errors on stdout are detected.  */
92   atexit (close_stdout);
93 
94   /* Parse command line options.  */
95   while ((optchar = getopt_long (argc, argv, "+d:eEhV", long_options, NULL))
96 	 != EOF)
97     switch (optchar)
98     {
99     case '\0':		/* Long option.  */
100       break;
101     case 'd':
102       domain = optarg;
103       break;
104     case 'e':
105       do_expand = true;
106       break;
107     case 'E':
108       /* Ignore.  Just for compatibility.  */
109       break;
110     case 'h':
111       do_help = true;
112       break;
113     case 'V':
114       do_version = true;
115       break;
116     default:
117       usage (EXIT_FAILURE);
118     }
119 
120   /* Version information is requested.  */
121   if (do_version)
122     {
123       printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
124       /* xgettext: no-wrap */
125       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
126 This is free software; see the source for copying conditions.  There is NO\n\
127 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
128 "),
129 	      "1995-1997, 2000-2006");
130       printf (_("Written by %s.\n"), proper_name ("Ulrich Drepper"));
131       exit (EXIT_SUCCESS);
132     }
133 
134   /* Help is requested.  */
135   if (do_help)
136     usage (EXIT_SUCCESS);
137 
138   /* More optional command line options.  */
139   switch (argc - optind)
140     {
141     default:
142       error (EXIT_FAILURE, 0, _("too many arguments"));
143 
144     case 4:
145       domain = argv[optind++];
146       /* FALLTHROUGH */
147 
148     case 3:
149       break;
150 
151     case 2:
152     case 1:
153     case 0:
154       error (EXIT_FAILURE, 0, _("missing arguments"));
155     }
156 
157   /* Now the mandatory command line options.  */
158   msgid = argv[optind++];
159   msgid_plural = argv[optind++];
160   count = argv[optind++];
161 
162   if (optind != argc)
163     abort ();
164 
165   {
166     char *endp;
167     unsigned long tmp_val;
168 
169     errno = 0;
170     tmp_val = strtoul (count, &endp, 10);
171     if (errno == 0 && count[0] != '\0' && endp[0] == '\0')
172       n = tmp_val;
173     else
174       /* When COUNT is not valid, use plural.  */
175       n = 99;
176   }
177 
178   /* Expand escape sequences if enabled.  */
179   if (do_expand)
180     {
181       msgid = expand_escape (msgid);
182       msgid_plural = expand_escape (msgid_plural);
183     }
184 
185   /* If no domain name is given we don't translate, and we use English
186      plural form handling.  */
187   if (domain == NULL || domain[0] == '\0')
188     fputs (n == 1 ? msgid : msgid_plural, stdout);
189   else
190     {
191       /* Bind domain to appropriate directory.  */
192       if (domaindir != NULL && domaindir[0] != '\0')
193 	bindtextdomain (domain, domaindir);
194 
195       /* Write out the result.  */
196       fputs (dngettext (domain, msgid, msgid_plural, n), stdout);
197     }
198 
199   exit (EXIT_SUCCESS);
200 }
201 
202 
203 /* Display usage information and exit.  */
204 static void
usage(int status)205 usage (int status)
206 {
207   if (status != EXIT_SUCCESS)
208     fprintf (stderr, _("Try `%s --help' for more information.\n"),
209 	     program_name);
210   else
211     {
212       /* xgettext: no-wrap */
213       printf (_("\
214 Usage: %s [OPTION] [TEXTDOMAIN] MSGID MSGID-PLURAL COUNT\n\
215 "), program_name);
216       printf ("\n");
217       /* xgettext: no-wrap */
218       printf (_("\
219 Display native language translation of a textual message whose grammatical\n\
220 form depends on a number.\n"));
221       printf ("\n");
222       /* xgettext: no-wrap */
223       printf (_("\
224   -d, --domain=TEXTDOMAIN   retrieve translated message from TEXTDOMAIN\n\
225   -e                        enable expansion of some escape sequences\n\
226   -E                        (ignored for compatibility)\n\
227   -h, --help                display this help and exit\n\
228   -V, --version             display version information and exit\n\
229   [TEXTDOMAIN]              retrieve translated message from TEXTDOMAIN\n\
230   MSGID MSGID-PLURAL        translate MSGID (singular) / MSGID-PLURAL (plural)\n\
231   COUNT                     choose singular/plural form based on this value\n"));
232       printf ("\n");
233       /* xgettext: no-wrap */
234       printf (_("\
235 If the TEXTDOMAIN parameter is not given, the domain is determined from the\n\
236 environment variable TEXTDOMAIN.  If the message catalog is not found in the\n\
237 regular directory, another location can be specified with the environment\n\
238 variable TEXTDOMAINDIR.\n\
239 Standard search directory: %s\n"),
240 	      getenv ("IN_HELP2MAN") == NULL ? LOCALEDIR : "@localedir@");
241       printf ("\n");
242       fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
243     }
244 
245   exit (status);
246 }
247 
248 
249 /* Expand some escape sequences found in the argument string.  */
250 static const char *
expand_escape(const char * str)251 expand_escape (const char *str)
252 {
253   char *retval, *rp;
254   const char *cp = str;
255 
256   for (;;)
257     {
258       while (cp[0] != '\0' && cp[0] != '\\')
259 	++cp;
260       if (cp[0] == '\0')
261 	return str;
262       /* Found a backslash.  */
263       if (cp[1] == '\0')
264 	return str;
265       if (strchr ("abcfnrtv\\01234567", cp[1]) != NULL)
266 	break;
267       ++cp;
268     }
269 
270   retval = (char *) xmalloc (strlen (str));
271 
272   rp = retval + (cp - str);
273   memcpy (retval, str, cp - str);
274 
275   do
276     {
277       /* Here cp[0] == '\\'.  */
278       switch (*++cp)
279 	{
280 	case 'a':		/* alert */
281 	  *rp++ = '\a';
282 	  ++cp;
283 	  break;
284 	case 'b':		/* backspace */
285 	  *rp++ = '\b';
286 	  ++cp;
287 	  break;
288 	case 'f':		/* form feed */
289 	  *rp++ = '\f';
290 	  ++cp;
291 	  break;
292 	case 'n':		/* new line */
293 	  *rp++ = '\n';
294 	  ++cp;
295 	  break;
296 	case 'r':		/* carriage return */
297 	  *rp++ = '\r';
298 	  ++cp;
299 	  break;
300 	case 't':		/* horizontal tab */
301 	  *rp++ = '\t';
302 	  ++cp;
303 	  break;
304 	case 'v':		/* vertical tab */
305 	  *rp++ = '\v';
306 	  ++cp;
307 	  break;
308 	case '\\':
309 	  *rp = '\\';
310 	  ++cp;
311 	  break;
312 	case '0': case '1': case '2': case '3':
313 	case '4': case '5': case '6': case '7':
314 	  {
315 	    int ch = *cp++ - '0';
316 
317 	    if (*cp >= '0' && *cp <= '7')
318 	      {
319 		ch *= 8;
320 		ch += *cp++ - '0';
321 
322 		if (*cp >= '0' && *cp <= '7')
323 		  {
324 		    ch *= 8;
325 		    ch += *cp++ - '0';
326 		  }
327 	      }
328 	    *rp = ch;
329 	  }
330 	  break;
331 	default:
332 	  *rp = '\\';
333 	  break;
334 	}
335 
336       while (cp[0] != '\0' && cp[0] != '\\')
337 	*rp++ = *cp++;
338     }
339   while (cp[0] != '\0');
340 
341   /* Terminate string.  */
342   *rp = '\0';
343 
344   return (const char *) retval;
345 }
346