1 /+
2  +  Copyright © 2015-2016 Rémi Thebault
3  +
4  +  Permission is hereby granted, free of charge, to any person
5  +  obtaining a copy of this software and associated documentation files
6  +  (the "Software"), to deal in the Software without restriction,
7  +  including without limitation the rights to use, copy, modify, merge,
8  +  publish, distribute, sublicense, and/or sell copies of the Software,
9  +  and to permit persons to whom the Software is furnished to do so,
10  +  subject to the following conditions:
11  +
12  +  The above copyright notice and this permission notice (including the
13  +  next paragraph) shall be included in all copies or substantial
14  +  portions of the Software.
15  +
16  +  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  +  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  +  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19  +  NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20  +  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21  +  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22  +  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  +  SOFTWARE.
24  +/
25 /+
26  +  A lot of code in this module is inspired by the wayland C scanner
27  +/
28 module wayland.scanner;
29 
30 import std.stdio;
31 import std.getopt;
32 import std.array;
33 
34 enum scannerVersion = "v1.0.0";
35 
36 int main(string[] args)
37 {
38     auto opt = new Options;
39 
40     opt.cmdline = args.join(" ");
41 
42     auto opt_handler = getopt(
43             args,
44             "input|i", "input file [stdin]", &opt.in_file,
45             "output|o", "output file [stdout]", &opt.out_file,
46             "module|m", "D module name (required)", &opt.module_name,
47 
48             "mode",     "output mode (client or server) [client]", &opt.mode,
49 
50             "protocol", "outputs main protocol code", &opt.protocol_code,
51             "ifaces", "outputs interfaces code", &opt.ifaces_code,
52             "ifaces_priv", "outputs private interface code", &opt.ifaces_priv,
53             "ifaces_priv_mod", "specify the private interface module",
54                     &opt.ifaces_priv_mod,
55 
56             "import|x", "external modules to import", &opt.priv_modules,
57             "public|p", "external modules to import publicly", &opt.pub_modules,
58         );
59 
60     if (opt_handler.helpWanted) {
61         defaultGetoptPrinter("A Wayland protocol scanner and D code generator",
62                 opt_handler.options);
63         return 0;
64     }
65 
66     if ((opt.protocol_code && opt.ifaces_code) ||
67             (!opt.protocol_code && !opt.ifaces_code)) {
68         stderr.writeln("must specify either procotol or interfaces code");
69         return 1;
70     }
71 
72     if (opt.ifaces_priv && !opt.ifaces_code) {
73         stderr.writeln("cannot output priv interface code out of ifaces mode");
74         return 1;
75     }
76 
77     if (opt.ifaces_priv && !opt.ifaces_priv_mod.empty && !opt.module_name.empty) {
78         stderr.writeln("interface priv module name must be specified either by -m"
79                         ~ " of by --ifaces_priv_mod");
80         return 1;
81     }
82 
83     if (opt.module_name.empty && opt.ifaces_priv && !opt.ifaces_priv_mod.empty) {
84         opt.module_name = opt.ifaces_priv_mod;
85     }
86 
87     if (opt.module_name.empty) {
88         stderr.writeln("D module name must be supplied with --module or -m");
89         return 1;
90     }
91 
92     try
93     {
94         File input = (opt.in_file.empty) ? stdin : File(opt.in_file, "r");
95         File output = (opt.out_file.empty) ? stdout : File(opt.out_file, "w");
96 
97         string xmlStr;
98         foreach (string l; lines(input)) {
99             xmlStr ~= l;
100         }
101 
102         Protocol p;
103         {
104             auto xmlDoc = new Document;
105             xmlDoc.parse(xmlStr, true, true);
106             p = Protocol(xmlDoc.root);
107         }
108 
109         output.printComment(
110                 "D bindings copyright:\n"       ~
111                 "Copyright © 2015 Rémi Thebault");
112         output.printComment(
113                 "    File generated by wayland-scanner-d " ~ scannerVersion ~
114                 ":\n" ~ splitLinesForWidth(opt.cmdline, "  \\", "    ", 78) ~ "\n\nDo not edit!");
115 
116         p.printOut(output, opt);
117     }
118     catch (Exception ex)
119     {
120         stderr.writeln("Error occured:\n", ex.toString());
121         return 1;
122     }
123 
124     return 0;
125 }
126 
127 
128 private:
129 
130 import arsd.dom;
131 import std.exception;
132 import std.uni;
133 import std.algorithm;
134 import std.conv;
135 import std.format;
136 import std.string;
137 
138 enum OutputMode
139 {
140     client,
141     server,
142 }
143 
144 class Options {
145     string cmdline;
146 
147     string in_file;
148     string out_file;
149     string module_name;
150 
151     OutputMode mode;
152 
153     bool protocol_code;
154     bool ifaces_code;
155     bool ifaces_priv;
156     string ifaces_priv_mod;
157 
158     string[] priv_modules;
159     string[] pub_modules;
160 }
161 
162 
163 enum ArgType {
164     Int, UInt, Fixed, String, Object, NewId, Array, Fd
165 }
166 
167 
168 struct Description {
169     string summary;
170     string text;
171 
172     this (Element parentEl) {
173         foreach (el; parentEl.getElementsByTagName("description")) {
174             summary = el.getAttribute("summary");
175             text = el.getElText();
176             break;
177         }
178     }
179 
180     bool opCast(T)() const if (is(T == bool)) {
181         return !summary.empty || !text.empty;
182     }
183 
184     void printOut(File output, int indent=0) {
185         string docStr = summary ~ "\n\n" ~ text;
186         output.printDocComment(docStr, indent);
187     }
188 }
189 
190 
191 
192 
193 
194 struct Entry
195 {
196     string iface_name;
197     string enum_name;
198     string name;
199     string value;
200     string summary;
201 
202     this (Element el, string iface_name, string enum_name) {
203         enforce(el.tagName == "entry");
204         this.iface_name = iface_name;
205         this.enum_name = enum_name;
206         name = el.getAttribute("name");
207         value = el.getAttribute("value");
208         summary = el.getAttribute("summary");
209         enforce(!value.empty, "enum entries without value aren't supported");
210     }
211 
212     @property string d_name() const {
213         return iface_name.toUpper ~ '_' ~ enum_name.toUpper ~ '_' ~ name.toUpper;
214     }
215 
216 }
217 
218 struct Enum
219 {
220     string name;
221     string iface_name;
222     Description description;
223     Entry[] entries;
224 
225     this (Element el, string iface_name) {
226         enforce(el.tagName == "enum");
227         name = el.getAttribute("name");
228         this.iface_name = iface_name;
229         description = Description(el);
230         foreach (entryEl; el.getElementsByTagName("entry")) {
231             entries ~= Entry(entryEl, iface_name, name);
232         }
233     }
234 
235     @property d_name() const {
236         return iface_name ~ "_" ~ name;
237     }
238 
239     bool entriesHaveDoc() const {
240         return entries.any!(e => !e.summary.empty);
241     }
242 
243     void printOut(File output, int indent=0)
244     {
245         if (description)
246             description.printOut(output, indent);
247 
248         immutable entriesDoc = entriesHaveDoc();
249         if (entriesDoc) {
250             string docComment;
251             foreach(e; entries) {
252                 if (e.summary.empty)
253                     docComment ~= e.d_name ~ '\n';
254                 else
255                     docComment ~= e.d_name ~ ": " ~ e.summary ~ '\n';
256             }
257             output.printDocComment(docComment, indent);
258         }
259 
260         bool first=true;
261         foreach (e; entries) {
262             string eStr = "enum uint " ~ e.d_name ~ " = " ~ e.value ~ ";";
263             if (entriesDoc && !first) {
264                 eStr ~= " /// ditto";
265             }
266             first = false;
267             output.writeln(indentStr(indent), eStr);
268         }
269     }
270 
271 }
272 
273 
274 
275 struct Arg {
276     string name;
277     string summary;
278     string iface;
279     ArgType type;
280 
281     this (Element el) {
282         enforce(el.tagName == "arg");
283         name = el.getAttribute("name");
284         summary = el.getAttribute("summary");
285         iface = el.getAttribute("interface");
286         switch (el.getAttribute("type"))
287         {
288             case "int":
289                 type = ArgType.Int;
290                 break;
291             case "uint":
292                 type = ArgType.UInt;
293                 break;
294             case "fixed":
295                 type = ArgType.Fixed;
296                 break;
297             case "string":
298                 type = ArgType.String;
299                 break;
300             case "object":
301                 type = ArgType.Object;
302                 break;
303             case "new_id":
304                 type = ArgType.NewId;
305                 break;
306             case "array":
307                 type = ArgType.Array;
308                 break;
309             case "fd":
310                 type = ArgType.Fd;
311                 break;
312             default:
313                 enforce(false, "unknown type");
314                 break;
315         }
316     }
317 
318     @property string d_type() const
319     {
320         final switch(type) {
321             case ArgType.Int:
322                 return "int ";
323             case ArgType.UInt:
324                 return "uint ";
325             case ArgType.Fixed:
326                 return "wl_fixed_t ";
327             case ArgType.String:
328                 return "const(char) *";
329             case ArgType.Object:
330                 return iface ~ " *";
331             case ArgType.NewId:
332                 return "uint ";
333             case ArgType.Array:
334                 return "wl_array *";
335             case ArgType.Fd:
336                 return "int ";
337         }
338     }
339 
340     @property string d_name() const {
341         if (name == "interface") {
342             return "iface";
343         }
344         else if (name == "version") {
345             return "ver";
346         }
347         return name;
348     }
349 
350 }
351 
352 
353 struct Message
354 {
355     string name;
356     string iface_name;
357     int since = 1;
358     bool is_dtor;
359     Description description;
360     Arg[] args;
361 
362     this (Element el, string iface_name) {
363         enforce(el.tagName == "request" || el.tagName == "event");
364         name = el.getAttribute("name");
365         this.iface_name = iface_name;
366         if (el.hasAttribute("since")) {
367             since = el.getAttribute("since").to!int;
368         }
369         is_dtor = (el.getAttribute("type") == "destructor");
370         description = Description(el);
371         foreach (argEl; el.getElementsByTagName("arg")) {
372             args ~= Arg(argEl);
373         }
374     }
375 
376     @property string opCodeSym() const {
377         return iface_name.toUpper ~ "_" ~ name.toUpper;
378     }
379 
380 
381     void printCallbackOut(File output, int indent, bool server)
382     {
383         if (description)
384             description.printOut(output, indent);
385 
386         string code = "void function (";
387         string argIndent = array(" ".replicate(code.length)).to!string;
388 
389         if (server) {
390             code ~= "wl_client *client,\n";
391             code ~= argIndent ~ "wl_resource *resource";
392         }
393         else {
394             code ~= "void *data,\n";
395             code ~= argIndent ~ format("%s *%s", iface_name, iface_name);
396         }
397 
398         foreach(arg; args) {
399             code ~= ",\n" ~ argIndent;
400 
401             if (server && arg.type == ArgType.Object)
402                 code ~= "wl_resource *";
403             else if (server && arg.type == ArgType.NewId && arg.iface.empty)
404                 code ~= "const(char) *iface, uint ver, uint ";
405             else if (!server && arg.type == ArgType.Object && arg.iface.empty)
406                 code ~= "void *";
407             else if (!server && arg.type == ArgType.NewId)
408                 code ~= arg.iface ~ " *";
409             else
410                 code ~= arg.d_type;
411 
412             code ~= arg.d_name;
413         }
414 
415         code ~= format(") %s;", name);
416 
417         output.printCode(code, indent);
418     }
419 
420 
421     void printStubOut(File output, int indent)
422     {
423         Arg *ret;
424         foreach (i; 0..args.length) {
425             if (args[i].type == ArgType.NewId) {
426                 if (ret) {
427                     stderr.writefln(
428                         "message '%s.%s' has more than one new_id arg\n" ~
429                         "not emitting stub", iface_name, name);
430                     return;
431                 }
432                 ret = &args[i];
433             }
434         }
435 
436         foreach(arg; args) {
437             enforce (!arg.iface.empty || arg.type != ArgType.Object,
438                 format("%s is object and has no interface defined", arg.name));
439         }
440 
441         string rettype = "void";
442         if (ret) {
443             if (ret.iface.empty) {
444                 rettype = "void *";
445             }
446             else {
447                 rettype = format("%s *", ret.iface);
448             }
449         }
450 
451         string code = format(
452                 "extern (D) %s\n" ~
453                 "%s_%s(%s *%s_", rettype, iface_name, name,
454                 iface_name, iface_name);
455         foreach (arg; args) {
456             if (arg.type == ArgType.NewId) {
457                 if (arg.iface.empty)
458                     code ~= ", const(wl_interface) *iface, uint ver";
459             }
460             else {
461                 code ~= format(", %s%s", arg.d_type, arg.d_name);
462             }
463         }
464 
465         code ~= ")\n{\n";
466 
467         if (ret) {
468             code ~= format("    wl_proxy *%s;\n\n", ret.d_name);
469             code ~= format("    %s = wl_proxy_marshal_constructor(\n", ret.d_name);
470             code ~= format("            cast(wl_proxy*) %s_,\n", iface_name);
471             code ~= format("            %s, ", opCodeSym);
472             if (ret.iface.empty)
473                 code ~= "iface";
474             else
475                 code ~= format("%s_interface", ret.iface);
476         }
477         else {
478             code ~= format("    wl_proxy_marshal(cast(wl_proxy*) %s_,\n", iface_name);
479             code ~= format("            %s", opCodeSym);
480         }
481 
482         foreach (arg; args) {
483             if (arg.type == ArgType.NewId) {
484                 if (arg.iface.empty) {
485                     code ~= ", iface.name, ver";
486                 }
487                 code ~= ", null";
488             }
489             else {
490                 code ~= format(", %s", arg.d_name);
491             }
492         }
493         code ~= ");\n";
494 
495         if (is_dtor) {
496             code ~= format("\n    wl_proxy_destroy(cast(wl_proxy*) %s_);\n", iface_name);
497         }
498 
499         if (ret) {
500             if (ret.iface.empty)
501                 code ~= format("\n    return cast(void *) %s;\n", ret.d_name);
502             else
503                 code ~= format("\n    return cast(%s *) %s;\n",
504                                 ret.iface, ret.d_name);
505         }
506 
507         code ~= "}";
508 
509         if (description)
510             description.printOut(output, indent);
511         output.printCode(code, indent);
512     }
513 
514 
515     void printEventWrapperOut(File output, int indent)
516     {
517         string code = format(
518                 "extern (D) void\n" ~
519                 "%s_send_%s(wl_resource *res", iface_name, name);
520 
521         foreach (arg; args) {
522             code ~= ", ";
523             switch(arg.type) {
524             case ArgType.NewId:
525             case ArgType.Object:
526                 code ~= "wl_resource *";
527                 break;
528             default:
529                 code ~= arg.d_type;
530                 break;
531             }
532             code ~= arg.d_name;
533         }
534 
535         code ~= format(")\n"    ~
536             "{\n"               ~
537             "    wl_resource_post_event(res, %s", opCodeSym);
538         foreach (arg; args) {
539             code ~= format(", %s", arg.d_name);
540         }
541         code ~= ");\n}";
542 
543         if (description) description.printOut(output, indent);
544         output.printCode(code, indent);
545     }
546 
547 }
548 
549 
550 struct Interface
551 {
552     string name;
553     string ver;
554     Description description;
555     Message[] requests;
556     Message[] events;
557     Enum[] enums;
558 
559     this (Element el) {
560         enforce(el.tagName == "interface");
561         name = el.getAttribute("name");
562         ver = el.getAttribute("version");
563         description = Description(el);
564         foreach (rqEl; el.getElementsByTagName("request")) {
565             requests ~= Message(rqEl, name);
566         }
567         foreach (evEl; el.getElementsByTagName("event")) {
568             events ~= Message(evEl, name);
569         }
570         foreach (enEl; el.getElementsByTagName("enum")) {
571             enums ~= Enum(enEl, name);
572         }
573     }
574 
575 
576     @property bool haveListener() const {
577         return !events.empty;
578     }
579 
580     @property bool haveInterface() const {
581         return !requests.empty;
582     }
583 
584     void printOutEnumCode(File output, int indent)
585     {
586         foreach (e; enums) {
587             e.printOut(output, indent);
588             output.writeln();
589         }
590     }
591 
592 
593     void printOutClientCode(File output, int indent)
594     {
595         if (haveListener) {
596             // listener struct
597             if (description) description.printOut(output, indent);
598             output.printCode(format("struct %s_listener\n{", name), indent);
599             foreach (ev; events) {
600                 ev.printCallbackOut(output, indent+1, false);
601             }
602             output.printCode("}", indent);
603             output.writeln();
604 
605             // add listener method
606             output.printCode(format(
607                 "extern (D) int\n"                                              ~
608                 "%s_add_listener(%s *%s,\n"                                     ~
609                 "                const(%s_listener) *listener, void *data)\n"   ~
610                 "{\n"                                                           ~
611                 "    alias Callback = extern (C) void function();\n\n"          ~
612                 "    return wl_proxy_add_listener(\n"                           ~
613                 "            cast(wl_proxy*)%s,\n"                              ~
614                 "            cast(Callback*)listener, data);\n"                 ~
615                 "}",
616                 name, name, name, name, name), indent);
617             output.writeln();
618         }
619 
620         // opcodes
621         foreach (i, rq; requests) {
622             output.printCode(format("enum uint %s = %s;",
623                                 rq.opCodeSym, i),
624                                 indent);
625         }
626         output.writeln();
627 
628         // write user data getter and setter
629         output.printCode(format(
630             "extern (D) void\n"                                             ~
631             "%s_set_user_data(%s *%s, void *user_data)\n"                   ~
632             "{\n"                                                           ~
633             "    wl_proxy_set_user_data(cast(wl_proxy*) %s, user_data);\n"  ~
634             "}", name, name, name, name), indent);
635         output.writeln();
636         output.printCode(format(
637             "extern (D) void *\n"                                       ~
638             "%s_get_user_data(%s *%s)\n"                                ~
639             "{\n"                                                       ~
640             "    return wl_proxy_get_user_data(cast(wl_proxy*) %s);\n"  ~
641             "}", name, name, name, name), indent);
642         output.writeln();
643 
644         bool hasDtor=false;
645         bool hasDestroy=false;
646         foreach (rq; requests) {
647             if (rq.is_dtor) hasDtor = true;
648             if (rq.name == "destroy") hasDestroy = true;
649         }
650         enforce(hasDtor || !hasDestroy);
651 
652         if (!hasDestroy && name != "wl_display") {
653             output.printCode(format(
654                 "extern (D) void\n"                             ~
655                 "%s_destroy(%s *%s)\n"                          ~
656                 "{\n"                                           ~
657                 "    wl_proxy_destroy(cast(wl_proxy*) %s);\n"   ~
658                 "}", name, name, name, name), indent);
659             output.writeln();
660         }
661 
662         foreach(rq; requests) {
663             rq.printStubOut(output, indent);
664             output.writeln();
665         }
666     }
667 
668 
669     void printOutServerCode(File output, int indent)
670     {
671         if (haveInterface) {
672             // interface struct
673             if (description) description.printOut(output, indent);
674             output.printCode(format("struct %s_interface\n{", name), indent);
675             foreach (rq; requests) {
676                 rq.printCallbackOut(output, indent+1, true);
677             }
678             output.printCode("}", indent);
679             output.writeln();
680         }
681 
682         // opcodes
683         foreach (i, ev; events) {
684             output.printCode(format("enum uint %s = %s;",
685                                 ev.opCodeSym, i),
686                                 indent);
687         }
688         output.writeln();
689 
690         // versions
691         foreach (i, ev; events) {
692             output.printCode(format("enum uint %s_SINCE_VERSION = %s;",
693                                 ev.opCodeSym, ev.since),
694                                 indent);
695         }
696         output.writeln();
697 
698         if (name != "wl_display") {
699             // wl_display have hand coded functions
700             foreach (ev; events) {
701                 ev.printEventWrapperOut(output, indent);
702                 output.writeln();
703             }
704         }
705 
706     }
707 
708 
709     void printProtocolOut(File output, Options opt, int indent=0)
710     {
711         printOutEnumCode(output, indent);
712 
713         final switch (opt.mode)
714         {
715             case OutputMode.client:
716                 printOutClientCode(output, indent);
717                 break;
718             case OutputMode.server:
719                 printOutServerCode(output, indent);
720                 break;
721         }
722     }
723 
724 }
725 
726 
727 struct Protocol
728 {
729     string name;
730     string copyright;
731     Interface[] ifaces;
732 
733     this(Element el) {
734         enforce(el.tagName == "protocol");
735         name = el.getAttribute("name");
736         foreach (cr; el.getElementsByTagName("copyright")) {
737             copyright = cr.getElText();
738             break;
739         }
740         foreach (ifEl; el.getElementsByTagName("interface")) {
741             ifaces ~= Interface(ifEl);
742         }
743     }
744 
745     Enum[] getEnums() {
746         Enum[] enums;
747         foreach (iface; ifaces) {
748             enums ~= iface.enums;
749         }
750         return enums;
751     }
752 
753 
754     void printLoaderOut(File output, Options opt, int indent)
755     {
756         string mode = (opt.mode == OutputMode.server) ? "Server" : "Client";
757 
758         string code = "import derelict.util.loader;\n\n";
759 
760         code ~= format("class %s%sLoader : SharedLibLoader {\n",
761             name.capitalize, mode);
762 
763         code ~=
764             "    this (string libName) {\n" ~
765             "        super(libName);\n"     ~
766             "    }\n";
767 
768         code ~= "    protected override void loadSymbols() {\n";
769         foreach(iface; ifaces) {
770             code ~= "        " ~ format(
771                 "%s_if_ptr = cast(wl_interface*) loadSymbol(\"%s_interface\");\n",
772                 iface.name, iface.name);
773         }
774         code ~= "    }\n";
775         code ~= "}";
776 
777         output.printCode(code, indent);
778     }
779 
780 
781     void printOut(File output, Options opt) {
782         if (!copyright.empty) {
783             output.printComment("Protocol copyright:\n"~copyright);
784         }
785 
786         output.writeln("module ", opt.module_name, ";\n");
787 
788         string mode = (opt.mode == OutputMode.server) ? "server" : "client";
789 
790         output.writeln(format("import wayland.%s.util;", mode));
791         output.writeln(format("import wayland.%s.opaque_types;", mode));
792         foreach (mod; opt.priv_modules)
793             output.writeln("import ", mod, ";");
794         foreach (mod; opt.pub_modules)
795             output.writeln("public import ", mod, ";");
796         output.writeln();
797 
798         if (opt.protocol_code) {
799             output.writeln("extern (C) {\n");
800 
801             foreach (iface; ifaces) {
802                 if (iface.name == "wl_display") continue;
803                 output.printCode(format("struct %s;", iface.name), 1);
804             }
805             output.writeln();
806 
807             foreach (iface; ifaces)
808             {
809                 iface.printProtocolOut(output, opt, 1);
810             }
811 
812             output.writeln("\n} // extern (C)\n");
813         }
814 
815         if (opt.ifaces_code) {
816             if (opt.ifaces_priv) {
817                 output.writeln("extern (C) {\n");
818 
819                 output.printCode("version (Dynamic) {}", 1);
820                 output.printCode("else {", 1);
821                 foreach (iface; ifaces) {
822                     output.printCode(format(
823                         "extern __gshared wl_interface %s_interface;",
824                         iface.name), 2);
825                 }
826                 output.printCode("}", 1);
827 
828                 output.writeln("\n} // extern (C)\n");
829             }
830             else {
831                 output.writeln("version (Dynamic) {\n");
832                 output.printCode("private {", 1);
833                 foreach(iface; ifaces) {
834                     output.printCode(format(
835                         "__gshared wl_interface *%s_if_ptr;",
836                         iface.name), 2);
837                 }
838                 output.printCode("}", 1);
839                 output.writeln();
840 
841                 output.printCode("package {", 1);
842                 printLoaderOut(output, opt, 2);
843 
844                 output.printCode("}", 1);
845                 output.writeln();
846 
847                 foreach (iface; ifaces) {
848                     string code = format(
849                         "@property wl_interface *%s_interface() {\n"    ~
850                         "    return %s_if_ptr;\n"                       ~
851                         "}", iface.name, iface.name);
852                     output.printCode(code, 1);
853                 }
854 
855                 output.printCode("\n}\nelse {\n", 0);
856 
857                 output.printCode(format(
858                         "import priv = %s;",
859                         opt.ifaces_priv_mod), 1);
860                 output.writeln();
861 
862                 foreach (iface; ifaces) {
863                     string code = format(
864                         "@property wl_interface *%s_interface() {\n"    ~
865                         "    return &priv.%s_interface;\n"              ~
866                         "}", iface.name, iface.name);
867                     output.printCode(code, 1);
868                 }
869 
870                 output.printCode("\n}", 0);
871             }
872         }
873     }
874 }
875 
876 
877 string getElText(Element el)
878 {
879     string fulltxt;
880     foreach (child; el.children) {
881         if (child.nodeType == NodeType.Text) {
882             fulltxt ~= child.nodeValue;
883         }
884     }
885 
886     string[] lines;
887     string offset;
888     bool offsetdone = false;
889     foreach (l; fulltxt.split('\n')) {
890         immutable bool allwhite = l.all!isWhite;
891         if (!offsetdone && allwhite) continue;
892 
893         if (!offsetdone && !allwhite) {
894             offsetdone = true;
895             offset = l
896                     .until!(c => !c.isWhite)
897                     .to!string;
898         }
899 
900         if (l.startsWith(offset)) {
901             l = l[offset.length .. $];
902         }
903 
904         lines ~= l;
905     }
906 
907     foreach_reverse(l; lines) {
908         if (l.all!isWhite) {
909             lines = lines[0..$-1];
910         }
911         else break;
912     }
913 
914     return lines.join("\n");
915 }
916 
917 
918 void printComment(File output, string text, int indent=0)
919 {
920     auto indStr = indentStr(indent);
921     output.writeln(indStr, "/+");
922     foreach (l; text.split("\n")) {
923         if (l.empty) output.writeln(indStr, " +");
924         else output.writeln(indStr, " +  ", l);
925     }
926     output.writeln(indStr, " +/");
927 }
928 
929 
930 void printDocComment(File output, string text, int indent=0)
931 {
932     auto indStr = indentStr(indent);
933     output.writeln(indStr, "/++");
934     foreach (l; text.split("\n")) {
935         if (l.empty) output.writeln(indStr, " +");
936         else output.writeln(indStr, " +  ", l);
937     }
938     output.writeln(indStr, " +/");
939 }
940 
941 
942 /++
943  + prints indented code and adds a final '\n'
944  +/
945 void printCode(File output, string code, int indent=0)
946 {
947     auto iStr = indentStr(indent);
948     foreach (l; code.split("\n")) {
949         if (l.empty) output.writeln();
950         else output.writeln(iStr, l);
951     }
952 }
953 
954 
955 string indentStr(int indent)
956 {
957     return "    ".replicate(indent);
958 }
959 
960 
961 string splitLinesForWidth(string input, in string suffix, in string indent, in size_t width=80)
962 {
963     string res;
964     size_t w;
965     foreach(i, word; input.split(" "))
966     {
967         if (i != 0)
968         {
969             string spacer = " ";
970             if (w + word.length + suffix.length >= width)
971             {
972                 spacer = suffix ~ "\n" ~ indent;
973                 w = indent.length;
974             }
975             res ~= spacer;
976         }
977         res ~= word;
978         w += word.length;
979     }
980     return res;
981 }