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