OSDN Git Service

* locale.cc (print_locale_with_codeset): Align printing of locale names
[pf3gnuchains/pf3gnuchains4x.git] / winsup / utils / locale.cc
1 /*
2  * Copyright (c) 2010, 2011 Corinna Vinschen
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 #include <stdio.h>
27 #include <ctype.h>
28 #include <getopt.h>
29 #include <string.h>
30 #include <wchar.h>
31 #include <locale.h>
32 #include <langinfo.h>
33 #include <limits.h>
34 #include <sys/cygwin.h>
35 #define WINVER 0x0601
36 #include <windows.h>
37
38 #define LOCALE_ALIAS            "/usr/share/locale/locale.alias"
39 #define LOCALE_ALIAS_LINE_LEN   255
40
41 extern char *__progname;
42
43 void usage (FILE *, int) __attribute__ ((noreturn));
44
45 void
46 usage (FILE * stream, int status)
47 {
48   fprintf (stream,
49            "Usage: %s [-amsuUvh]\n"
50            "   or: %s [-ck] NAME\n"
51            "Get locale-specific information.\n"
52            "\n"
53            "Options:\n"
54            "\n"
55            "  -a, --all-locales    List all available supported locales\n"
56            "  -c, --category-name  List information about given category NAME\n"
57            "  -k, --keyword-name   Print information about given keyword NAME\n"
58            "  -m, --charmaps       List all available character maps\n"
59            "  -s, --system         Print system default locale\n"
60            "  -u, --user           Print user's default locale\n"
61            "  -U, --utf            Attach \".UTF-8\" to the result\n"
62            "  -v, --verbose        More verbose output\n"
63            "  -h, --help           This text\n",
64            __progname, __progname);
65   exit (status);
66 }
67
68 struct option longopts[] = {
69   {"all-locales", no_argument, NULL, 'a'},
70   {"category-name", no_argument, NULL, 'c'},
71   {"keyword-name", no_argument, NULL, 'k'},
72   {"charmaps", no_argument, NULL, 'm'},
73   {"system", no_argument, NULL, 's'},
74   {"user", no_argument, NULL, 'u'},
75   {"utf", no_argument, NULL, 'U'},
76   {"verbose", no_argument, NULL, 'v'},
77   {"help", no_argument, NULL, 'h'},
78   {0, no_argument, NULL, 0}
79 };
80 const char *opts = "achkmsuUv";
81
82 int
83 getlocale (LCID lcid, char *name)
84 {
85   char iso639[10];
86   char iso3166[10];
87
88   iso3166[0] = '\0';
89   if (!GetLocaleInfo (lcid, LOCALE_SISO639LANGNAME, iso639, 10))
90     return 0;
91   GetLocaleInfo (lcid, LOCALE_SISO3166CTRYNAME, iso3166, 10);
92   sprintf (name, "%s%s%s", iso639, lcid > 0x3ff ? "_" : "",
93                            lcid > 0x3ff ? iso3166 : "");
94   return 1;
95 }
96
97 typedef struct {
98   const char *name;
99   const wchar_t *language;
100   const wchar_t *territory;
101   const char *codeset;
102   bool alias;
103 } loc_t;
104 loc_t *locale;
105 size_t loc_max;
106 size_t loc_num;
107
108 void
109 print_codeset (const char *codeset)
110 {
111   for (; *codeset; ++codeset)
112     if (*codeset != '-')
113       putc (tolower ((int)(unsigned char) *codeset), stdout);
114 }
115
116 void
117 print_locale_with_codeset (int verbose, loc_t *locale, bool utf8,
118                            const char *modifier)
119 {
120   static const char *sysroot;
121   char locname[32];
122
123   if (verbose
124       && (!strcmp (locale->name, "C") || !strcmp (locale->name, "POSIX")))
125     return;
126   if (!sysroot)
127     {
128       char sysbuf[PATH_MAX];
129       HMODULE k32 = GetModuleHandleW (L"kernel32.dll");
130       if (GetModuleFileName (k32, sysbuf, PATH_MAX))
131         sysroot = (const char *) cygwin_create_path (CCP_WIN_A_TO_POSIX,
132                                                      sysbuf);
133       if (!sysroot)
134         sysroot = "kernel32.dll";
135     }
136   snprintf (locname, 32, "%s%s%s%s", locale->name, utf8 ? ".utf8" : "",
137                                      modifier ? "@" : "", modifier ?: "");
138   if (verbose)
139     fputs ("locale: ", stdout);
140   if (verbose)
141     {
142       printf ("%-15s ", locname);
143       printf ("archive: %s\n",
144       locale->alias ? LOCALE_ALIAS : sysroot);
145       puts ("-------------------------------------------------------------------------------");
146       printf (" language | %ls\n", locale->language);
147       printf ("territory | %ls\n", locale->territory);
148       printf ("  codeset | %s\n\n", utf8 ? "UTF-8" : locale->codeset);
149     }
150   else
151     printf ("%s\n", locname);
152 }
153
154 void
155 print_locale (int verbose, loc_t *locale)
156 {
157   print_locale_with_codeset (verbose, locale, false, NULL);
158   char *modifier = strchr (locale->name, '@');
159   if (!locale->alias)
160     {
161       if (!modifier)
162         print_locale_with_codeset (verbose, locale, true, NULL);
163       else if (!strcmp (modifier, "@cjknarrow"))
164         {
165           *modifier++ = '\0';
166           print_locale_with_codeset (verbose, locale, true, modifier);
167         }
168     }
169 }
170
171 int
172 compare_locales (const void *a, const void *b)
173 {
174   const loc_t *la = (const loc_t *) a;
175   const loc_t *lb = (const loc_t *) b;
176   return strcmp (la->name, lb->name);
177 }
178
179 void
180 add_locale (const char *name, const wchar_t *language, const wchar_t *territory,
181             bool alias = false)
182 {
183   char orig_locale[32];
184
185   if (loc_num >= loc_max)
186     {
187       loc_t *tmp = (loc_t *) realloc (locale, (loc_max + 32) * sizeof (loc_t));
188       if (!tmp)
189         {
190           fprintf (stderr, "Out of memory!\n");
191           exit (1);
192         }
193       locale = tmp;
194       loc_max += 32;
195     }
196   locale[loc_num].name = strdup (name);
197   locale[loc_num].language = wcsdup (language);
198   locale[loc_num].territory = wcsdup (territory);
199   strcpy (orig_locale, setlocale (LC_CTYPE, NULL));
200   setlocale (LC_CTYPE, name);
201   locale[loc_num].codeset = strdup (nl_langinfo (CODESET));
202   setlocale (LC_CTYPE, orig_locale);
203   locale[loc_num].alias = alias;
204   ++loc_num;
205 }
206
207 void
208 add_locale_alias_locales ()
209 {
210   char alias_buf[LOCALE_ALIAS_LINE_LEN + 1], *c;
211   const char *alias, *replace;
212   char orig_locale[32];
213   loc_t search, *loc;
214   size_t orig_loc_num = loc_num;
215
216   FILE *fp = fopen (LOCALE_ALIAS, "rt");
217   if (!fp)
218     return;
219   strcpy (orig_locale, setlocale (LC_CTYPE, NULL));
220   while (fgets (alias_buf, LOCALE_ALIAS_LINE_LEN + 1, fp))
221     {
222       alias_buf[LOCALE_ALIAS_LINE_LEN] = '\0';
223       c = strrchr (alias_buf, '\n');
224       if (c)
225         *c = '\0';
226       c = alias_buf;
227       c += strspn (c, " \t");
228       if (!*c || *c == '#')
229         continue;
230       alias = c;
231       c += strcspn (c, " \t");
232       *c++ = '\0';
233       c += strspn (c, " \t");
234       if (*c == '#')
235         continue;
236       replace = c;
237       c += strcspn (c, " \t");
238       *c++ = '\0';
239       c = strchr (replace, '.');
240       if (c)
241         *c = '\0';
242       search.name = replace;
243       loc = (loc_t *) bsearch (&search, locale, orig_loc_num, sizeof (loc_t),
244                                compare_locales);
245       add_locale (alias, loc ? loc->language : L"", loc ? loc->territory : L"",
246                   true);
247     }
248   fclose (fp);
249 }
250
251 void
252 print_all_locales (int verbose)
253 {
254   LCID lcid = 0;
255   char name[32];
256   DWORD cp;
257
258   unsigned lang, sublang;
259
260   add_locale ("C", L"C", L"POSIX");
261   add_locale ("POSIX", L"C", L"POSIX", true);
262   for (lang = 1; lang <= 0xff; ++lang)
263     {
264       struct {
265         wchar_t language[256];
266         wchar_t country[256];
267         char loc[32];
268       } loc_list[32];
269       int lcnt = 0;
270
271       for (sublang = 1; sublang <= 0x3f; ++sublang)
272         {
273           lcid = (sublang << 10) | lang;
274           if (getlocale (lcid, name))
275             {
276               wchar_t language[256];
277               wchar_t country[256];
278               int i;
279               char *c, loc[32];
280               wchar_t wbuf[9];
281
282               /* Go figure.  Even the English name of a language or
283                  locale might contain native characters. */
284               GetLocaleInfoW (lcid, LOCALE_SENGLANGUAGE, language, 256);
285               GetLocaleInfoW (lcid, LOCALE_SENGCOUNTRY, country, 256);
286               /* Avoid dups */
287               for (i = 0; i < lcnt; ++ i)
288                 if (!wcscmp (loc_list[i].language, language)
289                     && !wcscmp (loc_list[i].country, country))
290                   break;
291               if (i < lcnt)
292                 continue;
293               if (lcnt < 32)
294                 {
295                   wcscpy (loc_list[lcnt].language, language);
296                   wcscpy (loc_list[lcnt].country, country);
297                 }
298               c = stpcpy (loc, name);
299               /* Convert old sr_SP silently to sr_CS on old systems.
300                  Make sure sr_CS country is in recent shape. */
301               if (lang == LANG_SERBIAN
302                   && (sublang == SUBLANG_SERBIAN_LATIN
303                       || sublang == SUBLANG_SERBIAN_CYRILLIC))
304                 {
305                   c = stpcpy (loc, "sr_CS");
306                   wcscpy (country, L"Serbia and Montenegro (Former)");
307                 }
308               /* Now check certain conditions to figure out if that
309                  locale requires a modifier. */
310               if (lang == LANG_SERBIAN && !strncmp (loc, "sr_", 3)
311                   && wcsstr (language, L"(Latin)"))
312                 stpcpy (c, "@latin");
313               else if (lang == LANG_UZBEK
314                        && sublang == SUBLANG_UZBEK_CYRILLIC)
315                 stpcpy (c, "@cyrillic");
316               /* Avoid more dups */
317               for (i = 0; i < lcnt; ++ i)
318                 if (!strcmp (loc_list[i].loc, loc))
319                   {
320                     lcnt++;
321                     break;
322                   }
323               if (i < lcnt)
324                 continue;
325               if (lcnt < 32)
326                 strcpy (loc_list[lcnt++].loc, loc);
327               /* Print */
328               add_locale (loc, language, country);
329               /* Check for locales which sport a modifier for
330                  changing the codeset and other stuff. */
331               if (lang == LANG_BELARUSIAN
332                   && sublang == SUBLANG_BELARUSIAN_BELARUS)
333                 stpcpy (c, "@latin");
334               else if (lang == LANG_TATAR
335                        && sublang == SUBLANG_TATAR_RUSSIA)
336                 stpcpy (c, "@iqtelif");
337               else if (GetLocaleInfoW (lcid,
338                                        LOCALE_IDEFAULTANSICODEPAGE
339                                        | LOCALE_RETURN_NUMBER,
340                                        (PWCHAR) &cp, sizeof cp)
341                        && cp == 1252 /* Latin1*/
342                        && GetLocaleInfoW (lcid, LOCALE_SINTLSYMBOL, wbuf, 9)
343                        && !wcsncmp (wbuf, L"EUR", 3))
344                 stpcpy (c, "@euro");
345               else if (lang == LANG_JAPANESE
346                        || lang == LANG_KOREAN
347                        || lang == LANG_CHINESE)
348                 stpcpy (c, "@cjknarrow");
349               else
350                 continue;
351               add_locale (loc, language, country);
352             }
353         }
354       /* Check Serbian language for the available territories.  Up to
355          Server 2003 we only had sr_SP (silently converted to sr_CS
356          above), in Vista we had only sr_CS.  First starting with W7 we
357          have the actual sr_RS and sr_ME.  However, all of them are
358          supported on all systems in Cygwin.  So we fake them here, if
359          they are missing. */
360       if (lang == LANG_SERBIAN)
361         {
362           int sr_CS_idx = -1;
363           int sr_RS_idx = -1;
364           int i;
365
366           for (i = 0; i < lcnt; ++ i)
367             if (!strcmp (loc_list[i].loc, "sr_CS"))
368               sr_CS_idx = i;
369             else if (!strcmp (loc_list[i].loc, "sr_RS"))
370               sr_RS_idx = i;
371           if (sr_CS_idx > 0 && sr_RS_idx == -1)
372             {
373               add_locale ("sr_RS@latin", L"Serbian (Latin)", L"Serbia");
374               add_locale ("sr_RS", L"Serbian (Cyrillic)", L"Serbia");
375               add_locale ("sr_ME@latin", L"Serbian (Latin)", L"Montenegro");
376               add_locale ("sr_ME", L"Serbian (Cyrillic)", L"Montenegro");
377             }
378         }
379     }
380   /* First sort allows add_locale_alias_locales to bsearch in locales. */
381   qsort (locale, loc_num, sizeof (loc_t), compare_locales);
382   add_locale_alias_locales ();
383   qsort (locale, loc_num, sizeof (loc_t), compare_locales);
384   for (size_t i = 0; i < loc_num; ++i)
385     print_locale (verbose, &locale[i]);
386 }
387
388 void
389 print_charmaps ()
390 {
391   /* FIXME: We need a method to fetch the available charsets from Cygwin, */
392   const char *charmaps[] =
393   {
394     "ASCII",
395     "BIG5",
396     "CP1125",
397     "CP1250",
398     "CP1251",
399     "CP1252",
400     "CP1253",
401     "CP1254",
402     "CP1255",
403     "CP1256",
404     "CP1257",
405     "CP1258",
406     "CP437",
407     "CP720",
408     "CP737",
409     "CP775",
410     "CP850",
411     "CP852",
412     "CP855",
413     "CP857",
414     "CP858",
415     "CP862",
416     "CP866",
417     "CP874",
418     "CP932",
419     "EUC-CN",
420     "EUC-JP",
421     "EUC-KR",
422     "GB2312",
423     "GBK",
424     "GEORGIAN-PS",
425     "ISO-8859-1",
426     "ISO-8859-10",
427     "ISO-8859-11",
428     "ISO-8859-13",
429     "ISO-8859-14",
430     "ISO-8859-15",
431     "ISO-8859-16",
432     "ISO-8859-2",
433     "ISO-8859-3",
434     "ISO-8859-4",
435     "ISO-8859-5",
436     "ISO-8859-6",
437     "ISO-8859-7",
438     "ISO-8859-8",
439     "ISO-8859-9",
440     "KOI8-R",
441     "KOI8-U",
442     "PT154",
443     "SJIS",
444     "TIS-620",
445     "UTF-8",
446     NULL
447   };
448   const char **charmap = charmaps;
449   while (*charmap)
450     printf ("%s\n", *charmap++);
451 }
452
453 void
454 print_lc_ivalue (int key, const char *name, int value)
455 {
456   if (key)
457     printf ("%s=", name);
458   printf ("%d", value == CHAR_MAX ? -1 : value);
459   fputc ('\n', stdout);
460 }
461
462 void
463 print_lc_svalue (int key, const char *name, const char *value)
464 {
465   if (key)
466     printf ("%s=\"", name);
467   fputs (value, stdout);
468   if (key)
469     fputc ('"', stdout);
470   fputc ('\n', stdout);
471 }
472
473 void
474 print_lc_sepstrings (int key, const char *name, const char *value)
475 {
476   char *c;
477
478   if (key)
479     printf ("%s=", name);
480   while (value && *value)
481     {
482       if (key)
483         fputc ('"', stdout);
484       c = strchr (value, ';');
485       if (!c)
486         {
487           fputs (value, stdout);
488           value = NULL;
489         }
490       else
491         {
492           printf ("%.*s", c - value, value);
493           value = c + 1;
494         }
495       if (key)
496         fputc ('"', stdout);
497       if (value && *value)
498         fputc (';', stdout);
499     }
500   fputc ('\n', stdout);
501 }
502
503 void
504 print_lc_strings (int key, const char *name, int from, int to)
505 {
506   if (key)
507     printf ("%s=\"", name);
508   for (int i = from; i <= to; ++i)
509     printf ("%s%s", i > from ? ";" : "", nl_langinfo (i));
510   if (key)
511     fputc ('"', stdout);
512   fputc ('\n', stdout);
513 }
514
515 void
516 print_lc_grouping (int key, const char *name, const char *grouping)
517 {
518   if (key)
519     printf ("%s=", name);
520   for (const char *g = grouping; *g; ++g)
521     printf ("%s%d", g > grouping ? ";" : "", *g == CHAR_MAX ? -1 : *g);
522   fputc ('\n', stdout);
523 }
524
525 enum type_t
526 {
527   is_string_fake,
528   is_grouping,
529   is_string,
530   is_mstrings,
531   is_sepstrings,
532   is_int,
533   is_wchar,
534   is_end
535 };
536
537 struct lc_names_t
538 {
539   const char *name;
540   type_t      type;
541   size_t      fromval;
542   size_t      toval;
543 };
544
545 const char *fake_string[] = {
546  "upper;lower;alpha;digit;xdigit;space;print;graph;blank;cntrl;punct;alnum",
547  "upper\";\"lower\";\"alpha\";\"digit\";\"xdigit\";\"space\";\"print\";\"graph\";\"blank\";\"cntrl\";\"punct\";\"alnum",
548  "toupper;tolower",
549  "toupper\";\"tolower"
550 };
551
552 lc_names_t lc_ctype_names[] =
553 {
554   { "ctype-class-names",         is_string_fake, 0,                      0 },
555   { "ctype-map-names",           is_string_fake, 2,                      0 },
556   { "ctype-outdigit0_mb",        is_string,     _NL_CTYPE_OUTDIGITS0_MB, 0 },
557   { "ctype-outdigit1_mb",        is_string,     _NL_CTYPE_OUTDIGITS1_MB, 0 },
558   { "ctype-outdigit2_mb",        is_string,     _NL_CTYPE_OUTDIGITS2_MB, 0 },
559   { "ctype-outdigit3_mb",        is_string,     _NL_CTYPE_OUTDIGITS3_MB, 0 },
560   { "ctype-outdigit4_mb",        is_string,     _NL_CTYPE_OUTDIGITS4_MB, 0 },
561   { "ctype-outdigit5_mb",        is_string,     _NL_CTYPE_OUTDIGITS5_MB, 0 },
562   { "ctype-outdigit6_mb",        is_string,     _NL_CTYPE_OUTDIGITS6_MB, 0 },
563   { "ctype-outdigit7_mb",        is_string,     _NL_CTYPE_OUTDIGITS7_MB, 0 },
564   { "ctype-outdigit8_mb",        is_string,     _NL_CTYPE_OUTDIGITS8_MB, 0 },
565   { "ctype-outdigit9_mb",        is_string,     _NL_CTYPE_OUTDIGITS9_MB, 0 },
566   { "ctype-outdigit0_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS0_WC, 0 },
567   { "ctype-outdigit1_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS1_WC, 0 },
568   { "ctype-outdigit2_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS2_WC, 0 },
569   { "ctype-outdigit3_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS3_WC, 0 },
570   { "ctype-outdigit4_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS4_WC, 0 },
571   { "ctype-outdigit5_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS5_WC, 0 },
572   { "ctype-outdigit6_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS6_WC, 0 },
573   { "ctype-outdigit7_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS7_WC, 0 },
574   { "ctype-outdigit8_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS8_WC, 0 },
575   { "ctype-outdigit9_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS9_WC, 0 },
576   { "charmap",                   is_string,     CODESET,                 0 },
577   { "ctype-mb-cur-max",          is_int,        _NL_CTYPE_MB_CUR_MAX,    0 },
578   { NULL,                        is_end,        0,                       0 }
579 };
580
581 lc_names_t lc_numeric_names[] =
582 {
583   { "decimal_point",             is_string,     RADIXCHAR,               0 },
584   { "thousands_sep",             is_string,     THOUSEP,                 0 },
585   { "grouping",                  is_grouping,   _NL_NUMERIC_GROUPING,    0 },
586   { "numeric-decimal-point-wc",  is_wchar,      _NL_NUMERIC_DECIMAL_POINT_WC, 0 },
587   { "numeric-thousands-sep-wc",  is_wchar,      _NL_NUMERIC_THOUSANDS_SEP_WC, 0 },
588   { "numeric-codeset",           is_string,     _NL_NUMERIC_CODESET,     0 },
589   { NULL,                        is_end,        0,                       0 }
590 };
591
592 lc_names_t lc_time_names[] =
593 {
594   { "abday",                     is_mstrings,   ABDAY_1,         ABDAY_7  },
595   { "day",                       is_mstrings,   DAY_1,           DAY_7    },
596   { "abmon",                     is_mstrings,   ABMON_1,         ABMON_12 },
597   { "mon",                       is_mstrings,   MON_1,           MON_12   },
598   { "am_pm",                     is_mstrings,   AM_STR,          PM_STR   },
599   { "d_t_fmt",                   is_string,     D_T_FMT,                0 },
600   { "d_fmt",                     is_string,     D_FMT,                  0 },
601   { "t_fmt",                     is_string,     T_FMT,                  0 },
602   { "t_fmt_ampm",                is_string,     T_FMT_AMPM,             0 },
603   { "era",                       is_sepstrings, ERA,                    0 },
604   { "era_d_fmt",                 is_string,     ERA_D_FMT,              0 },
605   { "alt_digits",                is_sepstrings,ALT_DIGITS,              0 },
606   { "era_d_t_fmt",               is_string,     ERA_D_T_FMT,            0 },
607   { "era_t_fmt",                 is_string,     ERA_T_FMT,              0 },
608   { "date_fmt",                  is_string,     _DATE_FMT,              0 },
609   { "time-codeset",              is_string,     _NL_TIME_CODESET,       0 },
610   { NULL,                        is_end,        0,                      0 }
611 };
612
613 lc_names_t lc_collate_names[] =
614 {
615   { "collate-codeset",           is_string,     _NL_COLLATE_CODESET,    0 },
616   { NULL,                        is_end,        0,                      0 }
617 };
618
619 lc_names_t lc_monetary_names[] =
620 {
621   { "int_curr_symbol",           is_string,     _NL_MONETARY_INT_CURR_SYMBOL, 0 },
622   { "currency_symbol",           is_string,     _NL_MONETARY_CURRENCY_SYMBOL, 0 },
623   { "mon_decimal_point",         is_string,     _NL_MONETARY_MON_DECIMAL_POINT, 0 },
624   { "mon_thousands_sep",         is_string,     _NL_MONETARY_MON_THOUSANDS_SEP, 0 },
625   { "mon_grouping",              is_grouping,   _NL_MONETARY_MON_GROUPING, 0 },
626   { "positive_sign",             is_string,     _NL_MONETARY_POSITIVE_SIGN, 0 },
627   { "negative_sign",             is_string,     _NL_MONETARY_NEGATIVE_SIGN, 0 },
628   { "int_frac_digits",           is_int,        _NL_MONETARY_INT_FRAC_DIGITS, 0 },
629   { "frac_digits",               is_int,        _NL_MONETARY_FRAC_DIGITS,   0 },
630   { "p_cs_precedes",             is_int,        _NL_MONETARY_P_CS_PRECEDES, 0 },
631   { "p_sep_by_space",            is_int,        _NL_MONETARY_P_SEP_BY_SPACE, 0 },
632   { "n_cs_precedes",             is_int,        _NL_MONETARY_N_CS_PRECEDES, 0 },
633   { "n_sep_by_space",            is_int,        _NL_MONETARY_N_SEP_BY_SPACE, 0 },
634   { "p_sign_posn",               is_int,        _NL_MONETARY_P_SIGN_POSN,   0 },
635   { "n_sign_posn",               is_int,        _NL_MONETARY_N_SIGN_POSN,   0 },
636   { "int_p_cs_precedes",         is_int,        _NL_MONETARY_INT_P_CS_PRECEDES, 0 },
637   { "int_p_sep_by_space",        is_int,        _NL_MONETARY_INT_P_SEP_BY_SPACE,0 },
638   { "int_n_cs_precedes",         is_int,        _NL_MONETARY_INT_N_CS_PRECEDES, 0 },
639   { "int_n_sep_by_space",        is_int,        _NL_MONETARY_INT_N_SEP_BY_SPACE,0 },
640   { "int_p_sign_posn",           is_int,        _NL_MONETARY_INT_P_SIGN_POSN, 0 },
641   { "int_n_sign_posn",           is_int,        _NL_MONETARY_INT_N_SIGN_POSN, 0 },
642   { "monetary-decimal-point-wc", is_wchar,      _NL_MONETARY_WMON_DECIMAL_POINT, 0 },
643   { "monetary-thousands-sep-wc", is_wchar,      _NL_MONETARY_WMON_THOUSANDS_SEP, 0 },
644   { "monetary-codeset",          is_string,     _NL_MONETARY_CODESET,      0 },
645   { NULL,                        is_end,        0,                         0 }
646 };
647
648 lc_names_t lc_messages_names[] =
649 {
650   { "yesexpr",                   is_string,     YESEXPR,                0 },
651   { "noexpr",                    is_string,     NOEXPR,                 0 },
652   { "yesstr",                    is_string,     YESSTR,                 0 },
653   { "nostr",                     is_string,     NOSTR,                  0 },
654   { "messages-codeset",          is_string,     _NL_MESSAGES_CODESET,   0 },
655   { NULL,                        is_end,        0,                      0 }
656 };
657
658 void
659 print_lc (int cat, int key, const char *category, const char *name,
660           lc_names_t *lc_name)
661 {
662   if (cat)
663     printf ("%s\n", category);
664   for (lc_names_t *lc = lc_name; lc->type != is_end; ++lc)
665     if (!name || !strcmp (name, lc->name))
666       switch (lc->type)
667         {
668         case is_string_fake:
669           print_lc_svalue (key, lc->name, fake_string[lc->fromval + key]);
670           break;
671         case is_grouping:
672           print_lc_grouping (key, lc->name, nl_langinfo (lc->fromval));
673           break;
674         case is_string:
675           print_lc_svalue (key, lc->name, nl_langinfo (lc->fromval));
676           break;
677         case is_sepstrings:
678           print_lc_sepstrings (key, lc->name, nl_langinfo (lc->fromval));
679           break;
680         case is_mstrings:
681           print_lc_strings (key, lc->name, lc->fromval, lc->toval);
682           break;
683         case is_int:
684           print_lc_ivalue (key, lc->name, (int) *nl_langinfo (lc->fromval));
685           break;
686         case is_wchar:
687           print_lc_ivalue (key, lc->name,
688                            *(wchar_t *) nl_langinfo (lc->fromval));
689           break;
690         default:
691           break;
692         }
693 }
694
695 struct cat_t
696 {
697   const char *category;
698   int lc_cat;
699   lc_names_t *lc_names;
700 } categories[] =
701 {
702   { "LC_CTYPE",    LC_CTYPE,    lc_ctype_names    },
703   { "LC_NUMERIC",  LC_NUMERIC,  lc_numeric_names  },
704   { "LC_TIME",     LC_TIME,     lc_time_names     },
705   { "LC_COLLATE",  LC_COLLATE,  lc_collate_names  },
706   { "LC_MONETARY", LC_MONETARY, lc_monetary_names },
707   { "LC_MESSAGES", LC_MESSAGES, lc_messages_names },
708   { NULL,          0,           NULL              }
709 };
710
711 void
712 print_names (int cat, int key, const char *name)
713 {
714   struct cat_t *c;
715   lc_names_t *lc;
716
717   for (c = categories; c->category; ++c)
718     if (!strcmp (name, c->category))
719       {
720         print_lc (cat, key, c->category, NULL, c->lc_names);
721         return;
722       }
723   for (c = categories; c->category; ++c)
724     for (lc = c->lc_names; lc->type != is_end; ++lc)
725       if (!strcmp (name, lc->name))
726       {
727         print_lc (cat, key, c->category, lc->name, lc);
728         return;
729       }
730 }
731
732 void
733 print_lc ()
734 {
735   printf ("LANG=%s\n", getenv ("LANG") ?: "");
736   printf ("LC_CTYPE=\"%s\"\n", setlocale (LC_CTYPE, NULL));
737   printf ("LC_NUMERIC=\"%s\"\n", setlocale (LC_NUMERIC, NULL));
738   printf ("LC_TIME=\"%s\"\n", setlocale (LC_TIME, NULL));
739   printf ("LC_COLLATE=\"%s\"\n", setlocale (LC_COLLATE, NULL));
740   printf ("LC_MONETARY=\"%s\"\n", setlocale (LC_MONETARY, NULL));
741   printf ("LC_MESSAGES=\"%s\"\n", setlocale (LC_MESSAGES, NULL));
742   printf ("LC_ALL=%s\n", getenv ("LC_ALL") ?: "");
743 }
744
745 int
746 main (int argc, char **argv)
747 {
748   int opt;
749   LCID lcid = 0;
750   int all = 0;
751   int cat = 0;
752   int key = 0;
753   int maps = 0;
754   int verbose = 0;
755   const char *utf = "";
756   char name[32];
757
758   setlocale (LC_ALL, "");
759   while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != EOF)
760     switch (opt)
761       {
762       case 'a':
763         all = 1;
764         break;
765       case 'c':
766         cat = 1;
767         break;
768       case 'k':
769         key = 1;
770         break;
771       case 'm':
772         maps = 1;
773         break;
774       case 's':
775         lcid = LOCALE_SYSTEM_DEFAULT;
776         break;
777       case 'u':
778         lcid = LOCALE_USER_DEFAULT;
779         break;
780       case 'U':
781         utf = ".UTF-8";
782         break;
783       case 'v':
784         verbose = 1;
785         break;
786       case 'h':
787         usage (stdout, 0);
788         break;
789       default:
790         usage (stderr, 1);
791         break;
792       }
793   if (all)
794     print_all_locales (verbose);
795   else if (maps)
796     print_charmaps ();
797   else if (lcid)
798     {
799       if (getlocale (lcid, name))
800         printf ("%s%s\n", name, utf);
801     }
802   else if (optind < argc)
803     while (optind < argc)
804       print_names (cat, key, argv[optind++]);
805   else
806     print_lc ();
807   return 0;
808 }