xref: /haiku/src/tools/locale/collectcatkeys.cpp (revision e36a1b58e6daf3efeec46621114691ef499faafc)
1 /*
2  * Copyright 2003-2009, Haiku.
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("(be_catalog\\s*->\\s*GetString\\s*"
32 	"|BCatalogAddOn\\s*::\\s*MarkForTranslation\\s*)");
33 
34 
35 BString str, ctx, cmt;
36 bool haveID;
37 int32 id;
38 
39 
40 PlainTextCatalog *catalog = NULL;
41 
42 
43 void
44 usage()
45 {
46 	fprintf(stderr,
47 		"usage: collectcatkeys [-pvw] [-r <regex>] [-o <outfile>] [-l <catalogLanguage>]\n"
48 		"                      -s <catalogSig> <prepCppFile>\n"
49 		"options:\n"
50 		"  -l <catalogLang>\tlanguage of the target-catalog (default is English)\n"
51 		"  -o <outfile>\t\texplicitly specifies the name of the output-file\n"
52 		"  -p\t\t\tprint keys as they are found\n"
53 		"  -r <regex>\t\tchanges the regex used by the key-scanner to the one given,\n"
54 		"      \t\t\tthe default is:   be_catalog\\s*->\\s*GetString\\s*\n"
55 		"  -s <catalogSig>\tsignature of the target-catalog\n"
56 		"  -v\t\t\tbe verbose, show summary\n"
57 		"  -w\t\t\tshow warnings about catalog-accesses that couldn't be resolved completely\n");
58 	exit(-1);
59 }
60 
61 
62 bool
63 fetchStr(const char *&in, BString &str, bool lookForID)
64 {
65 	int parLevel = 0;
66 	while (isspace(*in) || *in == '(') {
67 		if (*in == '(')
68 			parLevel++;
69 		in++;
70 	}
71 	if (*in == '"') {
72 		bool inString = true;
73 		bool quoted = false;
74 		in++;
75 		while (parLevel >= 0 && inString)
76 		{
77 			// Collect string content until we find a quote marking end of
78 			// string (skip escaped quotes)
79 			while (*in != '"' || quoted)
80 			{
81 				str.Append(in, 1);
82 				if (*in == '\\' && !quoted)
83 					quoted = true ;
84 				else
85 					quoted = false ;
86 				in++;
87 			}
88 			in++;
89 
90 			inString = false;
91 
92 			// Strip all whitespace until we find a closing parenthesis, or the
93 			// beginning of another string
94 			// TODO: ignore comments
95 			while (isspace(*in) || *in == ')') {
96 				if (*in == ')') {
97 					if (parLevel == 0)
98 						return true;
99 					parLevel--;
100 				}
101 
102 				in++;
103 			}
104 
105 			if (*in == '"') {
106 				inString = true;
107 				in++;
108 			}
109 		}
110 	} else {
111 		if (!memcmp(in, "__null", 6)) {
112 			// NULL is preprocessed into __null, which we parse as ""
113 			in += 6;
114 		} else if (lookForID && (isdigit(*in) || *in == '-' || *in == '+')) {
115 			// try to parse an ID (a long):
116 			errno = 0;
117 			char *next;
118 			id = strtol(in, &next, 10);
119 			if (id != 0 || errno == 0) {
120 				haveID = true;
121 				in = next;
122 			}
123 		} else
124 			return false;
125 
126 		while (isspace(*in) || *in == ')') {
127 			if (*in == ')') {
128 				if (!parLevel)
129 					return true;
130 				parLevel--;
131 			}
132 			in++;
133 		}
134 	}
135 	return true;
136 }
137 
138 
139 bool
140 fetchKey(const char *&in)
141 {
142 	str = ctx = cmt = "";
143 	haveID = false;
144 	// fetch native string or id:
145 	if (!fetchStr(in, str, true)) {
146 		return false;
147 	}
148 	if (*in == ',') {
149 		in++;
150 		// fetch context:
151 		if (!fetchStr(in, ctx, false)) {
152 			fprintf(stderr,"Context parsing error\n");
153 			return false;
154 		}
155 		if (*in == ',') {
156 			in++;
157 			// fetch comment:
158 			if (!fetchStr(in, cmt, false))
159 			{
160 				fprintf(stderr,"Comment parsing error\n");
161 				return false;
162 			}
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(%ld)\n", id);
187 				res = catalog->SetString(id, "");
188 				if (res != B_OK) {
189 					fprintf(stderr, "Collectcatkeys: couldn't add key %ld - "
190 						"error: %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 	if (!outputFile.Length() && inputFile) {
260 		// generate default output-file from input-file by replacing
261 		// the extension with '.catkeys':
262 		outputFile = inputFile;
263 		int32 dot = outputFile.FindLast('.');
264 		if (dot >= B_OK)
265 			outputFile.Truncate(dot);
266 		outputFile << ".catkeys";
267 	}
268 	if (!inputFile || !catalogSig || !outputFile.Length() || !catalogLang)
269 		usage();
270 
271 	BFile inFile;
272 	status_t res = inFile.SetTo(inputFile, B_READ_ONLY);
273 	if (res != B_OK) {
274 		fprintf(stderr, "unable to open inputfile %s - error: %s\n", inputFile,
275 			strerror(res));
276 		exit(-1);
277 	}
278 	off_t sz;
279 	inFile.GetSize(&sz);
280 	if (sz > 0) {
281 		BString inputStr;
282 		char *buf = inputStr.LockBuffer(sz);
283 		off_t rsz = inFile.Read(buf, sz);
284 		if (rsz < sz) {
285 			fprintf(stderr, "couldn't read %Ld bytes from %s (got only %Ld)\n",
286 				sz, inputFile, rsz);
287 			exit(-1);
288 		}
289 		inputStr.UnlockBuffer(rsz);
290 		catalog = new PlainTextCatalog(inputFile, catalogSig, catalogLang);
291 		collectAllCatalogKeys(inputStr);
292 		res = catalog->WriteToFile(outputFile.String());
293 		if (res != B_OK) {
294 			fprintf(stderr, "couldn't write catalog to %s - error: %s\n",
295 				outputFile.String(), strerror(res));
296 			exit(-1);
297 		}
298 		if (showSummary) {
299 			int32 count = catalog->CountItems();
300 			if (count)
301 				fprintf(stderr, "%ld key%s found and written to %s\n",
302 					count, (count==1 ? "": "s"), outputFile.String());
303 			else
304 				fprintf(stderr, "no keys found\n");
305 		}
306 		delete catalog;
307 	}
308 
309 //	BEntry inEntry(inputFile);
310 //	inEntry.Remove();
311 
312 	return res;
313 }
314