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
24using BPrivate::EditableCatalog;
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
36BString str, ctx, cmt;
37bool haveID;
38int32 id;
39
40
41EditableCatalog *catalog = NULL;
42
43
44void
45usage()
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
68bool
69fetchStr(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
146bool
147fetchKey(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
170void
171collectAllCatalogKeys(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
224int
225main(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