1#include <iostream>
2#include <sstream>
3#include <map>
4#include <set>
5#include <vector>
6#include <algorithm>
7
8#include <unistd.h>
9#include <langinfo.h>
10#include <locale.h>
11#include <xlocale.h>
12#include <sys/types.h>
13#include <dirent.h>
14
15#define LAST(array) array + (sizeof(array) / sizeof(*array))
16
17#define LC_SPECIAL (LC_COLLATE+LC_CTYPE+LC_MESSAGES+LC_MONETARY+LC_NUMERIC+LC_TIME)
18
19using namespace std;
20
21enum vtype {
22	V_STR, V_NUM
23};
24
25template<typename T> string tostr(T val) {
26	ostringstream ss;
27	ss << val;
28	return ss.str();
29}
30
31string quote(string s) {
32	if (s.length() == 0) {
33		return "";
34	}
35
36	return '"' + s + '"';
37}
38
39class keyword {
40  public:
41	virtual string get_category() const { return category; }
42	virtual string get_keyword() const { return kword; }
43	virtual string get_value(bool show_quotes) const {
44	  return (show_quotes && t == V_STR) ? quote(value) : value; }
45
46	virtual ~keyword() { }
47  protected:
48	keyword(int category_, string kword, string value, vtype t)
49	  : kword(kword), value(value), t(t) {
50		switch(category_) {
51			case LC_COLLATE:
52				category = "LC_COLLATE";
53				break;
54			case LC_CTYPE:
55				category = "LC_CTYPE";
56				break;
57			case LC_MESSAGES:
58				category = "LC_MESSAGES";
59				break;
60			case LC_MONETARY:
61				category = "LC_MONETARY";
62				break;
63			case LC_NUMERIC:
64				category = "LC_NUMERIC";
65				break;
66			case LC_TIME:
67				category = "LC_TIME";
68				break;
69			case LC_SPECIAL:
70				category = "LC_SPECIAL";
71				break;
72			default:
73				{
74					ostringstream lc;
75					lc << "LC_" << category_;
76					category = lc.str();
77				}
78				break;
79		}
80	}
81
82	string category, kword, value;
83	vtype t;
84};
85
86struct keyword_cmp {
87	bool operator()(const keyword *a, const keyword *b) const {
88		return a->get_category() < b->get_category();
89	}
90};
91
92class li_keyword : public keyword {
93  public:
94	li_keyword(int category, string kword, int itemnum, vtype t = V_STR)
95	  : keyword(category, kword, nl_langinfo(itemnum), t) { }
96};
97
98class lia_keyword : public keyword {
99  protected:
100	vector<string> values;
101  public:
102	virtual string get_value(bool show_quotes) const {
103		ostringstream ss;
104		vector<string>::const_iterator s(values.begin()), e(values.end()), i(s);
105
106		for(; i < e; ++i) {
107			if (i != s) {
108				ss << ';';
109			}
110			if (show_quotes && t == V_STR) {
111				ss << quote(*i);
112			} else {
113				ss << *i;
114			}
115		}
116
117		return ss.str();
118	}
119
120	lia_keyword(int category, string kword, int *s, int *e, vtype t = V_STR)
121	  : keyword(category, kword, "", t) {
122		for(; s < e; ++s) {
123			values.push_back(nl_langinfo(*s));
124		}
125	}
126};
127
128class lc_keyword : public keyword {
129  public:
130	lc_keyword(int category, string kword, string value, vtype t = V_STR)
131	  : keyword(category, kword, value, t) { }
132};
133
134void usage(char *argv0) {
135	clog << "usage: " << argv0 << "[-a|-m]\n   or: "
136	  << argv0 << " [-cCk] name..." << endl;
137}
138
139void list_all_valid_locales() {
140	string locale_dir("/usr/share/locale");
141	bool found_C = false, found_POSIX = false;
142	DIR *d = opendir(locale_dir.c_str());
143	struct dirent *de;
144	static string expected[] = { "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES",
145	  "LC_NUMERIC", "LC_TIME" };
146
147	for(de = readdir(d); de; de = readdir(d)) {
148		string lname(de->d_name, de->d_namlen);
149		string ldir(locale_dir + "/" + lname);
150		int cnt = 0;
151		DIR *ld = opendir(ldir.c_str());
152		if (ld) {
153			struct dirent *lde;
154			for(lde = readdir(ld); lde; lde = readdir(ld)) {
155				string fname(lde->d_name, lde->d_namlen);
156				if (LAST(expected) != find(expected, LAST(expected), fname)) {
157					cnt++;
158				}
159			}
160			closedir(ld);
161
162			if (cnt == LAST(expected) - expected) {
163				cout << lname << endl;
164				if (lname == "C") {
165					found_C = true;
166				}
167				if (lname == "POSIX") {
168					found_POSIX = true;
169				}
170			}
171		}
172	}
173	closedir(d);
174	if (!found_C) {
175		cout << "C" << endl;
176	}
177	if (!found_POSIX) {
178		cout << "POSIX" << endl;
179	}
180}
181
182void show_all_unique_codesets() {
183	string locale_dir("/usr/share/locale");
184	DIR *d = opendir(locale_dir.c_str());
185	struct dirent *de;
186	static string expected[] = { "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES",
187	  "LC_NUMERIC", "LC_TIME" };
188	set<string> codesets;
189	for(de = readdir(d); de; de = readdir(d)) {
190		string lname(de->d_name, de->d_namlen);
191		string ldir(locale_dir + "/" + lname);
192		int cnt = 0;
193		DIR *ld = opendir(ldir.c_str());
194		if (ld) {
195			struct dirent *lde;
196			for(lde = readdir(ld); lde; lde = readdir(ld)) {
197				string fname(lde->d_name, lde->d_namlen);
198				if (LAST(expected) != find(expected, LAST(expected), fname)) {
199					cnt++;
200				}
201			}
202			closedir(ld);
203
204			if (cnt == LAST(expected) - expected) {
205				locale_t xloc = newlocale(LC_ALL_MASK, lname.c_str(), NULL);
206				if (xloc) {
207					char *cs = nl_langinfo_l(CODESET, xloc);
208					if (cs && *cs && (codesets.find(cs) == codesets.end())) {
209						cout << cs << endl;
210						codesets.insert(cs);
211					}
212					freelocale(xloc);
213				}
214			}
215		}
216	}
217	closedir(d);
218}
219
220typedef map<string, keyword *> keywords_t;
221keywords_t keywords;
222
223typedef map<string, vector<keyword *> > catorgies_t;
224catorgies_t catoriges;
225
226void add_kw(keyword *k) {
227	keywords.insert(make_pair(k->get_keyword(), k));
228	catorgies_t::iterator c = catoriges.find(k->get_category());
229	if (c != catoriges.end()) {
230		c->second.push_back(k);
231	} else {
232		vector<keyword *> v;
233		v.push_back(k);
234		catoriges.insert(make_pair(k->get_category(), v));
235	}
236}
237
238string grouping(char *g) {
239	ostringstream ss;
240	if (*g == 0) {
241	    ss << "0";
242	} else {
243	    ss << static_cast<int>(*g);
244	    while(*++g) {
245		ss << ";" << static_cast<int>(*g);
246	    }
247	}
248	return ss.str();
249}
250
251void init_keywords() {
252	struct lconv *lc = localeconv();
253	if (lc) {
254		add_kw(new lc_keyword(LC_NUMERIC, "decimal_point", lc->decimal_point));
255		add_kw(new lc_keyword(LC_NUMERIC, "thousands_sep", lc->thousands_sep));
256		add_kw(new lc_keyword(LC_NUMERIC, "grouping", grouping(lc->grouping)));
257		add_kw(new lc_keyword(LC_MONETARY, "int_curr_symbol", lc->int_curr_symbol));
258		add_kw(new lc_keyword(LC_MONETARY, "currency_symbol", lc->currency_symbol));
259		add_kw(new lc_keyword(LC_MONETARY, "mon_decimal_point", lc->mon_decimal_point));
260		add_kw(new lc_keyword(LC_MONETARY, "mon_thousands_sep", lc->mon_thousands_sep));
261		add_kw(new lc_keyword(LC_MONETARY, "mon_grouping", grouping(lc->mon_grouping)));
262		add_kw(new lc_keyword(LC_MONETARY, "positive_sign", lc->positive_sign));
263		add_kw(new lc_keyword(LC_MONETARY, "negative_sign", lc->negative_sign));
264		add_kw(new lc_keyword(LC_MONETARY, "int_frac_digits", tostr((int)lc->int_frac_digits), V_NUM));
265		add_kw(new lc_keyword(LC_MONETARY, "frac_digits", tostr((int)lc->frac_digits), V_NUM));
266		add_kw(new lc_keyword(LC_MONETARY, "p_cs_precedes", tostr((int)lc->p_cs_precedes), V_NUM));
267		add_kw(new lc_keyword(LC_MONETARY, "p_sep_by_space", tostr((int)lc->p_sep_by_space), V_NUM));
268		add_kw(new lc_keyword(LC_MONETARY, "n_cs_precedes", tostr((int)lc->n_cs_precedes), V_NUM));
269		add_kw(new lc_keyword(LC_MONETARY, "n_sep_by_space", tostr((int)lc->n_sep_by_space), V_NUM));
270		add_kw(new lc_keyword(LC_MONETARY, "p_sign_posn", tostr((int)lc->p_sign_posn), V_NUM));
271		add_kw(new lc_keyword(LC_MONETARY, "n_sign_posn", tostr((int)lc->n_sign_posn), V_NUM));
272		add_kw(new lc_keyword(LC_MONETARY, "int_p_cs_precedes", tostr((int)lc->int_p_cs_precedes), V_NUM));
273		add_kw(new lc_keyword(LC_MONETARY, "int_n_cs_precedes", tostr((int)lc->int_n_cs_precedes), V_NUM));
274		add_kw(new lc_keyword(LC_MONETARY, "int_p_sep_by_space", tostr((int)lc->int_p_sep_by_space), V_NUM));
275		add_kw(new lc_keyword(LC_MONETARY, "int_n_sep_by_space", tostr((int)lc->int_n_sep_by_space), V_NUM));
276		add_kw(new lc_keyword(LC_MONETARY, "int_p_sign_posn", tostr((int)lc->int_p_sign_posn), V_NUM));
277		add_kw(new lc_keyword(LC_MONETARY, "int_n_sign_posn", tostr((int)lc->int_n_sign_posn), V_NUM));
278	}
279
280	int abdays[] = {ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7};
281	add_kw(new lia_keyword(LC_TIME, "ab_day", abdays, LAST(abdays)));
282	add_kw(new lia_keyword(LC_TIME, "abday", abdays, LAST(abdays)));
283
284	int days[] = {DAY_1, DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7};
285	add_kw(new lia_keyword(LC_TIME, "day", days, LAST(days)));
286
287	int abmons[] = {ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7, ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12};
288	add_kw(new lia_keyword(LC_TIME, "abmon", abmons, LAST(abmons)));
289
290	int mons[] = {MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, MON_7, MON_8, MON_9, MON_10, MON_11, MON_12};
291	add_kw(new lia_keyword(LC_TIME, "mon", mons, LAST(mons)));
292
293	int am_pms[] = {AM_STR, PM_STR};
294	add_kw(new lia_keyword(LC_TIME, "am_pm", am_pms, LAST(am_pms)));
295
296	add_kw(new li_keyword(LC_TIME, "t_fmt_ampm", T_FMT_AMPM));
297	add_kw(new li_keyword(LC_TIME, "era", ERA));
298	add_kw(new li_keyword(LC_TIME, "era_d_fmt", ERA_D_FMT));
299	add_kw(new li_keyword(LC_TIME, "era_t_fmt", ERA_T_FMT));
300	add_kw(new li_keyword(LC_TIME, "era_d_t_fmt", ERA_D_T_FMT));
301	add_kw(new li_keyword(LC_TIME, "alt_digits", ALT_DIGITS));
302
303	add_kw(new li_keyword(LC_TIME, "d_t_fmt", D_T_FMT));
304	add_kw(new li_keyword(LC_TIME, "d_fmt", D_FMT));
305	add_kw(new li_keyword(LC_TIME, "t_fmt", T_FMT));
306
307	add_kw(new li_keyword(LC_MESSAGES, "yesexpr", YESEXPR));
308	add_kw(new li_keyword(LC_MESSAGES, "noexpr", NOEXPR));
309	add_kw(new li_keyword(LC_MESSAGES, "yesstr", YESSTR));
310	add_kw(new li_keyword(LC_MESSAGES, "nostr", NOSTR));
311
312	add_kw(new li_keyword(LC_CTYPE, "charmap", CODESET));
313	add_kw(new lc_keyword(LC_SPECIAL, "categories", "LC_COLLATE LC_CTYPE LC_MESSAGES LC_MONETARY LC_NUMERIC LC_TIME"));
314
315	// add_kw: CRNCYSTR D_MD_ORDER CODESET RADIXCHAR THOUSEP
316}
317
318void show_keyword(string &last_cat, bool sw_categories, bool sw_keywords,
319  keyword *k) {
320	if (sw_categories && last_cat != k->get_category()) {
321		last_cat = k->get_category();
322		cout << last_cat << endl;
323	}
324	if (sw_keywords) {
325		cout << k->get_keyword() << "=";
326	}
327	cout << k->get_value(sw_keywords) << endl;
328}
329
330int main(int argc, char *argv[]) {
331	int sw;
332	bool sw_all_locales = false, sw_categories = false, sw_keywords = false,
333	  sw_charmaps = false;
334
335	while(-1 != (sw = getopt(argc, argv, "ackm"))) {
336		switch(sw) {
337			case 'a':
338				sw_all_locales = true;
339				break;
340			case 'c':
341				sw_categories = true;
342				break;
343			case 'k':
344				sw_keywords = true;
345				break;
346			case 'm':
347				sw_charmaps = true;
348				break;
349			default:
350				usage(argv[0]);
351				exit(1);
352		}
353	}
354
355	if ((sw_all_locales && sw_charmaps)
356	  || ((sw_all_locales || sw_charmaps) && (sw_keywords || sw_categories))
357	  ) {
358		usage(argv[0]);
359		exit(1);
360	}
361
362	setlocale(LC_ALL, "");
363
364	if (!(sw_all_locales || sw_categories || sw_keywords || sw_charmaps)
365	  && argc == optind) {
366		char *lang = getenv("LANG");
367		cout << "LANG=" << quote(lang ? lang : "") << endl;
368		cout << "LC_COLLATE=" << quote(setlocale(LC_COLLATE, NULL)) << endl;
369		cout << "LC_CTYPE=" << quote(setlocale(LC_CTYPE, NULL)) << endl;
370		cout << "LC_MESSAGES=" << quote(setlocale(LC_MESSAGES, NULL)) << endl;
371		cout << "LC_MONETARY=" << quote(setlocale(LC_MONETARY, NULL)) << endl;
372		cout << "LC_NUMERIC=" << quote(setlocale(LC_NUMERIC, NULL)) << endl;
373		cout << "LC_TIME=" << quote(setlocale(LC_TIME, NULL)) << endl;
374		if (getenv("LC_ALL")) {
375		    cout << "LC_ALL=" << quote(setlocale(LC_ALL, NULL)) << endl;
376		} else {
377		    cout << "LC_ALL=" << endl;
378		}
379
380		return 0;
381	}
382
383	if (sw_all_locales) {
384		list_all_valid_locales();
385		return 0;
386	}
387
388	if (sw_charmaps) {
389	        show_all_unique_codesets();
390		return 0;
391	}
392
393	init_keywords();
394	string last_cat("");
395	int exit_val = 0;
396	for(int i = optind; i < argc; ++i) {
397		keywords_t::iterator ki = keywords.find(argv[i]);
398		if (ki != keywords.end()) {
399			show_keyword(last_cat, sw_categories, sw_keywords, ki->second);
400		} else {
401			catorgies_t::iterator ci = catoriges.find(argv[i]);
402			if (ci != catoriges.end()) {
403				vector<keyword *>::iterator vi(ci->second.begin()),
404				  ve(ci->second.end());
405				for(; vi != ve; ++vi) {
406					show_keyword(last_cat, sw_categories, sw_keywords, *vi);
407				}
408			} else if (argv[i] == string("LC_ALL")) {
409			    ki = keywords.begin();
410			    keywords_t::iterator ke = keywords.end();
411			    for(; ki != ke; ++ki) {
412				show_keyword(last_cat, sw_categories, sw_keywords, ki->second);
413			    }
414			} else {
415				if (argv[i] == string("LC_CTYPE")
416				  || argv[i] == string("LC_COLLATE")) {
417				    // It would be nice to print a warning,
418				    // but we aren't allowed (locale.ex test#14)
419				    if (sw_categories) {
420					cout << argv[i] << endl;
421				    }
422				} else {
423				    clog << "unknown keyword "
424				      << argv[i] << endl;
425				    exit_val = 1;
426				}
427			}
428		}
429	}
430
431	return exit_val;
432}
433