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