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
24using BPrivate::PlainTextCatalog;
25
26
27bool showKeys = false;
28bool showSummary = false;
29bool showWarnings = false;
30const char *inputFile = NULL;
31BString outputFile;
32const char *catalogSig = NULL;
33const char *catalogLang = "English";
34BString rxString("B_CATKEY\\s*");
35
36
37BString str, ctx, cmt;
38bool haveID;
39int32 id;
40
41
42PlainTextCatalog *catalog = NULL;
43
44
45void
46usage()
47{
48	fprintf(stderr,
49		"usage: collectcatkeys [-pvw] [-r <regex>] [-o <outfile>] "
50		"[-l <catalogLanguage>]\n"
51		"                      -s <catalogSig> <prepCppFile>\n"
52		"options:\n"
53		"  -l <catalogLang>\tlanguage of the target-catalog (default is "
54		"English)\n"
55		"  -o <outfile>\t\texplicitly specifies the name of the output-file\n"
56		"  -p\t\t\tprint keys as they are found\n"
57		"  -r <regex>\t\tchanges the regex used by the key-scanner to the one "
58		"given,\n"
59		"      \t\t\tthe default is:   ");
60		fprintf(stderr, "%s", rxString.String());
61		fprintf(stderr,"\n  -s <catalogSig>\tsignature of the target-catalog\n"
62		"  -v\t\t\tbe verbose, show summary\n"
63		"  -w\t\t\tshow warnings about catalog-accesses that couldn't be "
64		" resolved completely\n");
65	exit(-1);
66}
67
68
69bool
70fetchStr(const char *&in, BString &str, bool lookForID)
71{
72	int parLevel = 0;
73	while (isspace(*in) || *in == '(') {
74		if (*in == '(')
75			parLevel++;
76		in++;
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
145bool
146fetchKey(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	}
154	if (*in == ',') {
155		in++;
156		// fetch context:
157		if (!fetchStr(in, ctx, false)) {
158			fprintf(stderr,"Context parsing error\n");
159			return false;
160		}
161		if (*in == ',') {
162			in++;
163			// fetch comment:
164			if (!fetchStr(in, cmt, false))
165			{
166				fprintf(stderr,"Comment parsing error\n");
167				return false;
168			}
169		}
170	}
171	return true;
172}
173
174
175void
176collectAllCatalogKeys(BString& inputStr)
177{
178	RegExp rx;
179	struct regexp *rxprg = rx.Compile(rxString.String());
180	if (rx.InitCheck() != B_OK) {
181		fprintf(stderr, "regex-compilation error %s\n", rx.ErrorString());
182		return;
183	}
184	status_t res;
185	const char *in = inputStr.String();
186	while (rx.RunMatcher(rxprg, in)) {
187		const char *start = rxprg->startp[0];
188		in = rxprg->endp[0];
189		if (fetchKey(in)) {
190			if (haveID) {
191				if (showKeys)
192					printf("CatKey(%d)\n", id);
193				res = catalog->SetString(id, "");
194				if (res != B_OK) {
195					fprintf(stderr, "Collectcatkeys: couldn't add key %d - "
196						"error: %s\n", id, strerror(res));
197					exit(-1);
198				}
199			} else {
200				if (showKeys) {
201					printf("CatKey(\"%s\", \"%s\", \"%s\")\n", str.String(),
202						ctx.String(), cmt.String());
203				}
204				res = catalog->SetString(str.String(), str.String(),
205					ctx.String(), cmt.String());
206				if (res != B_OK) {
207					fprintf(stderr, "couldn't add key %s,%s,%s - error: %s\n",
208						str.String(), ctx.String(), cmt.String(),
209						strerror(res));
210					exit(-1);
211				}
212			}
213		} else if (showWarnings) {
214			const char *end = strchr(in, ';');
215			BString match;
216			if (end)
217				match.SetTo(start, end-start+1);
218			else {
219				// can't determine end of statement, we output next 40 chars
220				match.SetTo(start, 40);
221			}
222			fprintf(stderr, "Warning: couldn't resolve catalog-access:\n\t%s\n",
223				match.String());
224		}
225	}
226}
227
228
229int
230main(int argc, char **argv)
231{
232	while ((++argv)[0]) {
233		if (argv[0][0] == '-' && argv[0][1] != '-') {
234			char *arg = argv[0] + 1;
235			char c;
236			while ((c = *arg++) != '\0') {
237				if (c == 'p')
238					showKeys = true;
239				else if (c == 'l')
240					catalogLang = (++argv)[0];
241				else if (c == 's')
242					catalogSig = (++argv)[0];
243				else if (c == 'v')
244					showSummary = true;
245				else if (c == 'w')
246					showWarnings = true;
247				else if (c == 'o') {
248					outputFile = (++argv)[0];
249					break;
250				} else if (c == 'r') {
251					rxString = (++argv)[0];
252					break;
253				}
254			}
255		} else if (!strcmp(argv[0], "--help")) {
256			usage();
257		} else {
258			if (!inputFile)
259				inputFile = argv[0];
260			else
261				usage();
262		}
263	}
264	if (!outputFile.Length() && inputFile) {
265		// generate default output-file from input-file by replacing
266		// the extension with '.catkeys':
267		outputFile = inputFile;
268		int32 dot = outputFile.FindLast('.');
269		if (dot >= B_OK)
270			outputFile.Truncate(dot);
271		outputFile << ".catkeys";
272	}
273	if (!inputFile || !catalogSig || !outputFile.Length() || !catalogLang)
274		usage();
275
276	BFile inFile;
277	status_t res = inFile.SetTo(inputFile, B_READ_ONLY);
278	if (res != B_OK) {
279		fprintf(stderr, "unable to open inputfile %s - error: %s\n", inputFile,
280			strerror(res));
281		exit(-1);
282	}
283	off_t sz;
284	inFile.GetSize(&sz);
285	if (sz > 0) {
286		BString inputStr;
287		char *buf = inputStr.LockBuffer(sz);
288		off_t rsz = inFile.Read(buf, sz);
289		if (rsz < sz) {
290			fprintf(stderr, "couldn't read %" B_PRIdOFF " bytes from %s "
291				"(got only %" B_PRIdOFF ")\n",
292				sz, inputFile, rsz);
293			exit(-1);
294		}
295		inputStr.UnlockBuffer(rsz);
296		catalog = new PlainTextCatalog(inputFile, catalogSig, catalogLang);
297		collectAllCatalogKeys(inputStr);
298		res = catalog->WriteToFile(outputFile.String());
299		if (res != B_OK) {
300			fprintf(stderr, "couldn't write catalog to %s - error: %s\n",
301				outputFile.String(), strerror(res));
302			exit(-1);
303		}
304		if (showSummary) {
305			int32 count = catalog->CountItems();
306			if (count)
307				fprintf(stderr, "%d key%s found and written to %s\n",
308					count, (count==1 ? "": "s"), outputFile.String());
309			else
310				fprintf(stderr, "no keys found\n");
311		}
312		delete catalog;
313	}
314
315//	BEntry inEntry(inputFile);
316//	inEntry.Remove();
317
318	return res;
319}
320