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