1 /* 2 * This file is part of gir-to-d. 3 * 4 * gir-to-d 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. 8 * 9 * gir-to-d is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public License 15 * along with gir-to-d. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 module gtd.GirWrapper; 19 20 import std.algorithm; 21 import std.array; 22 import std.file; 23 import std.uni; 24 import std.path; 25 import std.stdio; 26 import std.string; 27 28 import gtd.DefReader; 29 import gtd.GirField; 30 import gtd.GirFunction; 31 import gtd.GirPackage; 32 import gtd.GirStruct; 33 import gtd.GirType; 34 import gtd.GirVersion; 35 import gtd.GlibTypes; 36 import gtd.IndentedStringBuilder; 37 import gtd.Log; 38 39 enum PrintFileMethod 40 { 41 Absolute, 42 Relative, 43 Default 44 } 45 46 class GirWrapper 47 { 48 bool includeComments = true; 49 bool useRuntimeLinker; 50 bool useBindDir; 51 52 bool printFiles; 53 PrintFileMethod printFileMethod = PrintFileMethod.Default; 54 string cwdOrBaseDirectory; 55 56 string inputDir; 57 string outputDir; 58 string srcDir = "./"; 59 string commandlineGirPath; 60 61 static string licence; 62 static string[string] aliasses; 63 64 static GirPackage[string] packages; 65 66 public this(string inputDir, string outputDir) 67 { 68 this.inputDir = inputDir; 69 this.outputDir = outputDir; 70 } 71 72 void proccess(string lookupFileName) 73 { 74 if ( !exists(buildPath(inputDir, lookupFileName)) ) 75 error(lookupFileName, " not found, check '--help' for more information."); 76 77 DefReader defReader = new DefReader( buildPath(inputDir, lookupFileName) ); 78 79 proccess(defReader); 80 } 81 82 void proccess(DefReader defReader, GirPackage currentPackage = null, bool isDependency = false, GirStruct currentStruct = null) 83 { 84 while ( !defReader.empty ) 85 { 86 if ( !currentPackage && defReader.key.among( 87 "addAliases", "addConstants", "addEnums", "addFuncts", "addStructs", "file", "move", 88 "struct", "class", "interface", "namespace", "noAlias", "noConstant", "noEnum", "noCallback") ) 89 error("Found: '", defReader.key, "' before wrap.", defReader); 90 91 if ( !currentStruct && defReader.key.among( 92 "code", "cType", "extend", "implements", "import", "interfaceCode", "merge", 93 "noCode", "noExternal", "noProperty", "noSignal", "noStruct", "override", "structWrap", 94 "array", "in", "out", "inout", "ref") ) 95 error("Found: '", defReader.key, "' without an active struct.", defReader); 96 97 switch ( defReader.key ) 98 { 99 //Toplevel keys. 100 case "bindDir": 101 warning("Don't use bindDir, it is no longer used since the c definitions have moved.", defReader); 102 break; 103 case "includeComments": 104 includeComments = defReader.valueBool; 105 break; 106 case "inputRoot": 107 warning("Don't use inputRoot, it has been removed as it was never implemented.", defReader); 108 break; 109 case "license": 110 licence = defReader.readBlock().join(); 111 break; 112 case "outputRoot": 113 if ( outputDir == "./out" ) 114 outputDir = defReader.value; 115 break; 116 117 //Global keys. 118 case "alias": 119 if ( currentStruct ) 120 loadAA(currentStruct.aliases, defReader); 121 else 122 loadAA(aliasses, defReader); 123 break; 124 case "copy": 125 try 126 copyFiles(inputDir, buildPath(outputDir, srcDir), defReader.value); 127 catch(FileException ex) 128 error(ex.msg, defReader); 129 break; 130 case "dependency": 131 loadDependency(defReader); 132 break; 133 case "lookup": 134 DefReader reader = new DefReader( buildPath(inputDir, defReader.value) ); 135 136 proccess(reader, currentPackage, isDependency, currentStruct); 137 break; 138 case "srcDir": 139 srcDir = defReader.value; 140 break; 141 case "version": 142 if ( defReader.value == "end" ) 143 break; 144 145 if ( defReader.subKey.empty ) 146 error("No version specified.", defReader); 147 148 bool parseVersion = checkOsVersion(defReader.subKey); 149 bool smalerThen = false; 150 151 if ( defReader.subKey.length > 1 && defReader.subKey[0] == '<' && defReader.subKey[1].isNumber() ) 152 { 153 smalerThen = true; 154 defReader.subKey.popFront(); 155 } 156 157 if ( !parseVersion && defReader.subKey[0].isNumber() ) 158 { 159 if ( !currentPackage ) 160 error("Only use OS versions before wrap.", defReader); 161 parseVersion = defReader.subKey <= currentPackage._version; 162 163 if ( smalerThen ) 164 parseVersion = !parseVersion; 165 } 166 167 if ( defReader.value == "start" ) 168 { 169 if ( parseVersion ) 170 break; 171 172 defReader.skipBlock(); 173 } 174 175 if ( !parseVersion ) 176 break; 177 178 size_t index = defReader.value.indexOf(':'); 179 defReader.key = defReader.value[0 .. max(index, 0)].strip(); 180 defReader.value = defReader.value[index +1 .. $].strip(); 181 182 if ( !defReader.key.empty ) 183 continue; 184 185 break; 186 case "wrap": 187 if ( isDependency ) 188 { 189 currentPackage.name = defReader.value; 190 break; 191 } 192 193 if ( outputDir.empty ) 194 error("Found wrap while outputRoot isn't set", defReader); 195 if (defReader.value in packages) 196 error("Package '", defReader.value, "' is already defined.", defReader); 197 198 currentStruct = null; 199 currentPackage = new GirPackage(defReader.value, this, srcDir); 200 packages[defReader.value] = currentPackage; 201 break; 202 203 //Package keys 204 case "addAliases": 205 currentPackage.lookupAliases ~= defReader.readBlock(); 206 break; 207 case "addConstants": 208 currentPackage.lookupConstants ~= defReader.readBlock(); 209 break; 210 case "addEnums": 211 currentPackage.lookupEnums ~= defReader.readBlock(); 212 break; 213 case "addFuncts": 214 currentPackage.lookupFuncts ~= defReader.readBlock(); 215 break; 216 case "addStructs": 217 currentPackage.lookupStructs ~= defReader.readBlock(); 218 break; 219 case "file": 220 if ( !isAbsolute(defReader.value) ) 221 { 222 currentPackage.parseGIR(getAbsoluteGirPath(defReader.value)); 223 } 224 else 225 { 226 warning("Don't use absolute paths for specifying gir files.", defReader); 227 228 currentPackage.parseGIR(defReader.value); 229 } 230 break; 231 case "move": 232 string[] vals = defReader.value.split(); 233 if ( vals.length <= 1 ) 234 error("No destination for move: ", defReader.value, defReader); 235 string newFuncName = ( vals.length == 3 ) ? vals[2] : vals[0]; 236 GirStruct dest = currentPackage.getStruct(vals[1]); 237 if ( dest is null ) 238 dest = createClass(currentPackage, vals[1]); 239 240 if ( currentStruct && vals[0] in currentStruct.functions ) 241 { 242 currentStruct.functions[vals[0]].strct = dest; 243 dest.functions[newFuncName] = currentStruct.functions[vals[0]]; 244 dest.functions[newFuncName].name = newFuncName; 245 if ( newFuncName.startsWith("new") ) 246 dest.functions[newFuncName].type = GirFunctionType.Constructor; 247 if ( currentStruct.virtualFunctions.canFind(vals[0]) ) 248 dest.virtualFunctions ~= newFuncName; 249 currentStruct.functions.remove(vals[0]); 250 } 251 else if ( vals[0] in currentPackage.collectedFunctions ) 252 { 253 currentPackage.collectedFunctions[vals[0]].strct = dest; 254 dest.functions[newFuncName] = currentPackage.collectedFunctions[vals[0]]; 255 dest.functions[newFuncName].name = newFuncName; 256 currentPackage.collectedFunctions.remove(vals[0]); 257 } 258 else 259 error("Unknown function ", vals[0], defReader); 260 break; 261 case "noAlias": 262 currentPackage.collectedAliases.remove(defReader.value); 263 break; 264 case "noConstant": 265 currentPackage.collectedConstants.remove(defReader.value); 266 break; 267 case "noEnum": 268 currentPackage.collectedEnums.remove(defReader.value); 269 break; 270 case "noCallback": 271 currentPackage.collectedCallbacks.remove(defReader.value); 272 break; 273 case "struct": 274 if ( defReader.value.empty ) 275 { 276 currentStruct = null; 277 } 278 else 279 { 280 currentStruct = currentPackage.getStruct(defReader.value); 281 if ( currentStruct is null ) 282 currentStruct = createClass(currentPackage, defReader.value); 283 } 284 break; 285 286 //Struct keys. 287 case "array": 288 string[] vals = defReader.value.split(); 289 290 if ( vals[0] in currentStruct.functions ) 291 { 292 GirFunction func = currentStruct.functions[vals[0]]; 293 294 if ( vals[1] == "Return" ) 295 { 296 if ( vals.length < 3 ) 297 { 298 func.returnType.zeroTerminated = true; 299 break; 300 } 301 302 GirType elementType = new GirType(this); 303 304 elementType.name = func.returnType.name; 305 elementType.cType = func.returnType.cType[0..$-1]; 306 func.returnType.elementType = elementType; 307 func.returnType.girArray = true; 308 309 foreach( i, p; func.params ) 310 { 311 if ( p.name == vals[2] ) 312 func.returnType.length = cast(int)i; 313 } 314 } 315 else 316 { 317 GirParam param = findParam(currentStruct, vals[0], vals[1]); 318 GirType elementType = new GirType(this); 319 320 if ( !param ) 321 error("Unknown parameter ", vals[1], " in function ", vals[0]); 322 323 elementType.name = param.type.name; 324 elementType.cType = param.type.cType[0..$-1]; 325 param.type.elementType = elementType; 326 param.type.girArray = true; 327 328 if ( vals.length < 3 ) 329 { 330 param.type.zeroTerminated = true; 331 break; 332 } 333 334 if ( vals[2] == "Return" ) 335 { 336 param.type.length = -2; 337 break; 338 } 339 340 foreach( i, p; func.params ) 341 { 342 if ( p.name == vals[2] ) 343 param.type.length = cast(int)i; 344 } 345 } 346 } 347 else if ( currentStruct.fields.map!(a => a.name).canFind(vals[0]) ) 348 { 349 GirField arrayField; 350 int lengthID = -1; 351 352 foreach ( size_t i, field; currentStruct.fields ) 353 { 354 if ( field.name == vals[0] ) 355 arrayField = field; 356 else if ( field.name == vals[1] ) 357 lengthID = cast(int)i; 358 359 if ( arrayField && lengthID > -1 ) 360 break; 361 } 362 363 arrayField.type.length = lengthID; 364 currentStruct.fields[lengthID].isLength = true; 365 366 GirType elementType = new GirType(this); 367 elementType.name = arrayField.type.name; 368 elementType.cType = arrayField.type.cType[0..$-1]; 369 arrayField.type.elementType = elementType; 370 arrayField.type.girArray = true; 371 } 372 else 373 { 374 error("Field or function: `", vals[0], "' is unknown.", defReader); 375 } 376 break; 377 case "class": 378 if ( currentStruct is null ) 379 currentStruct = createClass(currentPackage, defReader.value); 380 381 currentStruct.lookupClass = true; 382 currentStruct.name = defReader.value; 383 break; 384 case "code": 385 currentStruct.lookupCode ~= defReader.readBlock; 386 break; 387 case "cType": 388 currentStruct.cType = defReader.value; 389 break; 390 case "extend": 391 currentStruct.lookupParent = true; 392 currentStruct.parent = defReader.value; 393 break; 394 case "implements": 395 if ( defReader.value.empty ) 396 currentStruct.implements = null; 397 else 398 currentStruct.implements ~= defReader.value; 399 break; 400 case "import": 401 currentStruct.imports ~= defReader.value; 402 break; 403 case "interface": 404 if ( currentStruct is null ) 405 currentStruct = createClass(currentPackage, defReader.value); 406 407 currentStruct.lookupInterface = true; 408 currentStruct.name = defReader.value; 409 break; 410 case "interfaceCode": 411 currentStruct.lookupInterfaceCode ~= defReader.readBlock; 412 break; 413 case "merge": 414 GirStruct mergeStruct = currentPackage.getStruct(defReader.value); 415 currentStruct.merge(mergeStruct); 416 GirStruct copy = currentStruct.dup(); 417 copy.noCode = true; 418 copy.noExternal = true; 419 mergeStruct.pack.collectedStructs[defReader.value] = copy; 420 break; 421 case "namespace": 422 currentStruct.type = GirStructType.Record; 423 currentStruct.lookupClass = false; 424 currentStruct.lookupInterface = false; 425 426 if ( defReader.value.empty ) 427 { 428 currentStruct.noNamespace = true; 429 } 430 else 431 { 432 currentStruct.noNamespace = false; 433 currentStruct.name = defReader.value; 434 } 435 break; 436 case "noCode": 437 if ( defReader.valueBool ) 438 { 439 currentStruct.noCode = true; 440 break; 441 } 442 if ( defReader.value !in currentStruct.functions ) 443 error("Unknown function ", defReader.value, ". Possible values: ", currentStruct.functions.keys, defReader); 444 445 currentStruct.functions[defReader.value].noCode = true; 446 break; 447 case "noExternal": 448 currentStruct.noExternal = true; 449 break; 450 case "noProperty": 451 foreach ( field; currentStruct.fields ) 452 { 453 if ( field.name == defReader.value ) 454 { 455 field.noProperty = true; 456 break; 457 } 458 else if ( field == currentStruct.fields.back ) 459 error("Unknown field ", defReader.value, defReader); 460 } 461 break; 462 case "noSignal": 463 currentStruct.functions[defReader.value~"-signal"].noCode = true; 464 break; 465 case "noStruct": 466 currentStruct.noDeclaration = true; 467 break; 468 case "structWrap": 469 loadAA(currentStruct.structWrap, defReader); 470 break; 471 472 //Function keys 473 case "in": 474 string[] vals = defReader.value.split(); 475 if ( vals[0] !in currentStruct.functions ) 476 error("Unknown function ", vals[0], ". Possible values: ", currentStruct.functions, defReader); 477 GirParam param = findParam(currentStruct, vals[0], vals[1]); 478 if ( !param ) 479 error("Unknown parameter ", vals[1], " in function ", vals[0]); 480 param.direction = GirParamDirection.Default; 481 break; 482 case "out": 483 string[] vals = defReader.value.split(); 484 if ( vals[0] !in currentStruct.functions ) 485 error("Unknown function ", vals[0], ". Possible values: ", currentStruct.functions, defReader); 486 GirParam param = findParam(currentStruct, vals[0], vals[1]); 487 if ( !param ) 488 error("Unknown parameter ", vals[1], " in function ", vals[0]); 489 param.direction = GirParamDirection.Out; 490 break; 491 case "override": 492 currentStruct.functions[defReader.value].lookupOverride = true; 493 break; 494 case "inout": 495 case "ref": 496 string[] vals = defReader.value.split(); 497 if ( vals[0] !in currentStruct.functions ) 498 error("Unknown function ", vals[0], ". Possible values: ", currentStruct.functions, defReader); 499 GirParam param = findParam(currentStruct, vals[0], vals[1]); 500 if ( !param ) 501 error("Unknown parameter ", vals[1], " in function ", vals[0]); 502 param.direction = GirParamDirection.InOut; 503 break; 504 505 default: 506 error("Unknown key: ", defReader.key, defReader); 507 } 508 509 defReader.popFront(); 510 } 511 } 512 513 void proccessGIR(string girFile) 514 { 515 GirPackage pack = new GirPackage("", this, srcDir); 516 517 if ( !isAbsolute(girFile) ) 518 { 519 girFile = getAbsoluteGirPath(girFile); 520 } 521 522 pack.parseGIR(girFile); 523 packages[pack.name] = pack; 524 } 525 526 void printFreeFunctions() 527 { 528 foreach ( pack; packages ) 529 { 530 foreach ( func; pack.collectedFunctions ) 531 { 532 if ( func.movedTo.empty ) 533 writefln("%s: %s", pack.name, func.name); 534 } 535 } 536 } 537 538 void writeFile(string fileName, string contents, bool createDirectory = false) 539 { 540 if ( createDirectory ) 541 { 542 try 543 { 544 if ( !exists(fileName.dirName()) ) 545 mkdirRecurse(fileName.dirName()); 546 } 547 catch (FileException ex) 548 { 549 error("Failed to create directory: ", ex.msg); 550 } 551 } 552 553 std.file.write(fileName, contents); 554 555 if ( printFiles ) 556 printFilePath(fileName); 557 } 558 559 string getAbsoluteGirPath(string girFile) 560 { 561 if ( commandlineGirPath ) 562 { 563 string cmdGirFile = buildNormalizedPath(commandlineGirPath, girFile); 564 565 if ( exists(cmdGirFile) ) 566 return cmdGirFile; 567 } 568 569 return buildNormalizedPath(getGirDirectory(), girFile); 570 } 571 572 private void printFilePath(string fileName) 573 { 574 with (PrintFileMethod) switch(printFileMethod) 575 { 576 case Absolute: 577 writeln(asAbsolutePath(fileName)); 578 break; 579 case Relative: 580 writeln(asRelativePath(asAbsolutePath(fileName), cwdOrBaseDirectory)); 581 break; 582 default: 583 writeln(fileName); 584 break; 585 } 586 } 587 588 private string getGirDirectory() 589 { 590 version(Windows) 591 { 592 import std.process : environment; 593 594 static string path; 595 596 if (path !is null) 597 return path; 598 599 foreach (p; splitter(environment.get("PATH"), ';')) 600 { 601 string dllPath = buildNormalizedPath(p, "libgtk-3-0.dll"); 602 603 if ( exists(dllPath) ) 604 path = p.buildNormalizedPath("../share/gir-1.0"); 605 } 606 607 return path; 608 } 609 else version(OSX) 610 { 611 import std.process : environment; 612 613 static string path; 614 615 if (path !is null) 616 return path; 617 618 path = environment.get("GTK_BASEPATH"); 619 if(path) 620 { 621 path = path.buildNormalizedPath("../share/gir-1.0"); 622 } 623 else 624 { 625 path = environment.get("HOMEBREW_ROOT"); 626 if(path) 627 { 628 path = path.buildNormalizedPath("share/gir-1.0"); 629 } 630 } 631 632 return path; 633 } 634 else 635 { 636 return "/usr/share/gir-1.0"; 637 } 638 } 639 640 private GirParam findParam(GirStruct strct, string func, string name) 641 { 642 foreach( param; strct.functions[func].params ) 643 { 644 if ( param.name == name ) 645 return param; 646 } 647 648 return null; 649 } 650 651 private void loadAA (ref string[string] aa, const DefReader defReader) 652 { 653 string[] vals = defReader.value.split(); 654 655 if ( vals.length == 1 ) 656 vals ~= ""; 657 658 if ( vals.length == 2 ) 659 aa[vals[0]] = vals[1]; 660 else 661 error("Worng amount of arguments for key: ", defReader.key, defReader); 662 } 663 664 private void loadDependency(DefReader defReader) 665 { 666 if ( defReader.value == "end" ) 667 return; 668 669 if ( defReader.subKey.empty ) 670 error("No dependency specified.", defReader); 671 672 GirInclude inc = GirPackage.includes.get(defReader.subKey, GirInclude.init); 673 674 if ( defReader.value == "skip" ) 675 inc.skip = true; 676 else if ( defReader.value == "start" ) 677 { 678 inc.lookupFile = defReader.fileName; 679 inc.lookupLine = defReader.lineNumber; 680 681 inc.lookupText = defReader.readBlock(); 682 } 683 else 684 error("Missing 'skip' or 'start' for dependency: ", defReader.subKey, defReader); 685 686 GirPackage.includes[defReader.subKey] = inc; 687 } 688 689 private void copyFiles(string srcDir, string destDir, string file) 690 { 691 string from = buildNormalizedPath(srcDir, file); 692 string to = buildNormalizedPath(destDir, file); 693 694 if ( !printFiles ) 695 writefln("copying file [%s] to [%s]", from, to); 696 697 if ( isFile(from) ) 698 { 699 if ( printFiles ) 700 writeln(to); 701 702 copy(from, to); 703 return; 704 } 705 706 void copyDir(string from, string to) 707 { 708 if ( !exists(to) ) 709 mkdirRecurse(to); 710 711 foreach ( entry; dirEntries(from, SpanMode.shallow) ) 712 { 713 string dst = buildPath(to, entry.name.baseName); 714 715 if ( isDir(entry.name) ) 716 { 717 copyDir(entry.name, dst); 718 } 719 else 720 { 721 if ( printFiles && !dst.endsWith("functions-runtime.d") && !dst.endsWith("functions-compiletime.d") ) 722 printFilePath(dst); 723 724 copy(entry.name, dst); 725 } 726 } 727 } 728 729 copyDir(from, to); 730 731 if ( file == "cairo" ) 732 { 733 if ( printFiles ) 734 printFilePath(buildNormalizedPath(to, "c", "functions.d")); 735 736 if ( useRuntimeLinker ) 737 copy(buildNormalizedPath(to, "c", "functions-runtime.d"), buildNormalizedPath(to, "c", "functions.d")); 738 else 739 copy(buildNormalizedPath(to, "c", "functions-compiletime.d"), buildNormalizedPath(to, "c", "functions.d")); 740 741 remove(buildNormalizedPath(to, "c", "functions-runtime.d")); 742 remove(buildNormalizedPath(to, "c", "functions-compiletime.d")); 743 } 744 } 745 746 private GirStruct createClass(GirPackage pack, string name) 747 { 748 GirStruct strct = new GirStruct(this, pack); 749 strct.name = name; 750 strct.cType = pack.cTypePrefix ~ name; 751 strct.type = GirStructType.Record; 752 strct.noDeclaration = true; 753 pack.collectedStructs["lookup"~name] = strct; 754 755 return strct; 756 } 757 758 private bool checkOsVersion(string _version) 759 { 760 if ( _version.empty || !(_version[0].isAlpha() || _version[0] == '!') ) 761 return false; 762 763 version(Windows) 764 { 765 return _version.among("Windows", "!OSX", "!linux", "!Linux", "!Posix") != 0; 766 } 767 else version(OSX) 768 { 769 return _version.among("!Windows", "OSX", "!linux", "!Linux", "Posix") != 0; 770 } 771 else version(linux) 772 { 773 return _version.among("!Windows", "!OSX", "linux", "Linux", "Posix") != 0; 774 } 775 else version(Posix) 776 { 777 return _version.among("!Windows", "!OSX", "!linux", "!Linux", "Posix") != 0; 778 } 779 else 780 { 781 return false; 782 } 783 } 784 785 } 786 787 /** 788 * Apply aliasses to the tokens in the string, and 789 * camelCase underscore separated tokens. 790 */ 791 string stringToGtkD(string str, string[string] aliases, bool caseConvert = true) 792 { 793 return stringToGtkD(str, aliases, null, caseConvert); 794 } 795 796 string stringToGtkD(string str, string[string] aliases, string[string] localAliases, bool caseConvert = true) 797 { 798 size_t pos, start; 799 string seps = " \n\r\t\f\v()[]*,;"; 800 auto converted = appender!string(); 801 802 while ( pos < str.length ) 803 { 804 if ( !seps.canFind(str[pos]) ) 805 { 806 start = pos; 807 808 while ( pos < str.length && !seps.canFind(str[pos]) ) 809 pos++; 810 811 //Workaround for the tm struct, type and variable have the same name. 812 if ( pos < str.length && str[pos] == '*' && str[start..pos] == "tm" ) 813 converted.put("void"); 814 else 815 converted.put(tokenToGtkD(str[start..pos], aliases, localAliases, caseConvert)); 816 817 if ( pos == str.length ) 818 break; 819 } 820 821 converted.put(str[pos]); 822 pos++; 823 } 824 825 return converted.data; 826 } 827 828 unittest 829 { 830 assert(stringToGtkD("token", ["token":"tok"]) == "tok"); 831 assert(stringToGtkD("string token_to_gtkD(string token, string[string] aliases)", ["token":"tok"]) 832 == "string tokenToGtkD(string tok, string[string] aliases)"); 833 } 834 835 string tokenToGtkD(string token, string[string] aliases, bool caseConvert=true) 836 { 837 return tokenToGtkD(token, aliases, null, caseConvert); 838 } 839 840 string tokenToGtkD(string token, string[string] aliases, string[string] localAliases, bool caseConvert=true) 841 { 842 if ( token in glibTypes ) 843 return glibTypes[token]; 844 else if ( token in localAliases ) 845 return localAliases[token]; 846 else if ( token in aliases ) 847 return aliases[token]; 848 else if ( token.endsWith("_t", "_t*", "_t**") ) 849 return token; 850 else if ( token == "pid_t" || token == "size_t" ) 851 return token; 852 else if ( caseConvert ) 853 return tokenToGtkD(removeUnderscore(token), aliases, localAliases, false); 854 else 855 return token; 856 } 857 858 string removeUnderscore(string token) 859 { 860 char pc; 861 auto converted = appender!string(); 862 863 while ( !token.empty ) 864 { 865 if ( token[0] == '_' ) 866 { 867 pc = token[0]; 868 token = token[1..$]; 869 870 continue; 871 } 872 873 if ( pc == '_' ) 874 converted.put(token[0].toUpper()); 875 else 876 converted.put(token[0]); 877 878 pc = token[0]; 879 token = token[1..$]; 880 } 881 882 return converted.data; 883 } 884 885 unittest 886 { 887 assert(removeUnderscore("this_is_a_test") == "thisIsATest"); 888 }