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