xref: /haiku/src/bin/locale/collectcatkeys.cpp (revision 04171cfc5c10c98b9ba3c7233a271f6165cdd36f)
1 /*
2  * Copyright 2003-2010, Haiku, Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Oliver Tappe, zooey@hirschkaefer.de
7  *		Adrien Destugues, pulkomandy@gmail.com
8  */
9 
10 
11 #include <cctype>
12 #include <cerrno>
13 #include <cstdio>
14 #include <cstdlib>
15 
16 #include <Entry.h>
17 #include <File.h>
18 #include "RegExp.h"
19 #include <StorageDefs.h>
20 #include <String.h>
21 
22 #include <EditableCatalog.h>
23 
24 using BPrivate::EditableCatalog;
25 
26 
27 bool showKeys = false;
28 bool showSummary = false;
29 bool showWarnings = false;
30 const char *inputFile = NULL;
31 BString outputFile;
32 const char *catalogSig = NULL;
33 const char *catalogLang = "English";
34 BString rxString("B_CATKEY\\s*");
35 
36 BString str, ctx, cmt;
37 bool haveID;
38 int32 id;
39 
40 
41 EditableCatalog *catalog = NULL;
42 
43 
44 void
45 usage()
46 {
47 	fputs("usage: collectcatkeys [-pvw] [-r <regex>] [-o <outfile>] "
48 		"[-l <catalogLanguage>]\n"
49 		"                      -s <catalogSig> <prepCppFile>\n"
50 		"options:\n"
51 		"  -l <catalogLang>\tlanguage of the target-catalog (default is "
52 		"English)\n"
53 		"  -o <outfile>\t\texplicitly specifies the name of the output-file\n"
54 		"  -p\t\t\tprint keys as they are found\n"
55 		"  -r <regex>\t\tchanges the regex used by the key-scanner to the one "
56 		"given,\n"
57 		"      \t\t\tthe default is:   ", stderr);
58 	fputs(rxString.String(), stderr);
59 	fputs("\n  -s <catalogSig>\tsignature of the target-catalog\n"
60 		"  -v\t\t\tbe verbose, show summary\n"
61 		"  -w\t\t\tshow warnings about catalog-accesses that couldn't be "
62 		" resolved completely\n", stderr);
63 	exit(-1);
64 }
65 
66 
67 bool
68 fetchStr(const char *&in, BString &str, bool lookForID)
69 {
70 	int parLevel = 0;
71 
72 	while (isspace(*in) || *in == '(') {
73 		if (*in == '(')
74 			parLevel++;
75 		in++;
76 	}
77 
78 	if (*in == '"') {
79 		bool inString = true;
80 		bool quoted = false;
81 		in++;
82 		while (parLevel >= 0 && inString)
83 		{
84 			// Collect string content until we find a quote marking end of
85 			// string (skip escaped quotes)
86 			while (*in != '"' || quoted)
87 			{
88 				str.Append(in,1);
89 				if (*in == '\\' && !quoted)
90 					quoted = true;
91 				else
92 					quoted = false;
93 				in++;
94 			}
95 			in++;
96 
97 			inString = false;
98 
99 			// Strip all whitespace until we find a closing parenthesis, or the
100 			// beginning of another string
101 			while (isspace(*in) || *in == ')') {
102 				if (*in == ')') {
103 					if (parLevel == 0)
104 						return true;
105 					parLevel--;
106 				}
107 
108 				in++;
109 			}
110 
111 			if (*in == '"') {
112 				inString = true;
113 				in++;
114 			}
115 		}
116 	} else {
117 		if (!memcmp(in, "__null", 6)) {
118 			// NULL is preprocessed into __null, which we parse as ""
119 			in += 6;
120 		} else if (lookForID && (isdigit(*in) || *in == '-' || *in == '+')) {
121 			// try to parse an ID (a long):
122 			errno = 0;
123 			char *next;
124 			id = strtol(in, &next, 10);
125 			if (id != 0 || errno == 0) {
126 				haveID = true;
127 				in = next;
128 			}
129 		} else
130 			return false;
131 
132 		while (isspace(*in) || *in == ')') {
133 			if (*in == ')') {
134 				if (!parLevel)
135 					return true;
136 				parLevel--;
137 			}
138 			in++;
139 		}
140 	}
141 	return true;
142 }
143 
144 
145 bool
146 fetchKey(const char *&in)
147 {
148 	str = ctx = cmt = "";
149 	haveID = false;
150 	// fetch native string or id:
151 	if (!fetchStr(in, str, true))
152 		return false;
153 	if (*in == ',') {
154 		in++;
155 		// fetch context:
156 		if (!fetchStr(in, ctx, false))
157 			return false;
158 		if (*in == ',') {
159 			in++;
160 			// fetch comment:
161 			if (!fetchStr(in, cmt, false))
162 				return false;
163 		}
164 	}
165 	return true;
166 }
167 
168 
169 void
170 collectAllCatalogKeys(BString& inputStr)
171 {
172 	RegExp rx;
173 	struct regexp *rxprg = rx.Compile(rxString.String());
174 	if (rx.InitCheck() != B_OK) {
175 		fprintf(stderr, "regex-compilation error %s\n", rx.ErrorString());
176 		return;
177 	}
178 	status_t res;
179 	const char *in = inputStr.String();
180 	while (rx.RunMatcher(rxprg, in)) {
181 		const char *start = rxprg->startp[0];
182 		in = rxprg->endp[0];
183 		if (fetchKey(in)) {
184 			if (haveID) {
185 				if (showKeys)
186 					printf("CatKey(%" B_PRId32 ")\n", id);
187 				res = catalog->SetString(id, "");
188 				if (res != B_OK) {
189 					fprintf(stderr, "couldn't add key %" B_PRId32 " - error: "
190 						"%s\n", id, strerror(res));
191 					exit(-1);
192 				}
193 			} else {
194 				if (showKeys) {
195 					printf("CatKey(\"%s\", \"%s\", \"%s\")\n", str.String(),
196 						ctx.String(), cmt.String());
197 				}
198 				res = catalog->SetString(str.String(), str.String(),
199 					ctx.String(), cmt.String());
200 				if (res != B_OK) {
201 					fprintf(stderr, "couldn't add key %s,%s,%s - error: %s\n",
202 						str.String(), ctx.String(), cmt.String(),
203 						strerror(res));
204 					exit(-1);
205 				}
206 			}
207 		} else if (showWarnings) {
208 			const char *end = strchr(in, ';');
209 			BString match;
210 			if (end)
211 				match.SetTo(start, end-start+1);
212 			else {
213 				// can't determine end of statement, we output next 40 chars
214 				match.SetTo(start, 40);
215 			}
216 			fprintf(stderr, "Warning: couldn't resolve catalog-access:\n\t%s\n",
217 				match.String());
218 		}
219 	}
220 }
221 
222 
223 int
224 main(int argc, char **argv)
225 {
226 	while ((++argv)[0]) {
227 		if (argv[0][0] == '-' && argv[0][1] != '-') {
228 			char *arg = argv[0] + 1;
229 			char c;
230 			while ((c = *arg++) != '\0') {
231 				if (c == 'p')
232 					showKeys = true;
233 				else if (c == 'l')
234 					catalogLang = (++argv)[0];
235 				else if (c == 's')
236 					catalogSig = (++argv)[0];
237 				else if (c == 'v')
238 					showSummary = true;
239 				else if (c == 'w')
240 					showWarnings = true;
241 				else if (c == 'o') {
242 					outputFile = (++argv)[0];
243 					break;
244 				}
245 				else if (c == 'r') {
246 					rxString = (++argv)[0];
247 					break;
248 				}
249 			}
250 		} else if (!strcmp(argv[0], "--help")) {
251 			usage();
252 		} else {
253 			if (!inputFile)
254 				inputFile = argv[0];
255 			else
256 				usage();
257 		}
258 	}
259 
260 	if (!outputFile.Length() && inputFile) {
261 		// generate default output-file from input-file by replacing
262 		// the extension with '.catkeys':
263 		outputFile = inputFile;
264 		int32 dot = outputFile.FindLast('.');
265 		if (dot >= B_OK)
266 			outputFile.Truncate(dot);
267 		outputFile << ".catkeys";
268 	}
269 	if (!inputFile || !catalogSig || !outputFile.Length() || !catalogLang)
270 		usage();
271 
272 	BFile inFile;
273 	status_t res = inFile.SetTo(inputFile, B_READ_ONLY);
274 	if (res != B_OK) {
275 		fprintf(stderr, "unable to open inputfile %s - error: %s\n", inputFile,
276 			strerror(res));
277 		exit(-1);
278 	}
279 
280 	off_t sz;
281 	inFile.GetSize(&sz);
282 	if (sz > 0) {
283 		BString inputStr;
284 		char *buf = inputStr.LockBuffer(sz);
285 		off_t rsz = inFile.Read(buf, sz);
286 		if (rsz < sz) {
287 			fprintf(stderr, "couldn't read %" B_PRId64 " bytes from %s (got "
288 				"only %" B_PRId64 ")\n", sz, inputFile, rsz);
289 			exit(-1);
290 		}
291 		inputStr.UnlockBuffer(rsz);
292 		catalog = new EditableCatalog("plaintext", catalogSig, catalogLang);
293 		collectAllCatalogKeys(inputStr);
294 		res = catalog->WriteToFile(outputFile.String());
295 		if (res != B_OK) {
296 			fprintf(stderr, "couldn't write catalog to %s - error: %s\n",
297 				outputFile.String(), strerror(res));
298 			exit(-1);
299 		}
300 		if (showSummary) {
301 			int32 count = catalog->CountItems();
302 			if (count)
303 				fprintf(stderr, "%" B_PRId32 " key%s found and written to %s\n",
304 					count, (count==1 ? "": "s"), outputFile.String());
305 			else
306 				fprintf(stderr, "no keys found\n");
307 		}
308 		delete catalog;
309 	}
310 
311 //	BEntry inEntry(inputFile);
312 //	inEntry.Remove();
313 
314 	return res;
315 }
316