locale.c revision 1.3 1 /* $NetBSD: locale.c,v 1.3 2004/01/05 23:23:35 jmmv Exp $ */
2
3 /*-
4 * Copyright (c) 2002, 2003 Alexey Zelkin <phantom (at) FreeBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 * FreeBSD: src/usr.bin/locale/locale.c,v 1.10 2003/06/26 11:05:56 phantom Exp
29 */
30
31 #include <sys/cdefs.h>
32 #if defined(LIBC_SCCS) && !defined(lint)
33 __RCSID("$NetBSD: locale.c,v 1.3 2004/01/05 23:23:35 jmmv Exp $");
34 #endif /* LIBC_SCCS and not lint */
35
36 /*
37 * XXX: implement missing era_* (LC_TIME) keywords (require libc &
38 * nl_langinfo(3) extensions)
39 *
40 * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require
41 * localedef(1) implementation). Currently it's handled via
42 * nl_langinfo(CODESET).
43 */
44
45 #include <sys/types.h>
46 #include <dirent.h>
47 #include <err.h>
48 #include <locale.h>
49 #include <langinfo.h>
50 #include <paths.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <stringlist.h>
55 #include <unistd.h>
56
57 /* Local prototypes */
58 void init_locales_list(void);
59 void list_charmaps(void);
60 void list_locales(void);
61 const char *lookup_localecat(int);
62 char *kwval_lconv(int);
63 int kwval_lookup(char *, char **, int *, int *);
64 void showdetails(char *);
65 void showkeywordslist(void);
66 void showlocale(void);
67 void usage(void);
68
69 /* Global variables */
70 static StringList *locales = NULL;
71 extern char *_PathLocale;
72
73 int all_locales = 0;
74 int all_charmaps = 0;
75 int prt_categories = 0;
76 int prt_keywords = 0;
77 int more_params = 0;
78
79 struct _lcinfo {
80 const char *name;
81 int id;
82 } lcinfo [] = {
83 { "LC_CTYPE", LC_CTYPE },
84 { "LC_COLLATE", LC_COLLATE },
85 { "LC_TIME", LC_TIME },
86 { "LC_NUMERIC", LC_NUMERIC },
87 { "LC_MONETARY", LC_MONETARY },
88 { "LC_MESSAGES", LC_MESSAGES }
89 };
90 #define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0]))
91
92 /* ids for values not referenced by nl_langinfo() */
93 #define KW_ZERO 10000
94 #define KW_GROUPING (KW_ZERO+1)
95 #define KW_INT_CURR_SYMBOL (KW_ZERO+2)
96 #define KW_CURRENCY_SYMBOL (KW_ZERO+3)
97 #define KW_MON_DECIMAL_POINT (KW_ZERO+4)
98 #define KW_MON_THOUSANDS_SEP (KW_ZERO+5)
99 #define KW_MON_GROUPING (KW_ZERO+6)
100 #define KW_POSITIVE_SIGN (KW_ZERO+7)
101 #define KW_NEGATIVE_SIGN (KW_ZERO+8)
102 #define KW_INT_FRAC_DIGITS (KW_ZERO+9)
103 #define KW_FRAC_DIGITS (KW_ZERO+10)
104 #define KW_P_CS_PRECEDES (KW_ZERO+11)
105 #define KW_P_SEP_BY_SPACE (KW_ZERO+12)
106 #define KW_N_CS_PRECEDES (KW_ZERO+13)
107 #define KW_N_SEP_BY_SPACE (KW_ZERO+14)
108 #define KW_P_SIGN_POSN (KW_ZERO+15)
109 #define KW_N_SIGN_POSN (KW_ZERO+16)
110 #define KW_INT_P_CS_PRECEDES (KW_ZERO+17)
111 #define KW_INT_P_SEP_BY_SPACE (KW_ZERO+18)
112 #define KW_INT_N_CS_PRECEDES (KW_ZERO+19)
113 #define KW_INT_N_SEP_BY_SPACE (KW_ZERO+20)
114 #define KW_INT_P_SIGN_POSN (KW_ZERO+21)
115 #define KW_INT_N_SIGN_POSN (KW_ZERO+22)
116
117 struct _kwinfo {
118 const char *name;
119 int isstr; /* true - string, false - number */
120 int catid; /* LC_* */
121 int value_ref;
122 const char *comment;
123 } kwinfo [] = {
124 { "charmap", 1, LC_CTYPE, CODESET, "" }, /* hack */
125
126 { "decimal_point", 1, LC_NUMERIC, RADIXCHAR, "" },
127 { "thousands_sep", 1, LC_NUMERIC, THOUSEP, "" },
128 { "grouping", 1, LC_NUMERIC, KW_GROUPING, "" },
129 { "radixchar", 1, LC_NUMERIC, RADIXCHAR,
130 "Same as decimal_point (BSD only)" }, /* compat */
131 { "thousep", 1, LC_NUMERIC, THOUSEP,
132 "Same as thousands_sep (BSD only)" }, /* compat */
133
134 { "int_curr_symbol", 1, LC_MONETARY, KW_INT_CURR_SYMBOL, "" },
135 { "currency_symbol", 1, LC_MONETARY, KW_CURRENCY_SYMBOL, "" },
136 { "mon_decimal_point", 1, LC_MONETARY, KW_MON_DECIMAL_POINT, "" },
137 { "mon_thousands_sep", 1, LC_MONETARY, KW_MON_THOUSANDS_SEP, "" },
138 { "mon_grouping", 1, LC_MONETARY, KW_MON_GROUPING, "" },
139 { "positive_sign", 1, LC_MONETARY, KW_POSITIVE_SIGN, "" },
140 { "negative_sign", 1, LC_MONETARY, KW_NEGATIVE_SIGN, "" },
141
142 { "int_frac_digits", 0, LC_MONETARY, KW_INT_FRAC_DIGITS, "" },
143 { "frac_digits", 0, LC_MONETARY, KW_FRAC_DIGITS, "" },
144 { "p_cs_precedes", 0, LC_MONETARY, KW_P_CS_PRECEDES, "" },
145 { "p_sep_by_space", 0, LC_MONETARY, KW_P_SEP_BY_SPACE, "" },
146 { "n_cs_precedes", 0, LC_MONETARY, KW_N_CS_PRECEDES, "" },
147 { "n_sep_by_space", 0, LC_MONETARY, KW_N_SEP_BY_SPACE, "" },
148 { "p_sign_posn", 0, LC_MONETARY, KW_P_SIGN_POSN, "" },
149 { "n_sign_posn", 0, LC_MONETARY, KW_N_SIGN_POSN, "" },
150 { "int_p_cs_precedes", 0, LC_MONETARY, KW_INT_P_CS_PRECEDES, "" },
151 { "int_p_sep_by_space", 0, LC_MONETARY, KW_INT_P_SEP_BY_SPACE, "" },
152 { "int_n_cs_precedes", 0, LC_MONETARY, KW_INT_N_CS_PRECEDES, "" },
153 { "int_n_sep_by_space", 0, LC_MONETARY, KW_INT_N_SEP_BY_SPACE, "" },
154 { "int_p_sign_posn", 0, LC_MONETARY, KW_INT_P_SIGN_POSN, "" },
155 { "int_n_sign_posn", 0, LC_MONETARY, KW_INT_N_SIGN_POSN, "" },
156
157 { "d_t_fmt", 1, LC_TIME, D_T_FMT, "" },
158 { "d_fmt", 1, LC_TIME, D_FMT, "" },
159 { "t_fmt", 1, LC_TIME, T_FMT, "" },
160 { "am_str", 1, LC_TIME, AM_STR, "" },
161 { "pm_str", 1, LC_TIME, PM_STR, "" },
162 { "t_fmt_ampm", 1, LC_TIME, T_FMT_AMPM, "" },
163 { "day_1", 1, LC_TIME, DAY_1, "" },
164 { "day_2", 1, LC_TIME, DAY_2, "" },
165 { "day_3", 1, LC_TIME, DAY_3, "" },
166 { "day_4", 1, LC_TIME, DAY_4, "" },
167 { "day_5", 1, LC_TIME, DAY_5, "" },
168 { "day_6", 1, LC_TIME, DAY_6, "" },
169 { "day_7", 1, LC_TIME, DAY_7, "" },
170 { "abday_1", 1, LC_TIME, ABDAY_1, "" },
171 { "abday_2", 1, LC_TIME, ABDAY_2, "" },
172 { "abday_3", 1, LC_TIME, ABDAY_3, "" },
173 { "abday_4", 1, LC_TIME, ABDAY_4, "" },
174 { "abday_5", 1, LC_TIME, ABDAY_5, "" },
175 { "abday_6", 1, LC_TIME, ABDAY_6, "" },
176 { "abday_7", 1, LC_TIME, ABDAY_7, "" },
177 { "mon_1", 1, LC_TIME, MON_1, "" },
178 { "mon_2", 1, LC_TIME, MON_2, "" },
179 { "mon_3", 1, LC_TIME, MON_3, "" },
180 { "mon_4", 1, LC_TIME, MON_4, "" },
181 { "mon_5", 1, LC_TIME, MON_5, "" },
182 { "mon_6", 1, LC_TIME, MON_6, "" },
183 { "mon_7", 1, LC_TIME, MON_7, "" },
184 { "mon_8", 1, LC_TIME, MON_8, "" },
185 { "mon_9", 1, LC_TIME, MON_9, "" },
186 { "mon_10", 1, LC_TIME, MON_10, "" },
187 { "mon_11", 1, LC_TIME, MON_11, "" },
188 { "mon_12", 1, LC_TIME, MON_12, "" },
189 { "abmon_1", 1, LC_TIME, ABMON_1, "" },
190 { "abmon_2", 1, LC_TIME, ABMON_2, "" },
191 { "abmon_3", 1, LC_TIME, ABMON_3, "" },
192 { "abmon_4", 1, LC_TIME, ABMON_4, "" },
193 { "abmon_5", 1, LC_TIME, ABMON_5, "" },
194 { "abmon_6", 1, LC_TIME, ABMON_6, "" },
195 { "abmon_7", 1, LC_TIME, ABMON_7, "" },
196 { "abmon_8", 1, LC_TIME, ABMON_8, "" },
197 { "abmon_9", 1, LC_TIME, ABMON_9, "" },
198 { "abmon_10", 1, LC_TIME, ABMON_10, "" },
199 { "abmon_11", 1, LC_TIME, ABMON_11, "" },
200 { "abmon_12", 1, LC_TIME, ABMON_12, "" },
201 { "era", 1, LC_TIME, ERA, "(unavailable)" },
202 { "era_d_fmt", 1, LC_TIME, ERA_D_FMT, "(unavailable)" },
203 { "era_d_t_fmt", 1, LC_TIME, ERA_D_T_FMT, "(unavailable)" },
204 { "era_t_fmt", 1, LC_TIME, ERA_T_FMT, "(unavailable)" },
205 { "alt_digits", 1, LC_TIME, ALT_DIGITS, "" },
206
207 { "yesexpr", 1, LC_MESSAGES, YESEXPR, "" },
208 { "noexpr", 1, LC_MESSAGES, NOEXPR, "" },
209 { "yesstr", 1, LC_MESSAGES, YESSTR,
210 "(POSIX legacy)" }, /* compat */
211 { "nostr", 1, LC_MESSAGES, NOSTR,
212 "(POSIX legacy)" } /* compat */
213
214 };
215 #define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0]))
216
217 int
218 main(int argc, char *argv[])
219 {
220 int ch;
221 int tmp;
222
223 while ((ch = getopt(argc, argv, "ackm")) != -1) {
224 switch (ch) {
225 case 'a':
226 all_locales = 1;
227 break;
228 case 'c':
229 prt_categories = 1;
230 break;
231 case 'k':
232 prt_keywords = 1;
233 break;
234 case 'm':
235 all_charmaps = 1;
236 break;
237 default:
238 usage();
239 }
240 }
241 argc -= optind;
242 argv += optind;
243
244 /* validate arguments */
245 if (all_locales && all_charmaps)
246 usage();
247 if ((all_locales || all_charmaps) && argc > 0)
248 usage();
249 if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
250 usage();
251 if ((prt_categories || prt_keywords) && argc <= 0)
252 usage();
253
254 /* process '-a' */
255 if (all_locales) {
256 list_locales();
257 exit(0);
258 }
259
260 /* process '-m' */
261 if (all_charmaps) {
262 list_charmaps();
263 exit(0);
264 }
265
266 /* check for special case '-k list' */
267 tmp = 0;
268 if (prt_keywords && argc > 0)
269 while (tmp < argc)
270 if (strcasecmp(argv[tmp++], "list") == 0) {
271 showkeywordslist();
272 exit(0);
273 }
274
275 /* process '-c' and/or '-k' */
276 if (prt_categories || prt_keywords || argc > 0) {
277 setlocale(LC_ALL, "");
278 while (argc > 0) {
279 showdetails(*argv);
280 argv++;
281 argc--;
282 }
283 exit(0);
284 }
285
286 /* no arguments, show current locale state */
287 showlocale();
288
289 return (0);
290 }
291
292 void
293 usage(void)
294 {
295 printf("usage: locale [ -a | -m ]\n"
296 " locale [ -ck ] name ...\n");
297 exit(1);
298 }
299
300 /*
301 * Output information about all available locales
302 *
303 * XXX actually output of this function does not guarantee that locale
304 * is really available to application, since it can be broken or
305 * inconsistent thus setlocale() will fail. Maybe add '-V' function to
306 * also validate these locales?
307 */
308 void
309 list_locales(void)
310 {
311 size_t i;
312
313 init_locales_list();
314 for (i = 0; i < locales->sl_cur; i++) {
315 printf("%s\n", locales->sl_str[i]);
316 }
317 }
318
319 /*
320 * qsort() helper function
321 */
322 static int
323 scmp(const void *s1, const void *s2)
324 {
325 return strcmp(*(const char **)s1, *(const char **)s2);
326 }
327
328 /*
329 * Output information about all available charmaps
330 *
331 * XXX this function is doing a task in hackish way, i.e. by scaning
332 * list of locales, spliting their codeset part and building list of
333 * them.
334 */
335 void
336 list_charmaps(void)
337 {
338 size_t i;
339 char *s, *cs;
340 StringList *charmaps;
341
342 /* initialize StringList */
343 charmaps = sl_init();
344 if (charmaps == NULL)
345 err(1, "could not allocate memory");
346
347 /* fetch locales list */
348 init_locales_list();
349
350 /* split codesets and build their list */
351 for (i = 0; i < locales->sl_cur; i++) {
352 s = locales->sl_str[i];
353 if ((cs = strchr(s, '.')) != NULL) {
354 cs++;
355 if (sl_find(charmaps, cs) == NULL)
356 sl_add(charmaps, cs);
357 }
358 }
359
360 /* add US-ASCII, if not yet added */
361 if (sl_find(charmaps, "US-ASCII") == NULL)
362 sl_add(charmaps, "US-ASCII");
363
364 /* sort the list */
365 qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
366
367 /* print results */
368 for (i = 0; i < charmaps->sl_cur; i++) {
369 printf("%s\n", charmaps->sl_str[i]);
370 }
371 }
372
373 /*
374 * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
375 * environment variable is set)
376 */
377 void
378 init_locales_list(void)
379 {
380 DIR *dirp;
381 struct dirent *dp;
382
383 /* why call this function twice ? */
384 if (locales != NULL)
385 return;
386
387 /* initialize StringList */
388 locales = sl_init();
389 if (locales == NULL)
390 err(1, "could not allocate memory");
391
392 /* get actual locales directory name */
393 setlocale(LC_CTYPE, "C");
394 if (_PathLocale == NULL)
395 errx(1, "unable to find locales storage");
396
397 /* open locales directory */
398 dirp = opendir(_PathLocale);
399 if (dirp == NULL)
400 err(1, "could not open directory '%s'", _PathLocale);
401
402 /* scan directory and store its contents except "." and ".." */
403 while ((dp = readdir(dirp)) != NULL) {
404 if (*(dp->d_name) == '.')
405 continue; /* exclude "." and ".." */
406 sl_add(locales, strdup(dp->d_name));
407 }
408 closedir(dirp);
409
410 /* make sure that 'POSIX' and 'C' locales are present in the list.
411 * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
412 * we also list 'C' for constistency
413 */
414 if (sl_find(locales, "POSIX") == NULL)
415 sl_add(locales, "POSIX");
416
417 if (sl_find(locales, "C") == NULL)
418 sl_add(locales, "C");
419
420 /* make output nicer, sort the list */
421 qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
422 }
423
424 /*
425 * Show current locale status, depending on environment variables
426 */
427 void
428 showlocale(void)
429 {
430 size_t i;
431 const char *lang, *vval, *eval;
432
433 setlocale(LC_ALL, "");
434
435 lang = getenv("LANG");
436 if (lang == NULL) {
437 lang = "";
438 }
439 printf("LANG=%s\n", lang);
440 /* XXX: if LANG is null, then set it to "C" to get implied values? */
441
442 for (i = 0; i < NLCINFO; i++) {
443 vval = setlocale(lcinfo[i].id, NULL);
444 eval = getenv(lcinfo[i].name);
445 if (eval != NULL && !strcmp(eval, vval)
446 && strcmp(lang, vval)) {
447 /*
448 * Appropriate environment variable set, its value
449 * is valid and not overriden by LC_ALL
450 *
451 * XXX: possible side effect: if both LANG and
452 * overriden environment variable are set into same
453 * value, then it'll be assumed as 'implied'
454 */
455 printf("%s=%s\n", lcinfo[i].name, vval);
456 } else {
457 printf("%s=\"%s\"\n", lcinfo[i].name, vval);
458 }
459 }
460
461 vval = getenv("LC_ALL");
462 if (vval == NULL) {
463 vval = "";
464 }
465 printf("LC_ALL=%s\n", vval);
466 }
467
468 /*
469 * keyword value lookup helper (via localeconv())
470 */
471 char *
472 kwval_lconv(int id)
473 {
474 struct lconv *lc;
475 char *rval;
476
477 rval = NULL;
478 lc = localeconv();
479 switch (id) {
480 case KW_GROUPING:
481 rval = lc->grouping;
482 break;
483 case KW_INT_CURR_SYMBOL:
484 rval = lc->int_curr_symbol;
485 break;
486 case KW_CURRENCY_SYMBOL:
487 rval = lc->currency_symbol;
488 break;
489 case KW_MON_DECIMAL_POINT:
490 rval = lc->mon_decimal_point;
491 break;
492 case KW_MON_THOUSANDS_SEP:
493 rval = lc->mon_thousands_sep;
494 break;
495 case KW_MON_GROUPING:
496 rval = lc->mon_grouping;
497 break;
498 case KW_POSITIVE_SIGN:
499 rval = lc->positive_sign;
500 break;
501 case KW_NEGATIVE_SIGN:
502 rval = lc->negative_sign;
503 break;
504 case KW_INT_FRAC_DIGITS:
505 rval = &(lc->int_frac_digits);
506 break;
507 case KW_FRAC_DIGITS:
508 rval = &(lc->frac_digits);
509 break;
510 case KW_P_CS_PRECEDES:
511 rval = &(lc->p_cs_precedes);
512 break;
513 case KW_P_SEP_BY_SPACE:
514 rval = &(lc->p_sep_by_space);
515 break;
516 case KW_N_CS_PRECEDES:
517 rval = &(lc->n_cs_precedes);
518 break;
519 case KW_N_SEP_BY_SPACE:
520 rval = &(lc->n_sep_by_space);
521 break;
522 case KW_P_SIGN_POSN:
523 rval = &(lc->p_sign_posn);
524 break;
525 case KW_N_SIGN_POSN:
526 rval = &(lc->n_sign_posn);
527 break;
528 case KW_INT_P_CS_PRECEDES:
529 rval = &(lc->int_p_cs_precedes);
530 break;
531 case KW_INT_P_SEP_BY_SPACE:
532 rval = &(lc->int_p_sep_by_space);
533 break;
534 case KW_INT_N_CS_PRECEDES:
535 rval = &(lc->int_n_cs_precedes);
536 break;
537 case KW_INT_N_SEP_BY_SPACE:
538 rval = &(lc->int_n_sep_by_space);
539 break;
540 case KW_INT_P_SIGN_POSN:
541 rval = &(lc->int_p_sign_posn);
542 break;
543 case KW_INT_N_SIGN_POSN:
544 rval = &(lc->int_n_sign_posn);
545 break;
546 default:
547 break;
548 }
549 return (rval);
550 }
551
552 /*
553 * keyword value and properties lookup
554 */
555 int
556 kwval_lookup(char *kwname, char **kwval, int *cat, int *isstr)
557 {
558 int rval;
559 size_t i;
560
561 rval = 0;
562 for (i = 0; i < NKWINFO; i++) {
563 if (strcasecmp(kwname, kwinfo[i].name) == 0) {
564 rval = 1;
565 *cat = kwinfo[i].catid;
566 *isstr = kwinfo[i].isstr;
567 if (kwinfo[i].value_ref < KW_ZERO) {
568 *kwval = nl_langinfo(kwinfo[i].value_ref);
569 } else {
570 *kwval = kwval_lconv(kwinfo[i].value_ref);
571 }
572 break;
573 }
574 }
575
576 return (rval);
577 }
578
579 /*
580 * Show details about requested keyword according to '-k' and/or '-c'
581 * command line options specified.
582 */
583 void
584 showdetails(char *kw)
585 {
586 int isstr, cat, tmpval;
587 char *kwval;
588
589 if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
590 /*
591 * invalid keyword specified.
592 * XXX: any actions?
593 */
594 return;
595 }
596
597 if (prt_categories) {
598 printf("%s\n", lookup_localecat(cat));
599 }
600
601 if (prt_keywords) {
602 if (isstr) {
603 printf("%s=\"%s\"\n", kw, kwval);
604 } else {
605 tmpval = (char) *kwval;
606 printf("%s=%d\n", kw, tmpval);
607 }
608 }
609
610 if (!prt_categories && !prt_keywords) {
611 if (isstr) {
612 printf("%s\n", kwval);
613 } else {
614 tmpval = (char) *kwval;
615 printf("%d\n", tmpval);
616 }
617 }
618 }
619
620 /*
621 * Convert locale category id into string
622 */
623 const char *
624 lookup_localecat(int cat)
625 {
626 size_t i;
627
628 for (i = 0; i < NLCINFO; i++)
629 if (lcinfo[i].id == cat) {
630 return (lcinfo[i].name);
631 }
632 return ("UNKNOWN");
633 }
634
635 /*
636 * Show list of keywords
637 */
638 void
639 showkeywordslist(void)
640 {
641 size_t i;
642
643 #define FMT "%-20s %-12s %-7s %-20s\n"
644
645 printf("List of available keywords\n\n");
646 printf(FMT, "Keyword", "Category", "Type", "Comment");
647 printf("-------------------- ------------ ------- --------------------\n");
648 for (i = 0; i < NKWINFO; i++) {
649 printf(FMT,
650 kwinfo[i].name,
651 lookup_localecat(kwinfo[i].catid),
652 (kwinfo[i].isstr == 0) ? "number" : "string",
653 kwinfo[i].comment);
654 }
655 }
656