1/* gettext - retrieve text string from message catalog and print it.
2   Copyright (C) 1995-1997, 2000-2007 Free Software Foundation, Inc.
3   Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, May 1995.
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
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, add newline after last string.  This makes only sense in
41   the `echo' emulation mode.  */
42static bool add_newline;
43
44/* If true, expand escape sequences in strings before looking in the
45   message catalog.  */
46static bool do_expand;
47
48/* Long options.  */
49static const struct option long_options[] =
50{
51  { "domain", required_argument, NULL, 'd' },
52  { "help", no_argument, NULL, 'h' },
53  { "shell-script", no_argument, NULL, 's' },
54  { "version", no_argument, NULL, 'V' },
55  { NULL, 0, NULL, 0 }
56};
57
58/* Forward declaration of local functions.  */
59static void usage (int status)
60#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
61     __attribute__ ((noreturn))
62#endif
63;
64static const char *expand_escape (const char *str);
65
66int
67main (int argc, char *argv[])
68{
69  int optchar;
70  const char *msgid;
71
72  /* Default values for command line options.  */
73  bool do_help = false;
74  bool do_shell = false;
75  bool do_version = false;
76  const char *domain = getenv ("TEXTDOMAIN");
77  const char *domaindir = getenv ("TEXTDOMAINDIR");
78  add_newline = true;
79  do_expand = false;
80
81  /* Set program name for message texts.  */
82  set_program_name (argv[0]);
83
84#ifdef HAVE_SETLOCALE
85  /* Set locale via LC_ALL.  */
86  setlocale (LC_ALL, "");
87#endif
88
89  /* Set the text message domain.  */
90  bindtextdomain (PACKAGE, relocate (LOCALEDIR));
91  textdomain (PACKAGE);
92
93  /* Ensure that write errors on stdout are detected.  */
94  atexit (close_stdout);
95
96  /* Parse command line options.  */
97  while ((optchar = getopt_long (argc, argv, "+d:eEhnsV", long_options, NULL))
98	 != EOF)
99    switch (optchar)
100    {
101    case '\0':		/* Long option.  */
102      break;
103    case 'd':
104      domain = optarg;
105      break;
106    case 'e':
107      do_expand = true;
108      break;
109    case 'E':
110      /* Ignore.  Just for compatibility.  */
111      break;
112    case 'h':
113      do_help = true;
114      break;
115    case 'n':
116      add_newline = false;
117      break;
118    case 's':
119      do_shell = true;
120      break;
121    case 'V':
122      do_version = true;
123      break;
124    default:
125      usage (EXIT_FAILURE);
126    }
127
128  /* Version information is requested.  */
129  if (do_version)
130    {
131      printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
132      /* xgettext: no-wrap */
133      printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
134License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
135This is free software: you are free to change and redistribute it.\n\
136There is NO WARRANTY, to the extent permitted by law.\n\
137"),
138	      "1995-1997, 2000-2007");
139      printf (_("Written by %s.\n"), proper_name ("Ulrich Drepper"));
140      exit (EXIT_SUCCESS);
141    }
142
143  /* Help is requested.  */
144  if (do_help)
145    usage (EXIT_SUCCESS);
146
147  /* We have two major modes: use following Uniforum spec and as
148     internationalized `echo' program.  */
149  if (!do_shell)
150    {
151      /* We have to write a single strings translation to stdout.  */
152
153      /* Get arguments.  */
154      switch (argc - optind)
155	{
156	  default:
157	    error (EXIT_FAILURE, 0, _("too many arguments"));
158
159	  case 2:
160	    domain = argv[optind++];
161	    /* FALLTHROUGH */
162
163	  case 1:
164	    break;
165
166	  case 0:
167	    error (EXIT_FAILURE, 0, _("missing arguments"));
168	}
169
170      msgid = argv[optind++];
171
172      /* Expand escape sequences if enabled.  */
173      if (do_expand)
174	msgid = expand_escape (msgid);
175
176      /* If no domain name is given we don't translate.  */
177      if (domain == NULL || domain[0] == '\0')
178	{
179	  fputs (msgid, stdout);
180	}
181      else
182	{
183	  /* Bind domain to appropriate directory.  */
184	  if (domaindir != NULL && domaindir[0] != '\0')
185	    bindtextdomain (domain, domaindir);
186
187	  /* Write out the result.  */
188	  fputs (dgettext (domain, msgid), stdout);
189	}
190    }
191  else
192    {
193      if (optind < argc)
194	{
195	  /* If no domain name is given we print the original string.
196	     We mark this assigning NULL to domain.  */
197	  if (domain == NULL || domain[0] == '\0')
198	    domain = NULL;
199	  else
200	    /* Bind domain to appropriate directory.  */
201	    if (domaindir != NULL && domaindir[0] != '\0')
202	      bindtextdomain (domain, domaindir);
203
204	  /* We have to simulate `echo'.  All arguments are strings.  */
205	  do
206	    {
207	      msgid = argv[optind++];
208
209	      /* Expand escape sequences if enabled.  */
210	      if (do_expand)
211		msgid = expand_escape (msgid);
212
213	      /* Write out the result.  */
214	      fputs (domain == NULL ? msgid : dgettext (domain, msgid),
215		     stdout);
216
217	      /* We separate the arguments by a single ' '.  */
218	      if (optind < argc)
219		fputc (' ', stdout);
220	    }
221	  while (optind < argc);
222	}
223
224      /* If not otherwise told: add trailing newline.  */
225      if (add_newline)
226	fputc ('\n', stdout);
227    }
228
229  exit (EXIT_SUCCESS);
230}
231
232
233/* Display usage information and exit.  */
234static void
235usage (int status)
236{
237  if (status != EXIT_SUCCESS)
238    fprintf (stderr, _("Try `%s --help' for more information.\n"),
239	     program_name);
240  else
241    {
242      /* xgettext: no-wrap */
243      printf (_("\
244Usage: %s [OPTION] [[TEXTDOMAIN] MSGID]\n\
245or:    %s [OPTION] -s [MSGID]...\n\
246"), program_name, program_name);
247      printf ("\n");
248      /* xgettext: no-wrap */
249      printf (_("\
250Display native language translation of a textual message.\n"));
251      printf ("\n");
252      /* xgettext: no-wrap */
253      printf (_("\
254  -d, --domain=TEXTDOMAIN   retrieve translated messages from TEXTDOMAIN\n\
255  -e                        enable expansion of some escape sequences\n\
256  -E                        (ignored for compatibility)\n\
257  -h, --help                display this help and exit\n\
258  -n                        suppress trailing newline\n\
259  -V, --version             display version information and exit\n\
260  [TEXTDOMAIN] MSGID        retrieve translated message corresponding\n\
261                            to MSGID from TEXTDOMAIN\n"));
262      printf ("\n");
263      /* xgettext: no-wrap */
264      printf (_("\
265If the TEXTDOMAIN parameter is not given, the domain is determined from the\n\
266environment variable TEXTDOMAIN.  If the message catalog is not found in the\n\
267regular directory, another location can be specified with the environment\n\
268variable TEXTDOMAINDIR.\n\
269When used with the -s option the program behaves like the `echo' command.\n\
270But it does not simply copy its arguments to stdout.  Instead those messages\n\
271found in the selected catalog are translated.\n\
272Standard search directory: %s\n"),
273	      getenv ("IN_HELP2MAN") == NULL ? LOCALEDIR : "@localedir@");
274      printf ("\n");
275      /* TRANSLATORS: The placeholder indicates the bug-reporting address
276         for this package.  Please add _another line_ saying
277         "Report translation bugs to <...>\n" with the address for translation
278         bugs (typically your translation team's web or email address).  */
279      fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
280    }
281
282  exit (status);
283}
284
285
286/* Expand some escape sequences found in the argument string.  */
287static const char *
288expand_escape (const char *str)
289{
290  char *retval, *rp;
291  const char *cp = str;
292
293  for (;;)
294    {
295      while (cp[0] != '\0' && cp[0] != '\\')
296	++cp;
297      if (cp[0] == '\0')
298	return str;
299      /* Found a backslash.  */
300      if (cp[1] == '\0')
301	return str;
302      if (strchr ("abcfnrtv\\01234567", cp[1]) != NULL)
303	break;
304      ++cp;
305    }
306
307  retval = XNMALLOC (strlen (str), char);
308
309  rp = retval + (cp - str);
310  memcpy (retval, str, cp - str);
311
312  do
313    {
314      /* Here cp[0] == '\\'.  */
315      switch (*++cp)
316	{
317	case 'a':		/* alert */
318	  *rp++ = '\a';
319	  ++cp;
320	  break;
321	case 'b':		/* backspace */
322	  *rp++ = '\b';
323	  ++cp;
324	  break;
325	case 'c':		/* suppress trailing newline */
326	  add_newline = false;
327	  ++cp;
328	  break;
329	case 'f':		/* form feed */
330	  *rp++ = '\f';
331	  ++cp;
332	  break;
333	case 'n':		/* new line */
334	  *rp++ = '\n';
335	  ++cp;
336	  break;
337	case 'r':		/* carriage return */
338	  *rp++ = '\r';
339	  ++cp;
340	  break;
341	case 't':		/* horizontal tab */
342	  *rp++ = '\t';
343	  ++cp;
344	  break;
345	case 'v':		/* vertical tab */
346	  *rp++ = '\v';
347	  ++cp;
348	  break;
349	case '\\':
350	  *rp = '\\';
351	  ++cp;
352	  break;
353	case '0': case '1': case '2': case '3':
354	case '4': case '5': case '6': case '7':
355	  {
356	    int ch = *cp++ - '0';
357
358	    if (*cp >= '0' && *cp <= '7')
359	      {
360		ch *= 8;
361		ch += *cp++ - '0';
362
363		if (*cp >= '0' && *cp <= '7')
364		  {
365		    ch *= 8;
366		    ch += *cp++ - '0';
367		  }
368	      }
369	    *rp = ch;
370	  }
371	  break;
372	default:
373	  *rp = '\\';
374	  break;
375	}
376
377      while (cp[0] != '\0' && cp[0] != '\\')
378	*rp++ = *cp++;
379    }
380  while (cp[0] != '\0');
381
382  /* Terminate string.  */
383  *rp = '\0';
384
385  return (const char *) retval;
386}
387