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