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