1/*
2 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5#ifndef PACKAGE_INFO_STRING_BUILDER_H
6#define PACKAGE_INFO_STRING_BUILDER_H
7
8
9#include <ctype.h>
10
11#include <DataIO.h>
12#include <package/PackageInfo.h>
13
14
15namespace BPackageKit {
16
17
18struct BPackageInfo::StringBuilder {
19	StringBuilder()
20		:
21		fData(),
22		fError(B_OK),
23		fBasePackage()
24	{
25	}
26
27	status_t Error() const
28	{
29		return fError;
30	}
31
32	status_t GetString(BString& _string) const
33	{
34		if (fError != B_OK) {
35			_string = BString();
36			return fError;
37		}
38
39		_string.SetTo((const char*)fData.Buffer(), fData.BufferLength());
40		return (size_t)_string.Length() == fData.BufferLength()
41			? B_OK : B_NO_MEMORY;
42	}
43
44	template<typename Value>
45	StringBuilder& Write(const char* attribute, Value value)
46	{
47		if (_IsValueEmpty(value))
48			return *this;
49
50		_Write(attribute);
51		_Write('\t');
52		_WriteValue(value);
53		_Write('\n');
54		return *this;
55	}
56
57	StringBuilder& WriteFlags(const char* attribute, uint32 flags)
58	{
59		if ((flags & B_PACKAGE_FLAG_APPROVE_LICENSE) == 0
60			&& (flags & B_PACKAGE_FLAG_SYSTEM_PACKAGE) == 0) {
61			return *this;
62		}
63
64		_Write(attribute);
65		_Write('\t');
66
67		if ((flags & B_PACKAGE_FLAG_APPROVE_LICENSE) != 0)
68			_Write(" approve_license");
69		if ((flags & B_PACKAGE_FLAG_SYSTEM_PACKAGE) != 0)
70			_Write(" system_package");
71
72		_Write('\n');
73		return *this;
74	}
75
76	StringBuilder& BeginRequires(const BString& basePackage)
77	{
78		fBasePackage = basePackage;
79		return *this;
80	}
81
82	StringBuilder& EndRequires()
83	{
84		fBasePackage.Truncate(0);
85		return *this;
86	}
87
88private:
89	void _WriteValue(const char* value)
90	{
91		_WriteMaybeQuoted(value);
92	}
93
94	void _WriteValue(const BPackageVersion& value)
95	{
96		if (fError != B_OK)
97			return;
98
99		if (value.InitCheck() != B_OK) {
100			fError = B_BAD_VALUE;
101			return;
102		}
103
104		_Write(value.ToString());
105	}
106
107	void _WriteValue(const BStringList& value)
108	{
109		int32 count = value.CountStrings();
110		if (count == 1) {
111			_WriteMaybeQuoted(value.StringAt(0));
112		} else {
113			_Write("{\n", 2);
114
115			int32 count = value.CountStrings();
116			for (int32 i = 0; i < count; i++) {
117				_Write('\t');
118				_WriteMaybeQuoted(value.StringAt(i));
119				_Write('\n');
120			}
121
122			_Write('}');
123		}
124	}
125
126	template<typename Value>
127	void _WriteValue(const BObjectList<Value>& value)
128	{
129		// Note: The fBasePackage solution is disgusting, but any attempt of
130		// encapsulating the stringification via templates seems to result in
131		// an Internal Compiler Error with gcc 2.
132
133		_Write("{\n", 2);
134
135		int32 count = value.CountItems();
136		for (int32 i = 0; i < count; i++) {
137			_Write('\t');
138			_WriteListElement(value.ItemAt(i));
139			_Write('\n');
140		}
141
142		_Write('}');
143	}
144
145	template<typename Value>
146	void _WriteListElement(const Value* value)
147	{
148		_Write(value->ToString());
149		if (!fBasePackage.IsEmpty()
150			&& value->Name() == fBasePackage) {
151			_Write(" base");
152		}
153	}
154
155	void _WriteListElement(const BGlobalWritableFileInfo* value)
156	{
157		_WriteMaybeQuoted(value->Path());
158		if (value->IsDirectory()) {
159			_Write(' ');
160			_Write("directory");
161		}
162		if (value->IsIncluded()) {
163			_Write(' ');
164			_Write(kWritableFileUpdateTypes[value->UpdateType()]);
165		}
166	}
167
168	void _WriteListElement(const BUserSettingsFileInfo* value)
169	{
170		_WriteMaybeQuoted(value->Path());
171		if (value->IsDirectory()) {
172			_Write(" directory");
173		} else if (!value->TemplatePath().IsEmpty()) {
174			_Write(" template ");
175			_WriteMaybeQuoted(value->TemplatePath());
176		}
177	}
178
179	void _WriteListElement(const BUser* value)
180	{
181		_WriteMaybeQuoted(value->Name());
182
183		if (!value->RealName().IsEmpty()) {
184			_Write(" real-name ");
185			_WriteMaybeQuoted(value->RealName());
186		}
187
188		if (!value->Home().IsEmpty()) {
189			_Write(" home ");
190			_WriteMaybeQuoted(value->Home());
191		}
192
193		if (!value->Shell().IsEmpty()) {
194			_Write(" shell ");
195			_WriteMaybeQuoted(value->Shell());
196		}
197
198		if (!value->Groups().IsEmpty()) {
199			_Write(" groups ");
200			BString groups = value->Groups().Join(" ");
201			if (groups.IsEmpty()) {
202				if (fError == B_OK)
203					fError = B_NO_MEMORY;
204				return;
205			}
206			_Write(groups);
207		}
208	}
209
210	static inline bool _IsValueEmpty(const char* value)
211	{
212		return value[0] == '\0';
213	}
214
215	static inline bool _IsValueEmpty(const BPackageVersion& value)
216	{
217		return false;
218	}
219
220	template<typename List>
221	static inline bool _IsValueEmpty(const List& value)
222	{
223		return value.IsEmpty();
224	}
225
226	void _WriteMaybeQuoted(const char* data)
227	{
228		// check whether quoting is needed
229		bool needsQuoting = false;
230		bool needsEscaping = false;
231		for (const char* it = data; *it != '\0'; it++) {
232			if (isalnum(*it) || *it == '.' || *it == '-' || *it == '_'
233				|| *it == ':' || *it == '+') {
234				continue;
235			}
236
237			needsQuoting = true;
238
239			if (*it == '\t' || *it == '\n' || *it == '"' || *it == '\\') {
240				needsEscaping = true;
241				break;
242			}
243		}
244
245		if (!needsQuoting) {
246			_Write(data);
247			return;
248		}
249
250		// we need quoting
251		_Write('"');
252
253		// escape the string, if necessary
254		if (needsEscaping) {
255			const char* start = data;
256			const char* end = data;
257			while (*end != '\0') {
258				char replacement[2];
259				switch (*end) {
260					case '\t':
261						replacement[1] = 't';
262						break;
263					case '\n':
264						replacement[1] = 'n';
265						break;
266					case '"':
267					case '\\':
268						replacement[1] = *end;
269						break;
270					default:
271						end++;
272						continue;
273				}
274
275				if (start < end)
276					_Write(start, end - start);
277
278				replacement[0] = '\\';
279				_Write(replacement, 2);
280				start = ++end;
281			}
282
283			if (start < end)
284				_Write(start, end - start);
285		} else
286			_Write(data);
287
288		_Write('"');
289	}
290
291	inline void _Write(char data)
292	{
293		_Write(&data, 1);
294	}
295
296	inline void _Write(const char* data)
297	{
298		_Write(data, strlen(data));
299	}
300
301	inline void _Write(const BString& data)
302	{
303		_Write(data, data.Length());
304	}
305
306	void _Write(const void* data, size_t size)
307	{
308		if (fError == B_OK) {
309			ssize_t bytesWritten = fData.Write(data, size);
310			if (bytesWritten < 0)
311				fError = bytesWritten;
312		}
313	}
314
315private:
316	BMallocIO	fData;
317	status_t	fError;
318	BString		fBasePackage;
319};
320
321
322} // namespace BPackageKit
323
324
325#endif	// PACKAGE_INFO_STRING_BUILDER_H
326