1 /*
2  * This file is part of gtkD.
3  *
4  * gtkD is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation; either version 3
7  * of the License, or (at your option) any later version, with
8  * some exceptions, please read the COPYING file.
9  *
10  * gtkD is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with gtkD; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
18  */
19 
20 module utils.GtkPackage;
21 
22 import std.algorithm;
23 import std.array: empty;
24 import std.file;
25 import std.path;
26 import std.string : splitLines, strip, split;
27 import std.uni;
28 import std.stdio;
29 
30 import utils.GtkAlias;
31 import utils.GtkEnum;
32 import utils.GtkFunction;
33 import utils.GtkStruct;
34 import utils.GtkWrapper;
35 import utils.IndentedStringBuilder;
36 import utils.XML;
37 import utils.LinkedHasMap: Map = LinkedHashMap;
38 
39 class GtkPackage
40 {
41 	string name;
42 	string cTypePrefix;
43 	string srcDir;
44 	string bindDir;
45 	GtkWrapper wrapper;
46 
47 	string[] publicImports;
48 	string[] lookupAliases;     /// Aliases defined in the lookupfile.
49 	string[] lookupEnums;       /// Enums defined in the lookupfile.
50 	string[] lookupStructs;     /// Structs defined in the lookupfile.
51 	string[] lookupFuncts;      /// Functions defined in the lookupfile.
52 	string[] lookupConstants;   /// Constants defined in the lookupfile.
53 	
54 	static GtkPackage[string] namespaces;
55 
56 	Map!(string, GtkAlias)    collectedAliases; /// Aliases defined in the gir file.
57 	Map!(string, GtkEnum)     collectedEnums;   /// Enums defined in the gir file.
58 	Map!(string, GtkStruct)   collectedStructs;
59 	Map!(string, GtkFunction) collectedCallbacks;
60 	Map!(string, GtkFunction) collectedFunctions;
61 	GtkEnum stockIDs;           /// The StockID enum (Deprecated).
62 	GtkEnum GdkKeys;            /// The GdkKey enum.
63 
64 	public this(string pack, GtkWrapper wrapper, string srcDir, string bindDir)
65 	{
66 		this.name = pack;
67 		this.wrapper = wrapper;
68 		this.srcDir = srcDir;
69 		this.bindDir = bindDir;
70 		this.stockIDs = new GtkEnum(wrapper, this);
71 		this.GdkKeys  = new GtkEnum(wrapper, this);
72 
73 		try
74 		{
75 			if ( !exists(buildPath(wrapper.outputRoot, srcDir, bindDir)) )
76 				mkdirRecurse(buildPath(wrapper.outputRoot, srcDir, bindDir));
77 		}
78 		catch (Exception)
79 		{
80 			throw new Exception("Failed to create directory: "~ buildPath(wrapper.outputRoot, srcDir, bindDir));
81 		}
82 
83 		try
84 		{
85 			if ( !exists(buildPath(wrapper.outputRoot, srcDir, pack)) )
86 				mkdirRecurse(buildPath(wrapper.outputRoot, srcDir, pack));
87 		}
88 		catch (Exception)
89 		{
90 			throw new Exception("Failed to create directory: "~ buildPath(wrapper.outputRoot, srcDir, pack));
91 		}
92 
93 		publicImports ~= bindDir ~"."~ pack;
94 	}
95 
96 	void parseGIR(string girFile)
97 	{
98 		if ( !isAbsolute(girFile) )
99 			girFile = buildNormalizedPath("/usr/share/gir-1.0", girFile);
100 
101 		auto reader = new XMLReader!string(readText(girFile));
102 
103 		while ( !reader.empty && reader.front.value != "repository" )
104 			reader.popFront();
105 
106 		reader.popFront();
107 
108 		while ( !reader.empty && reader.front.value == "include" )
109 		{
110 			//TODO: parse imports.
111 
112 			reader.popFront();
113 		}
114 
115 		while ( !reader.empty && reader.front.value != "namespace" )
116 			reader.popFront();
117 
118 		namespaces[reader.front.attributes["name"]] = this;
119 		cTypePrefix = reader.front.attributes["c:identifier-prefixes"];
120 
121 		reader.popFront();
122 
123 		while ( !reader.empty && !reader.endTag("namespace") )
124 		{
125 			if ( reader.front.type == XMLNodeType.EndTag )
126 			{
127 				reader.popFront();
128 				continue;
129 			}
130 
131 			switch (reader.front.value)
132 			{
133 				case "alias":
134 					GtkAlias gtkAlias = new GtkAlias(wrapper);
135 					gtkAlias.parse(reader);
136 
137 					if ( gtkAlias.cType == "GType" )
138 						break;
139 
140 					collectedAliases[gtkAlias.name] = gtkAlias;
141 					break;
142 				case "bitfield":
143 				case "enumeration":
144 					GtkEnum gtkEnum = new GtkEnum(wrapper, this);
145 					gtkEnum.parse(reader);
146 					collectedEnums[gtkEnum.name] = gtkEnum;
147 					break;
148 				case "class":
149 				case "interface":
150 				case "record":
151 				case "union":
152 					GtkStruct gtkStruct = new GtkStruct(wrapper, this);
153 					gtkStruct.parse(reader);
154 
155 					//Workaround: Dont overwrite the regular pango classes.
156 					if ( gtkStruct.cType.among("PangoCairoFont", "PangoCairoFontMap") )
157 					{
158 						collectedStructs["FcFontMap"].merge(gtkStruct);
159 						break;
160 					}
161 
162 					collectedStructs[gtkStruct.name] = gtkStruct;
163 
164 					if ( name == "pango" )
165 						gtkStruct.name = "Pg"~gtkStruct.name;
166 					break;
167 				case "callback":
168 					GtkFunction callback = new GtkFunction(wrapper, null);
169 					callback.parse(reader);
170 					collectedCallbacks[callback.name] = callback;
171 					break;
172 				case "constant":
173 					parseConstant(reader);
174 					break;
175 				case "function":
176 					parseFunction(reader);
177 					break;
178 				default:
179 					assert(false, "Unexpected tag: "~ reader.front.value);
180 			}
181 			reader.popFront();
182 		}
183 	}
184 
185 	void parseConstant(T)(XMLReader!T reader)
186 	{
187 		if ( reader.front.attributes["name"].startsWith("STOCK_") )
188 		{
189 			GtkEnumMember member = GtkEnumMember(wrapper);
190 			member.parse(reader);
191 			member.name = member.name[6..$];
192 
193 			stockIDs.members ~= member;
194 			return;
195 		}
196 		else if ( reader.front.attributes["c:type"].startsWith("GDK_KEY_") )
197 		{
198 			GtkEnumMember member = GtkEnumMember(wrapper);
199 			member.parse(reader);
200 			member.name = "GDK_"~ member.name[4..$];
201 
202 			GdkKeys.members ~= member;
203 			return;
204 		}
205 
206 		//TODO: other constants.
207 		reader.skipTag();
208 	}
209 
210 	void parseFunction(T)(XMLReader!T reader)
211 	{
212 		GtkFunction funct = new GtkFunction(wrapper, null);
213 		funct.parse(reader);
214 		collectedFunctions[funct.name] = funct;
215 	}
216 
217 	GtkStruct getStruct(string name)
218 	{
219 		GtkPackage pack = this;
220 
221 		if ( name.canFind(".") )
222 		{
223 			string[] vals = name.split(".");
224 
225 			if ( vals[0] !in namespaces )
226 				return null;
227 
228 			pack = namespaces[vals[0]];
229 			name = vals[1];
230 		}
231 		return pack.collectedStructs.get(name, pack.collectedStructs.get("lookup"~name, null));
232 	}
233 
234 	GtkEnum getEnum(string name)
235 	{
236 		GtkPackage pack = this;
237 
238 		if ( name.canFind(".") )
239 		{
240 			string[] vals = name.split(".");
241 
242 			if ( vals[0] !in namespaces )
243 				return null;
244 
245 			pack = namespaces[vals[0]];
246 			name = vals[1];
247 		}
248 		return pack.collectedEnums.get(name, null);
249 	}
250 
251 	void writeClasses()
252 	{
253 		foreach ( strct; collectedStructs )
254 			strct.writeClass();
255 	}
256 
257 	void writeTypes()
258 	{
259 		string buff = wrapper.licence;
260 		auto indenter = new IndentedStringBuilder();
261 
262 		buff ~= "module "~ bindDir ~"."~ name ~"types;\n\n";
263 
264 		buff ~= indenter.format(lookupAliases);
265 		foreach ( a; collectedAliases )
266 		{
267 			buff ~= "\n";
268 			buff ~= indenter.format(a.getAliasDeclaration());
269 		}
270 
271 		buff ~= indenter.format(lookupEnums);
272 		foreach ( e; collectedEnums )
273 		{
274 			buff ~= "\n";
275 			buff ~= indenter.format(e.getEnumDeclaration());
276 		}
277 
278 		buff ~= indenter.format(lookupStructs);
279 		foreach ( s; collectedStructs )
280 		{
281 			if ( s.noExternal || s.noDecleration )
282 				continue;
283 
284 			buff ~= "\n";
285 			buff ~= indenter.format(s.getStructDeclaration());
286 		}
287 
288 		buff ~= indenter.format(lookupFuncts);
289 		foreach ( f; collectedCallbacks )
290 		{
291 			buff ~= "\n";
292 			buff ~= indenter.format(f.getCallbackDeclaration());
293 		}
294 
295 		buff ~= indenter.format(lookupConstants);
296 		if ( stockIDs.members !is null )
297 		{
298 			stockIDs.cName = "StockID";
299 			stockIDs.doc = "StockIds";
300 			buff ~= "\n";
301 			buff ~= indenter.format(stockIDs.getEnumDeclaration());
302 		}
303 
304 		if ( GdkKeys.members !is null )
305 			writeGdkKeys();
306 
307 		std.file.write(buildPath(wrapper.outputRoot, srcDir, bindDir, name ~"types.d"), buff);
308 	}
309 
310 	void writeGdkKeys()
311 	{
312 		string buff = wrapper.licence;
313 
314 		buff ~= "module "~ name ~".Keysyms;\n\n";
315 
316 		buff ~= "/**\n";
317 		buff ~= " * GdkKeysyms.\n";
318 		buff ~= " */\n";
319 		buff ~= "public enum GdkKeysyms\n";
320 		buff ~= "{\n";
321 
322 		foreach ( member; GdkKeys.members )
323 		{
324 			buff ~= "\t"~ tokenToGtkD(member.name, wrapper.aliasses, false) ~" = "~ member.value ~",\n";
325 		}
326 
327 		buff ~= "}\n";
328 
329 		std.file.write(buildPath(wrapper.outputRoot, srcDir, name, "Keysyms.d"), buff);
330 	}
331 
332 	void writeLoaderTable()
333 	{
334 		string buff = wrapper.licence;
335 
336 		buff ~= "module "~ bindDir ~"."~ name ~";\n\n";
337 		buff ~= "import std.stdio;\n";
338 		buff ~= "import "~ bindDir ~"."~ name ~"types;\n";
339 
340 		if ( name == "glib" )
341 			buff ~= "import gtkc.gobjecttypes;\n";
342 		if ( name == "gdk" || name == "pango" )
343 			buff ~= "import gtkc.cairotypes;\n";
344 
345 		buff ~= "import gtkc.Loader;\n"
346 			~ "import gtkc.paths;\n\n"
347 			~ "shared static this()\n"
348 			~ "{";
349 
350 		foreach ( strct; collectedStructs )
351 		{
352 			if ( strct.functions.empty || strct.noExternal )
353 				continue;
354 
355 			buff ~= "\n\t// "~ name ~"."~ strct.name ~"\n\n";
356 
357 			foreach ( funct; strct.functions )
358 			{
359 				if ( funct.type == GtkFunctionType.Callback || funct.type == GtkFunctionType.Signal || funct.name.empty )
360 					continue;
361 
362 				buff ~= "\tLinker.link("~ funct.cType ~", \""~ funct.cType ~"\", "~ getLibrary(funct.cType) ~");\n";
363 			}
364 		}
365 
366 		buff ~= "}\n\n"
367 			~ "__gshared extern(C)\n"
368 			~ "{\n";
369 
370 		foreach ( strct; collectedStructs )
371 		{
372 			if ( strct.functions.empty || strct.noExternal )
373 				continue;
374 
375 			buff ~= "\n\t// "~ name ~"."~ strct.name ~"\n\n";
376 
377 			foreach ( funct; strct.functions )
378 			{
379 				if ( funct.type == GtkFunctionType.Callback || funct.type == GtkFunctionType.Signal || funct.name.empty )
380 					continue;
381 
382 				buff ~= "\t"~ funct.getExternal() ~"\n";
383 			}
384 		}
385 
386 		buff ~= "}\n\n";
387 
388 		foreach ( strct; collectedStructs )
389 		{
390 			if ( strct.functions.empty || strct.noExternal )
391 				continue;
392 
393 			buff ~= "\n// "~ name ~"."~ strct.name ~"\n\n";
394 
395 			foreach ( funct; strct.functions )
396 			{
397 				if ( funct.type == GtkFunctionType.Callback || funct.type == GtkFunctionType.Signal || funct.name.empty )
398 					continue;
399 
400 				buff ~= "alias c_"~ funct.cType ~" "~ funct.cType ~";\n";
401 			}
402 		}
403 
404 		std.file.write(buildPath(wrapper.outputRoot, srcDir, bindDir, name ~".d"), buff);
405 	}
406 
407 	private string getLibrary(string funct)
408 	{
409 		string library = "LIBRARY."~ name.toUpper();
410 
411 		if ( startsWith(funct, "gdk") && !startsWith(funct, "gdk_gl") )
412 			return library ~ ", LIBRARY.GDKPIXBUF";
413 		else if	( startsWith(funct, "pango_cairo") )
414 			return library ~ ", LIBRARY.PANGOCAIRO";
415 		else if	( startsWith(funct, "g_module") )
416 			return library ~ ", LIBRARY.GMODULE";
417 		else
418 			return library;
419 	}
420 }