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