1/*
2 * Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <ctype.h>
8#include <errno.h>
9#include <getopt.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13
14#include <Message.h>
15#include <String.h>
16
17
18enum {
19	FLAG_MANDATORY_FIELD	= 0x01,
20	FLAG_LIST_ATTRIBUTE		= 0x02,
21	FLAG_DONT_QUOTE			= 0x04,
22};
23
24
25extern const char* __progname;
26const char* kCommandName = __progname;
27
28
29static const char* kUsage =
30	"Usage: %s [ <options> ] <optional package description> "
31		"[ <package info> ]\n"
32	"Converts an .OptionalPackageDescription to a .PackageInfo. If "
33		"<package info>\n"
34	"is not specified, the output is printed to stdout.\n"
35	"Note that the generated .PackageInfo will not be complete. For several\n"
36	"fields an empty string will be used, unless specified via an option.\n"
37	"The \"provides\" and \"requires\" lists will always be empty, though\n"
38	"\n"
39	"Options:\n"
40	"  -a <arch>        - Use the given architecture string. Default is to "
41		"guess from the file name.\n"
42	"  -d <description> - Use the given descripton string. Default is to use\n"
43	"                     the summary.\n"
44	"  -h, --help       - Print this usage info.\n"
45	"  -p <packager>    - Use the given packager string. Default is an empty "
46		"string.\n"
47	"  -s <summary>     - Use the given summary string. Default is an empty "
48		"string.\n"
49	"  -v <version>     - Use the given version string. Overrides the version\n"
50	"                     from the input file.\n"
51	"  -V <vendor>      - Use the given vendor string. Default is an empty "
52		"string.\n"
53;
54
55
56static void
57print_usage_and_exit(bool error)
58{
59    fprintf(error ? stderr : stdout, kUsage, kCommandName);
60    exit(error ? 1 : 0);
61}
62
63
64static const char*
65guess_architecture(const char* name)
66{
67	if (strstr(name, "x86") != NULL) {
68		if (strstr(name, "gcc4") != NULL)
69			return "x86";
70
71		return "x86_gcc2";
72	}
73
74	return NULL;
75}
76
77
78struct OuputWriter {
79	OuputWriter(FILE* output, const BMessage& package)
80		:
81		fOutput(output),
82		fPackage(package)
83	{
84	}
85
86	void WriteAttribute(const char* attributeName, const char* fieldName,
87		const char* defaultValue, uint32 flags)
88	{
89		if (fieldName != NULL) {
90			int32 count;
91			type_code type;
92			if (fPackage.GetInfo(fieldName, &type, &count) != B_OK) {
93				if ((flags & FLAG_MANDATORY_FIELD) != 0) {
94					fprintf(stderr, "Error: Missing mandatory field \"%s\" in "
95						"input file.\n", fieldName);
96					exit(1);
97				}
98				count = 0;
99			}
100
101			if (count > 0) {
102				if (count == 1) {
103					const char* value;
104					fPackage.FindString(fieldName, &value);
105					_WriteSingleElementAttribute(attributeName, value, flags);
106				} else {
107					fprintf(fOutput, "\n%s {\n", attributeName);
108
109					for (int32 i = 0; i < count; i++) {
110						fprintf(fOutput, "\t");
111						const char* value;
112						fPackage.FindString(fieldName, i, &value);
113						_WriteValue(value, flags);
114						fputc('\n', fOutput);
115					}
116
117					fputs("}\n", fOutput);
118				}
119
120				return;
121			}
122		}
123
124		// write the default value
125		if (defaultValue != NULL)
126			_WriteSingleElementAttribute(attributeName, defaultValue, flags);
127	}
128
129private:
130	void _WriteSingleElementAttribute(const char* attributeName,
131		const char* value, uint32 flags)
132	{
133		fputs(attributeName, fOutput);
134
135		int32 indentation = 16 - (int32)strlen(attributeName);
136		if (indentation > 0)
137			indentation = (indentation + 3) / 4;
138		else
139			indentation = 1;
140
141		for (int32 i = 0; i < indentation; i++)
142			fputc('\t', fOutput);
143
144		_WriteValue(value, flags);
145		fputc('\n',  fOutput);
146	}
147
148	void _WriteValue(const char* value, uint32 flags)
149	{
150		BString escapedValue(value);
151
152		if ((flags & FLAG_DONT_QUOTE) != 0) {
153			escapedValue.CharacterEscape("\\\"' \t", '\\');
154			fputs(escapedValue.String(), fOutput);
155		} else {
156			escapedValue.CharacterEscape("\\\"", '\\');
157			fprintf(fOutput, "\"%s\"", escapedValue.String());
158		}
159	}
160
161private:
162	FILE*			fOutput;
163	const BMessage&	fPackage;
164};
165
166
167int
168main(int argc, const char* const* argv)
169{
170	const char* architecture = NULL;
171	const char* version = NULL;
172	const char* summary = "";
173	const char* description = "";
174	const char* packager = "";
175	const char* vendor = "";
176
177	while (true) {
178		static const struct option kLongOptions[] = {
179			{ "help", no_argument, 0, 'h' },
180			{ 0, 0, 0, 0 }
181		};
182
183		opterr = 0; // don't print errors
184		int c = getopt_long(argc, (char**)argv, "+ha:d:p:s:v:V:", kLongOptions,
185			NULL);
186		if (c == -1)
187			break;
188
189		switch (c) {
190			case 'a':
191				architecture = optarg;
192				break;
193
194			case 'd':
195				description = optarg;
196				break;
197
198			case 'h':
199				print_usage_and_exit(false);
200				break;
201
202			case 'p':
203				packager = optarg;
204				break;
205
206			case 's':
207				summary = optarg;
208				break;
209
210			case 'v':
211				version = optarg;
212				break;
213
214			case 'V':
215				vendor = optarg;
216				break;
217
218			default:
219				print_usage_and_exit(true);
220				break;
221		}
222	}
223
224	// One or two argument should remain -- the input file and optionally the
225	// output file.
226	if (optind + 1 != argc && optind + 2 != argc)
227		print_usage_and_exit(true);
228
229	const char* opdName = argv[optind++];
230	const char* packageInfoName = optind < argc ? argv[optind++] : NULL;
231
232	// guess architecture from the input file name, if not given
233	if (architecture == NULL) {
234		const char* fileName = strrchr(opdName, '/');
235		if (fileName == NULL)
236			fileName = opdName;
237		else
238			fileName++;
239
240		// Try to guess from the file name.
241		architecture = guess_architecture(fileName);
242
243		// If we've got nothing yet, try to guess from the file name.
244		if (architecture == NULL && fileName != opdName)
245			architecture = guess_architecture(opdName);
246
247		// fallback is "any"
248		if (architecture == NULL)
249			architecture = "any";
250	}
251
252	// open the input
253	FILE* input = fopen(opdName, "r");
254	if (input == NULL) {
255		fprintf(stderr, "Failed to open input file \"%s\": %s\n", opdName,
256			strerror(errno));
257		exit(1);
258	}
259
260	// open the output
261	FILE* output = packageInfoName != NULL
262		? fopen(packageInfoName, "w+") : stdout;
263	if (output == NULL) {
264		fprintf(stderr, "Failed to open output file \"%s\": %s\n",
265			packageInfoName, strerror(errno));
266		exit(1);
267	}
268
269	// read and parse the input file
270	BMessage package;
271	BString fieldName;
272	BString fieldValue;
273	char lineBuffer[LINE_MAX];
274	bool seenPackageAttribute = false;
275
276	while (char* line = fgets(lineBuffer, sizeof(lineBuffer), input)) {
277		// chop off line break
278		size_t lineLen = strlen(line);
279		if (lineLen > 0 && line[lineLen - 1] == '\n')
280			line[--lineLen] = '\0';
281
282		// flush previous field, if a new field begins, otherwise append
283		if (lineLen == 0 || !isspace(line[0])) {
284			// new field -- flush the previous one
285			if (fieldName.Length() > 0) {
286				fieldValue.Trim();
287				package.AddString(fieldName.String(), fieldValue);
288				fieldName = "";
289			}
290		} else if (fieldName.Length() > 0) {
291			// append to current field
292			fieldValue += line;
293			continue;
294		} else {
295			// bogus line -- ignore
296			continue;
297		}
298
299		if (lineLen == 0)
300			continue;
301
302		// parse new field
303		char* colon = strchr(line, ':');
304		if (colon == NULL) {
305			// bogus line -- ignore
306			continue;
307		}
308
309		fieldName.SetTo(line, colon - line);
310		fieldName.Trim();
311		if (fieldName.Length() == 0) {
312			// invalid field name
313			continue;
314		}
315
316		fieldValue = colon + 1;
317
318		if (fieldName == "Package") {
319			if (seenPackageAttribute) {
320				fprintf(stderr, "Duplicate \"Package\" attribute!\n");
321				exit(1);
322			}
323
324			seenPackageAttribute = true;
325		}
326	}
327
328	// write the output
329	OuputWriter writer(output, package);
330
331	// name
332	writer.WriteAttribute("name", "Package", NULL,
333		FLAG_MANDATORY_FIELD | FLAG_DONT_QUOTE);
334
335	// version
336	writer.WriteAttribute("version", "Version", version, FLAG_DONT_QUOTE);
337
338	// architecture
339	fprintf(output, "architecture\t%s\n", architecture);
340
341	// summary
342	fprintf(output, "summary\t\t\t\"%s\"\n", summary);
343
344	// description
345	if (description != NULL)
346		fprintf(output, "description\t\t\"%s\"\n", description);
347	else
348		fprintf(output, "description\t\t\"%s\"\n", summary);
349
350	// packager
351	fprintf(output, "packager\t\t\"%s\"\n", packager);
352
353	// vendor
354	fprintf(output, "vendor\t\t\t\"%s\"\n", vendor);
355
356	// copyrights
357	writer.WriteAttribute("copyrights", "Copyright", NULL,
358		FLAG_MANDATORY_FIELD | FLAG_LIST_ATTRIBUTE);
359
360	// licenses
361	writer.WriteAttribute("licenses", "License", NULL, FLAG_LIST_ATTRIBUTE);
362
363	// empty provides
364	fprintf(output, "\nprovides {\n}\n");
365
366	// empty requires
367	fprintf(output, "\nrequires {\n}\n");
368
369	// URLs
370	writer.WriteAttribute("urls", "URL", NULL, FLAG_LIST_ATTRIBUTE);
371
372	// source URLs
373	writer.WriteAttribute("source-urls", "SourceURL", NULL,
374		FLAG_LIST_ATTRIBUTE);
375
376	return 0;
377}
378