Imported Upstream version 0.19.7
[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, 2015 Free Software Foundation,
3    Inc.
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 3 of the License, or
8    (at your option) 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, see <http://www.gnu.org/licenses/>.  */
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 "propername.h"
37 #include "gettext.h"
38
39 #define _(str) gettext (str)
40
41 /* If true, expand escape sequences in strings before looking in the
42    message catalog.  */
43 static int do_expand;
44
45 /* Long options.  */
46 static const struct option long_options[] =
47 {
48   { "domain", required_argument, NULL, 'd' },
49   { "help", no_argument, NULL, 'h' },
50   { "version", no_argument, NULL, 'V' },
51   { NULL, 0, NULL, 0 }
52 };
53
54 /* Forward declaration of local functions.  */
55 static void usage (int status)
56 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
57      __attribute__ ((noreturn))
58 #endif
59 ;
60 static const char *expand_escape (const char *str);
61
62 int
63 main (int argc, char *argv[])
64 {
65   int optchar;
66   const char *msgid;
67   const char *msgid_plural;
68   const char *count;
69   unsigned long n;
70
71   /* Default values for command line options.  */
72   bool do_help = false;
73   bool do_version = false;
74   const char *domain = getenv ("TEXTDOMAIN");
75   const char *domaindir = getenv ("TEXTDOMAINDIR");
76   do_expand = false;
77
78   /* Set program name for message texts.  */
79   set_program_name (argv[0]);
80
81 #ifdef HAVE_SETLOCALE
82   /* Set locale via LC_ALL.  */
83   setlocale (LC_ALL, "");
84 #endif
85
86   /* Set the text message domain.  */
87   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
88   textdomain (PACKAGE);
89
90   /* Ensure that write errors on stdout are detected.  */
91   atexit (close_stdout);
92
93   /* Parse command line options.  */
94   while ((optchar = getopt_long (argc, argv, "+d:eEhV", long_options, NULL))
95          != EOF)
96     switch (optchar)
97     {
98     case '\0':          /* Long option.  */
99       break;
100     case 'd':
101       domain = optarg;
102       break;
103     case 'e':
104       do_expand = true;
105       break;
106     case 'E':
107       /* Ignore.  Just for compatibility.  */
108       break;
109     case 'h':
110       do_help = true;
111       break;
112     case 'V':
113       do_version = true;
114       break;
115     default:
116       usage (EXIT_FAILURE);
117     }
118
119   /* Version information is requested.  */
120   if (do_version)
121     {
122       printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
123       /* xgettext: no-wrap */
124       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
125 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
126 This is free software: you are free to change and redistribute it.\n\
127 There is NO WARRANTY, to the extent permitted by law.\n\
128 "),
129               "1995-1997, 2000-2007");
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
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       /* TRANSLATORS: The placeholder indicates the bug-reporting address
243          for this package.  Please add _another line_ saying
244          "Report translation bugs to <...>\n" with the address for translation
245          bugs (typically your translation team's web or email address).  */
246       fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
247     }
248
249   exit (status);
250 }
251
252
253 /* Expand some escape sequences found in the argument string.  */
254 static const char *
255 expand_escape (const char *str)
256 {
257   char *retval, *rp;
258   const char *cp = str;
259
260   for (;;)
261     {
262       while (cp[0] != '\0' && cp[0] != '\\')
263         ++cp;
264       if (cp[0] == '\0')
265         return str;
266       /* Found a backslash.  */
267       if (cp[1] == '\0')
268         return str;
269       if (strchr ("abcfnrtv\\01234567", cp[1]) != NULL)
270         break;
271       ++cp;
272     }
273
274   retval = XNMALLOC (strlen (str), char);
275
276   rp = retval + (cp - str);
277   memcpy (retval, str, cp - str);
278
279   do
280     {
281       /* Here cp[0] == '\\'.  */
282       switch (*++cp)
283         {
284         case 'a':               /* alert */
285           *rp++ = '\a';
286           ++cp;
287           break;
288         case 'b':               /* backspace */
289           *rp++ = '\b';
290           ++cp;
291           break;
292         case 'f':               /* form feed */
293           *rp++ = '\f';
294           ++cp;
295           break;
296         case 'n':               /* new line */
297           *rp++ = '\n';
298           ++cp;
299           break;
300         case 'r':               /* carriage return */
301           *rp++ = '\r';
302           ++cp;
303           break;
304         case 't':               /* horizontal tab */
305           *rp++ = '\t';
306           ++cp;
307           break;
308         case 'v':               /* vertical tab */
309           *rp++ = '\v';
310           ++cp;
311           break;
312         case '\\':
313           *rp = '\\';
314           ++cp;
315           break;
316         case '0': case '1': case '2': case '3':
317         case '4': case '5': case '6': case '7':
318           {
319             int ch = *cp++ - '0';
320
321             if (*cp >= '0' && *cp <= '7')
322               {
323                 ch *= 8;
324                 ch += *cp++ - '0';
325
326                 if (*cp >= '0' && *cp <= '7')
327                   {
328                     ch *= 8;
329                     ch += *cp++ - '0';
330                   }
331               }
332             *rp = ch;
333           }
334           break;
335         default:
336           *rp = '\\';
337           break;
338         }
339
340       while (cp[0] != '\0' && cp[0] != '\\')
341         *rp++ = *cp++;
342     }
343   while (cp[0] != '\0');
344
345   /* Terminate string.  */
346   *rp = '\0';
347
348   return (const char *) retval;
349 }