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.  */
44static int do_expand;
45
46/* Long options.  */
47static 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.  */
56static void usage (int status)
57#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
58     __attribute__ ((noreturn))
59#endif
60;
61static const char *expand_escape (const char *str);
62
63int
64main (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\
126This is free software; see the source for copying conditions.  There is NO\n\
127warranty; 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.  */
204static void
205usage (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 (_("\
214Usage: %s [OPTION] [TEXTDOMAIN] MSGID MSGID-PLURAL COUNT\n\
215"), program_name);
216      printf ("\n");
217      /* xgettext: no-wrap */
218      printf (_("\
219Display native language translation of a textual message whose grammatical\n\
220form 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 (_("\
235If the TEXTDOMAIN parameter is not given, the domain is determined from the\n\
236environment variable TEXTDOMAIN.  If the message catalog is not found in the\n\
237regular directory, another location can be specified with the environment\n\
238variable TEXTDOMAINDIR.\n\
239Standard 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.  */
250static const char *
251expand_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