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