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 }