From 18514afb9bd40c62bcd04c10579291e9b930284b Mon Sep 17 00:00:00 2001 From: phantom Date: Fri, 20 Jun 2003 11:45:43 +0000 Subject: Complete rewrite of locale(1) in order to become POSIX complaint utilitty. It's possibly not completely complaint with POSIX requirements, but very close to it now. --- usr.bin/locale/locale.c | 484 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 400 insertions(+), 84 deletions(-) (limited to 'usr.bin') diff --git a/usr.bin/locale/locale.c b/usr.bin/locale/locale.c index f7d82aa..1af8d19 100644 --- a/usr.bin/locale/locale.c +++ b/usr.bin/locale/locale.c @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2002 Alexey Zelkin + * Copyright (c) 2002, 2003 Alexey Zelkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,9 +26,24 @@ * $FreeBSD$ */ +/* + * XXX: implement missing int_* (LC_MONETARY) and era_* (LC_CTYIME) keywords + * (require libc modification) + * + * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require + * localedef(1) implementation). Currently it's handled via + * nl_langinfo(CODESET). + * + * XXX: implement '-k list' to show all available keywords. Add descriptions + * for all of keywords (and mention FreeBSD only there) + * + */ + #include #include +#include #include +#include #include /* for _PATH_LOCALE */ #include #include @@ -38,177 +53,478 @@ /* Local prototypes */ void usage(void); -void get_locales_list(void); +void init_locales_list(void); void list_locales(void); -void display_locale_status(int); +void showdetails(char *); +void showlocale(void); /* Global variables */ static StringList *locales = NULL; int all_locales = 0; -int verbose = 0; -int env_used = 0; +int all_charmaps = 0; +int prt_categories = 0; +int prt_keywords = 0; +int more_params = 0; struct _lcinfo { char *name; int id; } lcinfo [] = { - "LANG", LC_ALL, - "LC_ALL", LC_ALL, - "LC_CTYPE", LC_CTYPE, - "LC_COLLATE", LC_COLLATE, - "LC_TIME", LC_TIME, - "LC_NUMERIC", LC_NUMERIC, - "LC_MONETARY", LC_MONETARY, - "LC_MESSAGES", LC_MESSAGES + { "LC_CTYPE", LC_CTYPE }, + { "LC_COLLATE", LC_COLLATE }, + { "LC_TIME", LC_TIME }, + { "LC_NUMERIC", LC_NUMERIC }, + { "LC_MONETARY", LC_MONETARY }, + { "LC_MESSAGES", LC_MESSAGES } }; #define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0])) +/* ids for values not referenced by nl_langinfo() */ +#define KW_ZERO 10000 +#define KW_GROUPING (KW_ZERO+1) +#define KW_INT_CURR_SYMBOL (KW_ZERO+2) +#define KW_CURRENCY_SYMBOL (KW_ZERO+3) +#define KW_MON_DECIMAL_POINT (KW_ZERO+4) +#define KW_MON_THOUSANDS_SEP (KW_ZERO+5) +#define KW_MON_GROUPING (KW_ZERO+6) +#define KW_POSITIVE_SIGN (KW_ZERO+7) +#define KW_NEGATIVE_SIGN (KW_ZERO+8) +#define KW_INT_FRAC_DIGITS (KW_ZERO+9) +#define KW_FRAC_DIGITS (KW_ZERO+10) +#define KW_P_CS_PRECEDES (KW_ZERO+11) +#define KW_P_SEP_BY_SPACE (KW_ZERO+12) +#define KW_N_CS_PRECEDES (KW_ZERO+13) +#define KW_N_SEP_BY_SPACE (KW_ZERO+14) +#define KW_P_SIGN_POSN (KW_ZERO+15) +#define KW_N_SIGN_POSN (KW_ZERO+16) + +struct _kwinfo { + char *name; + int isstr; /* true - string, false - number */ + int catid; /* LC_* */ + int value_ref; +} kwinfo [] = { + { "charmap", 1, LC_CTYPE, CODESET }, /* hack */ + + { "decimal_point", 1, LC_NUMERIC, RADIXCHAR }, + { "thousands_sep", 1, LC_NUMERIC, THOUSEP }, + { "grouping", 1, LC_NUMERIC, KW_GROUPING }, + { "radixchar", 1, LC_NUMERIC, RADIXCHAR }, /* compat */ + { "thousep", 1, LC_NUMERIC, THOUSEP}, /* compat */ + + { "currency_symbol", 1, LC_MONETARY, KW_CURRENCY_SYMBOL }, /*compat*/ + { "int_curr_symbol", 1, LC_MONETARY, KW_INT_CURR_SYMBOL }, + { "currency_symbol", 1, LC_MONETARY, KW_CURRENCY_SYMBOL }, + { "mon_decimal_point", 1, LC_MONETARY, KW_MON_DECIMAL_POINT }, + { "mon_thousands_sep", 1, LC_MONETARY, KW_MON_THOUSANDS_SEP }, + { "mon_grouping", 1, LC_MONETARY, KW_MON_GROUPING }, + { "positive_sign", 1, LC_MONETARY, KW_POSITIVE_SIGN }, + { "negative_sign", 1, LC_MONETARY, KW_NEGATIVE_SIGN }, + + { "int_frac_digits", 0, LC_MONETARY, KW_INT_FRAC_DIGITS }, + { "frac_digits", 0, LC_MONETARY, KW_FRAC_DIGITS }, + { "p_cs_precedes", 0, LC_MONETARY, KW_P_CS_PRECEDES }, + { "p_sep_by_space", 0, LC_MONETARY, KW_P_SEP_BY_SPACE }, + { "n_cs_precedes", 0, LC_MONETARY, KW_N_CS_PRECEDES }, + { "n_sep_by_space", 0, LC_MONETARY, KW_N_SEP_BY_SPACE }, + { "p_sign_posn", 0, LC_MONETARY, KW_P_SIGN_POSN }, + { "n_sign_posn", 0, LC_MONETARY, KW_N_SIGN_POSN }, + + { "d_t_fmt", 1, LC_TIME, D_T_FMT }, + { "d_fmt", 1, LC_TIME, D_FMT }, + { "t_fmt", 1, LC_TIME, T_FMT }, + { "am_str", 1, LC_TIME, AM_STR }, + { "pm_str", 1, LC_TIME, PM_STR }, + { "t_fmt_ampm", 1, LC_TIME, T_FMT_AMPM }, + { "day_1", 1, LC_TIME, DAY_1 }, + { "day_2", 1, LC_TIME, DAY_2 }, + { "day_3", 1, LC_TIME, DAY_3 }, + { "day_4", 1, LC_TIME, DAY_4 }, + { "day_5", 1, LC_TIME, DAY_5 }, + { "day_6", 1, LC_TIME, DAY_6 }, + { "day_7", 1, LC_TIME, DAY_7 }, + { "abday_1", 1, LC_TIME, ABDAY_1 }, + { "abday_2", 1, LC_TIME, ABDAY_2 }, + { "abday_3", 1, LC_TIME, ABDAY_3 }, + { "abday_4", 1, LC_TIME, ABDAY_4 }, + { "abday_5", 1, LC_TIME, ABDAY_5 }, + { "abday_6", 1, LC_TIME, ABDAY_6 }, + { "abday_7", 1, LC_TIME, ABDAY_7 }, + { "mon_1", 1, LC_TIME, MON_1 }, + { "mon_2", 1, LC_TIME, MON_2 }, + { "mon_3", 1, LC_TIME, MON_3 }, + { "mon_4", 1, LC_TIME, MON_4 }, + { "mon_5", 1, LC_TIME, MON_5 }, + { "mon_6", 1, LC_TIME, MON_6 }, + { "mon_7", 1, LC_TIME, MON_7 }, + { "mon_8", 1, LC_TIME, MON_8 }, + { "mon_9", 1, LC_TIME, MON_9 }, + { "mon_10", 1, LC_TIME, MON_10 }, + { "mon_11", 1, LC_TIME, MON_11 }, + { "mon_12", 1, LC_TIME, MON_12 }, + { "abmon_1", 1, LC_TIME, ABMON_1 }, + { "abmon_2", 1, LC_TIME, ABMON_2 }, + { "abmon_3", 1, LC_TIME, ABMON_3 }, + { "abmon_4", 1, LC_TIME, ABMON_4 }, + { "abmon_5", 1, LC_TIME, ABMON_5 }, + { "abmon_6", 1, LC_TIME, ABMON_6 }, + { "abmon_7", 1, LC_TIME, ABMON_7 }, + { "abmon_8", 1, LC_TIME, ABMON_8 }, + { "abmon_9", 1, LC_TIME, ABMON_9 }, + { "abmon_10", 1, LC_TIME, ABMON_10 }, + { "abmon_11", 1, LC_TIME, ABMON_11 }, + { "abmon_12", 1, LC_TIME, ABMON_12 }, + { "era", 1, LC_TIME, ERA }, + { "era_d_fmt", 1, LC_TIME, ERA_D_FMT }, + { "era_d_t_fmt", 1, LC_TIME, ERA_D_T_FMT }, + { "era_t_fmt", 1, LC_TIME, ERA_T_FMT }, + { "alt_digits", 1, LC_TIME, ALT_DIGITS }, + { "d_md_order", 1, LC_TIME, D_MD_ORDER }, /* local */ + + { "yesexpr", 1, LC_MESSAGES, YESEXPR }, + { "noexpr", 1, LC_MESSAGES, NOEXPR }, + { "yesstr", 1, LC_MESSAGES, YESSTR }, /* local */ + { "nostr", 1, LC_MESSAGES, NOSTR } /* local */ + +}; +#define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0])) + int main(int argc, char *argv[]) { char ch; - while ((ch = getopt(argc, argv, "av")) != -1) + while ((ch = getopt(argc, argv, "ackm")) != -1) { switch (ch) { case 'a': all_locales = 1; break; - case 'v': - verbose = 1; + case 'c': + prt_categories = 1; + break; + case 'k': + prt_keywords = 1; + break; + case 'm': + all_charmaps = 1; break; default: usage(); } + } argc -= optind; argv += optind; + /* validate arguments */ + if (all_locales && all_charmaps) + usage(); + if ((all_locales || all_charmaps) && argc > 0) + usage(); + if ((all_locales || all_charmaps) && (prt_categories || prt_keywords)) + usage(); + if ((prt_categories || prt_keywords) && argc <= 0) + usage(); + + /* process '-a' */ if (all_locales) { list_locales(); exit(0); } - display_locale_status(verbose); + /* process '-m' */ + if (all_charmaps) { + /* + * XXX: charmaps are not supported by FreeBSD now. It + * need to be implemented as soon as localedef(1) implemented. + */ + exit(1); + } + + /* process '-c' and/or '-k' */ + if (prt_categories || prt_keywords || argc > 0) { + setlocale(LC_ALL, ""); + while (argc > 0) { + showdetails(*argv); + argv++; + argc--; + } + exit(0); + } + + /* no arguments, show current locale state */ + showlocale(); + return (0); } void usage(void) { - printf("\nUsage: locale [ -a ] [ -v ]\n"); + printf("Usage: locale [ -a | -m ]\n" + " locale [ -ck ] name ...\n"); exit(1); } +/* + * Output information about all available locales + * + * XXX actually output of this function does not guarantee that locale + * is really available to application, since it can be broken or + * inconsistent thus setlocale() will fail. Maybe add '-V' function to + * also validate these locales? + */ void list_locales(void) { int i; - get_locales_list(); + init_locales_list(); for (i = 0; i < locales->sl_cur; i++) { printf("%s\n", locales->sl_str[i]); } } + +/* + * qsort() helper function + */ +static int +scmp(const void *s1, const void *s2) +{ + return strcmp(*(const char **)s1, *(const char **)s2); +} + + +/* + * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE + * environment variable is set) + */ void -probe_var(char *varname, int id) +init_locales_list(void) { - char *value, *res1, *res2; + DIR *dirp; + struct dirent *dp; + char *dirname; - value = getenv(varname); - if (value == NULL) { - printf("%s = (null)\n", varname); + /* why call this function twice ? */ + if (locales != NULL) return; + + /* initialize StringList */ + locales = sl_init(); + if (locales == NULL) + err(1, "could not allocate memory"); + + /* get actual locales directory name */ + dirname = getenv("PATH_LOCALE"); + if (dirname == NULL) + dirname = _PATH_LOCALE; + + /* open locales directory */ + dirp = opendir(dirname); + if (dirp == NULL) + err(1, "could not open directory '%s'", dirname); + + /* scan directory and store its contents except "." and ".." */ + while ((dp = readdir(dirp)) != NULL) { + if (*(dp->d_name) == '.') + continue; /* exclude "." and ".." */ + sl_add(locales, strdup(dp->d_name)); } + closedir(dirp); - if (sl_find(locales, value) == NULL) - res1 = "invalid"; - else - res1 = "valid"; + /* make sure that 'POSIX' and 'C' locales are present in the list. + * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but + * we also list 'C' for constistency + */ + if (sl_find(locales, "POSIX") == NULL) + sl_add(locales, "POSIX"); - if (strcmp(value, setlocale(id, value)) != 0) - res2 = "invalid"; - else - res2 = "valid"; + if (sl_find(locales, "C") == NULL) + sl_add(locales, "C"); - printf("%s = \"%s\"\t\t(name is %s, content is %s)\n", - varname, value, res1, res2); + /* make output nicer, sort the list */ + qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp); } +/* + * Show current locale status, depending on environment variables + */ void -display_locale_status(int verbose) +showlocale(void) { - int i; + int i; + char *lang, *vval, *eval; setlocale(LC_ALL, ""); - if (verbose) { - get_locales_list(); - if (env_used) - printf("WARNING: PATH_LOCALE environment variable is set!"); + lang = getenv("LANG"); + if (lang == NULL || *lang == '\0') { + lang = ""; } + printf("LANG=%s\n", lang); - printf("Current status of locale settings:\n\n"); - for (i = 1; i < NLCINFO; i++) { /* start with 1 to avoid 'LANG' */ - printf("%s = \"%s\"\n", - lcinfo[i].name, - setlocale(lcinfo[i].id, "")); + for (i = 0; i < NLCINFO; i++) { + vval = setlocale(lcinfo[i].id, NULL); + eval = getenv(lcinfo[i].name); + if (eval != NULL && !strcmp(eval, vval) + && strcmp(lang, vval)) { + /* + * Appropriate environment variable set, its value + * is valid and not overriden by LC_ALL + * + * XXX: possible side effect: if both LANG and + * overriden environment variable are set into same + * value, then it'll be assumed as 'implied' + */ + printf("%s=%s\n", lcinfo[i].name, vval); + } else { + printf("%s=\"%s\"\n", lcinfo[i].name, vval); + } } - /* - * If verbose flag is set, add environment information - */ - if (verbose) { - printf("\nCurrent environment variables (and status):\n\n"); - for (i = 0; i < NLCINFO; i++) - probe_var(lcinfo[i].name, lcinfo[i].id); + vval = getenv("LC_ALL"); + if (vval == NULL || *vval == '\0') { + vval = ""; } + printf("LC_ALL=%s\n", vval); } -static char * -get_locale_path(void) +/* + * keyword value lookup helper (via localeconv()) + */ +char * +kwval_lconv(int id) { - char *localedir; - - localedir = getenv("PATH_LOCALE"); - if (localedir == NULL) - localedir = _PATH_LOCALE; - else - env_used = 1; - return (localedir); + struct lconv *lc = localeconv(); + char *rval = NULL; + + switch (id) { + case KW_GROUPING: + rval = lc->grouping; + break; + case KW_INT_CURR_SYMBOL: + rval = lc->int_curr_symbol; + break; + case KW_CURRENCY_SYMBOL: + rval = lc->currency_symbol; + break; + case KW_MON_DECIMAL_POINT: + rval = lc->mon_decimal_point; + break; + case KW_MON_THOUSANDS_SEP: + rval = lc->mon_thousands_sep; + break; + case KW_MON_GROUPING: + rval = lc->mon_grouping; + break; + case KW_POSITIVE_SIGN: + rval = lc->positive_sign; + break; + case KW_NEGATIVE_SIGN: + rval = lc->negative_sign; + break; + case KW_INT_FRAC_DIGITS: + rval = &(lc->int_frac_digits); + break; + case KW_FRAC_DIGITS: + rval = &(lc->frac_digits); + break; + case KW_P_CS_PRECEDES: + rval = &(lc->p_cs_precedes); + break; + case KW_P_SEP_BY_SPACE: + rval = &(lc->p_sep_by_space); + break; + case KW_N_CS_PRECEDES: + rval = &(lc->n_cs_precedes); + break; + case KW_N_SEP_BY_SPACE: + rval = &(lc->n_sep_by_space); + break; + case KW_P_SIGN_POSN: + rval = &(lc->p_sign_posn); + break; + case KW_N_SIGN_POSN: + rval = &(lc->n_sign_posn); + break; + default: + break; + } + return (rval); } -static int -scmp(const void *s1, const void *s2) +/* + * keyword value and properties lookup + */ +int +kwval_lookup(char *kwname, char **kwval, int *cat, int *isstr) { - return strcmp(*(const char **)s1, *(const char **)s2); + int rval = 0; + int i; + + for (i = 0; i < NKWINFO; i++) { + if (strcasecmp(kwname, kwinfo[i].name) == 0) { + rval = 1; + *cat = kwinfo[i].catid; + *isstr = kwinfo[i].isstr; + if (kwinfo[i].value_ref < KW_ZERO) { + *kwval = nl_langinfo(kwinfo[i].value_ref); + } else { + *kwval = kwval_lconv(kwinfo[i].value_ref); + } + break; + } + } + + return (rval); } +/* + * Show details about requested keyword according to '-k' and/or '-c' + * command line options specified. + */ void -get_locales_list() +showdetails(char *kw) { - DIR *dirp; - struct dirent *dp; - char *dirname; + char *kwval, *tmps; + int isstr, cat, tmp; - if (locales != NULL) + if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) { + /* + * invalid keyword specified. + * XXX: any actions? + */ return; + } - locales = sl_init(); - if (locales == NULL) - err(1, "could not allocate memory"); - - dirname = get_locale_path(); - dirp = opendir(dirname); - if (dirp == NULL) - err(1, "could not open directory '%s'", dirname); + if (prt_categories) { + tmps = NULL; + for (tmp = 0; tmp < NLCINFO; tmp++) { + if (lcinfo[tmp].id == cat) + tmps = lcinfo[tmp].name; + } + if (tmps == NULL) + tmps = "UNKNOWN"; + printf("%s\n", tmps); + } - while ((dp = readdir(dirp)) != NULL) { - if (*dp->d_name == '.') - continue; - sl_add(locales, strdup(dp->d_name)); + if (prt_keywords) { + if (isstr) { + printf("%s=\"%s\"\n", kw, kwval); + } else { + tmp = (char) *kwval; + printf("%s=%d\n", kw, tmp); + } } - closedir(dirp); - qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp); + if (!prt_categories && !prt_keywords) { + if (isstr) { + printf("%s\n", kwval); + } else { + tmp = (char) *kwval; + printf("%d\n", tmp); + } + } } -- cgit v1.1