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