1 module generator; 2 3 import std.array; 4 import std.stdio; 5 import std..string; 6 import std.typecons; 7 import std.json; 8 import std.algorithm; 9 import std.format; 10 import std.conv : to; 11 import transforms.camel; 12 import transforms.snake; 13 import iban; 14 15 class Generator { 16 import fakerjsgenerator; 17 string output; 18 string locale; 19 string[] toOverride; 20 21 this(string locale, string fallback, string[] toOverride) { 22 this.toOverride = toOverride; 23 this.locale = locale; 24 if(locale == "en") { 25 this.output = ` 26 /// 27 module faked.base; 28 29 import std.datetime; 30 import std.exception : enforce; 31 32 struct Currency { 33 @safe: 34 string name; 35 string code; 36 string symbol; 37 38 @property bool empty() const { 39 return name.length == 0; 40 } 41 } 42 43 /// 44 struct BBan { 45 string type; 46 long count; 47 } 48 49 /// 50 struct IbanFormat { 51 string country; 52 long total; 53 BBan[] bban; 54 string format; 55 } 56 57 /// 58 struct IbanData { 59 dchar[] alpha; 60 string[] pattern10; 61 string[] pattern100; 62 IbanFormat[] formats; 63 string[] iso3166; 64 } 65 66 /// 67 class Faker { 68 @safe: 69 import std.random; 70 import std.array; 71 import std.format; 72 import std.conv : to; 73 import std.string : toUpper; 74 import std.range : iota, take, repeat; 75 import std.algorithm : map, joiner; 76 77 /// 78 Random rnd; 79 80 /// 81 this(int seed) { 82 this.rnd = Random(seed); 83 } 84 85 /// 86 string addressLatitude() { 87 return to!string(uniform(-90.0, 90.0, this.rnd)); 88 } 89 90 /// 91 string addressLongitude() { 92 return to!string(uniform(-90.0, 90.0, this.rnd)); 93 } 94 95 /// 96 string financeAccount(size_t length = 8) { 97 string s; 98 foreach(i; 0 .. length) { 99 s ~= "#"; 100 } 101 return digitBuild(s); 102 } 103 104 /// 105 string financeRoutingNumber() { 106 import std.conv : to; 107 import std.math : ceil; 108 auto routingNumber = digitBuild("########"); 109 110 // Modules 10 straight summation. 111 size_t sum = 0; 112 113 for(size_t i = 0; i < routingNumber.length; i += 3) { 114 sum += to!size_t(routingNumber[i]) * 3; 115 sum += to!size_t(routingNumber[i + 1]) * 7; 116 if(i + 2 < routingNumber.length) { 117 sum += to!size_t(routingNumber[i + 2]); 118 } else { 119 sum += 0; 120 } 121 } 122 123 return routingNumber ~ to!string((ceil(sum / 10.0) * 10 - sum)); 124 } 125 126 /// 127 string financeMask(size_t length = 4, bool parents = true, 128 bool ellipsis = true) 129 { 130 import std.algorithm : joiner; 131 import std.conv : to; 132 string tmp = ""; 133 134 for(size_t i = 0; i < length; i++) { 135 tmp ~= '#'; 136 } 137 138 //prefix with ellipsis 139 tmp = ellipsis ? ["...", tmp].joiner("").to!string() : tmp; 140 141 tmp = parents ? ["(", tmp, ")"].joiner("").to!string() : tmp; 142 143 //generate random numbers 144 tmp = digitBuild(tmp); 145 146 return tmp; 147 } 148 149 /// 150 string financeBitcoinAddress() { 151 import std.conv : to; 152 static enum data = 153 to!(dchar[])( 154 "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ" 155 ); 156 157 string ret = to!string(choice([1, 3], this.rnd)); 158 foreach(it; 0 .. choice([25,35], this.rnd)) { 159 ret ~= choice(data, this.rnd); 160 } 161 return ret; 162 } 163 164 /// 165 string loremSentance(size_t length = size_t.max) { 166 import std.algorithm : copy; 167 length = length == size_t.max || length == 0 168 ? uniform(3, 10, this.rnd) 169 : length; 170 auto app = appender!string(); 171 copy(iota(length).map!(a => loremWords).joiner(" "), app); 172 //foreach(it; 0 .. length) { 173 // app.put(loremWords()); 174 // app.put(" "); 175 //} 176 switch(uniform(0, 15, this.rnd)) { 177 case 0: app.put("!"); break; 178 case 1: app.put("?"); break; 179 default: app.put("."); break; 180 } 181 182 string ret = app.data; 183 string f = to!string(toUpper(ret.front)); 184 ret.popFront(); 185 return f ~ ret; 186 } 187 188 /// 189 string loremSentances(size_t length = size_t.max) { 190 import std.algorithm : map, joiner; 191 import std.range : iota; 192 import std.conv : to; 193 length = length == size_t.max || length == 0 194 ? uniform(2, 6, this.rnd) 195 : length; 196 197 return iota(length) 198 .map!(a => loremSentance()) 199 .joiner(" ") 200 .to!string(); 201 } 202 203 /// 204 string loremParagraph(size_t length = size_t.max) { 205 length = length == size_t.max || length == 0 206 ? uniform(2, 6, this.rnd) 207 : length; 208 209 return loremSentances(length + uniform(0, 3, this.rnd)); 210 } 211 212 /// 213 string loremParagraphs(size_t length = size_t.max) { 214 import std.algorithm : map, joiner; 215 import std.range : iota; 216 217 length = length == size_t.max || length == 0 218 ? uniform(2, 6, this.rnd) 219 : length; 220 return iota(length) 221 .map!(a => loremParagraph()) 222 .joiner("\n") 223 .to!string(); 224 } 225 226 /// 227 string loremText(size_t length = size_t.max) { 228 length = length == size_t.max || length == 0 229 ? uniform(2, 6, this.rnd) 230 : length; 231 232 auto app = appender!string(); 233 foreach(it; 0 .. length) { 234 switch(uniform(0, 4, this.rnd)) { 235 case 0: 236 app.put(loremWords()); 237 continue; 238 case 1: 239 app.put(loremParagraph()); 240 continue; 241 case 2: 242 app.put(loremSentance()); 243 continue; 244 case 3: 245 app.put(loremSentances()); 246 continue; 247 default: 248 assert(false); 249 } 250 } 251 252 return app.data(); 253 } 254 255 /// 256 string phoneNumber() { 257 return this.digitBuild(this.phoneNumberFormats()); 258 } 259 260 /// 261 string commerceProductName() { 262 return this.commerceProductNameAdjective() ~ 263 this.commerceProductNameMaterial() ~ " " ~ 264 this.commerceProductNameProduct(); 265 } 266 267 /// 268 string companyCatchPhrase() { 269 return companyAdjective() ~ " " 270 ~ companyDescriptor() ~ " " 271 ~ companyNoun(); 272 } 273 274 // 275 string companyBs() { 276 return companyBsVerb() ~ " " ~ companyBsAdjective() ~ " " ~ 277 companyBsNoun(); 278 } 279 280 /// 281 string internetUserName(string firstname = "", string lastname = "") { 282 firstname = firstname.empty ? this.nameFirstName() : firstname; 283 lastname = lastname.empty ? this.nameLastName() : lastname; 284 285 string ret; 286 287 switch(uniform(0, 3, this.rnd)) { 288 case 0: 289 ret = firstname ~ to!string(uniform(0, 100, this.rnd)); 290 break; 291 case 1: 292 ret = firstname ~ choice([".", "_"], this.rnd) ~ lastname; 293 break; 294 case 2: 295 ret = firstname ~ choice([".", "_"], this.rnd) ~ lastname 296 ~ to!string(uniform(0, 100, this.rnd)); 297 break; 298 default: 299 assert(false); 300 } 301 302 return ret.replace("'", "").replace(" ", ""); 303 } 304 305 /// 306 string internetProtocol() { 307 return choice(["http", "https"], this.rnd); 308 } 309 310 /// 311 string internetDomainWord() { 312 import std.uni : isAlphaNum; 313 import std.utf : byDchar; 314 import std.algorithm : filter; 315 316 return this.nameFirstName() 317 .byDchar() 318 .filter!(a => isAlphaNum(a)) 319 .to!string(); 320 } 321 322 /// 323 string internetDomainName() { 324 return this.internetDomainWord() ~ "." ~ this.internetDomainSuffix(); 325 } 326 327 /// 328 string internetUrl() { 329 return this.internetProtocol() ~ "://" ~ this.internetDomainName(); 330 } 331 332 /// 333 string internetIPv4() { 334 int[4] t; 335 foreach(i; 0 .. t.length) { 336 t[i] = uniform(0, 256, this.rnd); 337 } 338 339 return t[].map!(a => to!string(a)).joiner(".").to!string(); 340 } 341 342 /// 343 string internetIPv6() { 344 static enum elem = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 345 "a", "b", "c", "d", "e", "f"]; 346 347 return iota(8) 348 .map!(a => randomCover(elem, this.rnd).take(4).to!string()) 349 .joiner(".") 350 .to!string(); 351 } 352 353 /// 354 string internetColor(int baseRed255 = 0, int baseGreen255 = 0, 355 int baseBlue255 = 0) 356 { 357 int red = to!int((uniform(0, 256, this.rnd) + baseRed255) / 2); 358 int green = to!int((uniform(0, 256, this.rnd) + baseGreen255) / 2); 359 int blue = to!int((uniform(0, 256, this.rnd) + baseBlue255) / 2); 360 string redStr = red.to!string(16); 361 string greenStr = green.to!string(16); 362 string blueStr = blue.to!string(16); 363 return "#" ~ 364 (redStr.length == 1 ? "0" : "") ~ redStr ~ 365 (greenStr.length == 1 ? "0" : "") ~ greenStr ~ 366 (blueStr.length == 1 ? "0": "") ~ blueStr; 367 } 368 369 /// 370 string internetPassword(bool strong = false) { 371 return strong ? "Password" : "password"; 372 } 373 374 /// 375 string vehicle() { 376 return this.vehicleManufacturer() ~ " " ~ this.vehicleModel(); 377 } 378 379 /// 380 string vehicleVin() { 381 return (this.helperAlphaNum(10) ~ this.helperAlpha(1, true) 382 ~ this.helperAlphaNum(1) 383 ~ to!string(uniform(10000, 100000, this.rnd)) 384 ).toUpper(); 385 } 386 387 /// 388 string helperAlpha(size_t count = 1, bool upperCase = false) @trusted { 389 static enum data = to!(dchar[])(['a', 'b', 'c', 'd', 'e', 'f', 'g', 390 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 391 'u', 'v', 'w', 'x', 'y', 'z']); 392 393 return iota(count).map!(a => choice(data, this.rnd)).to!string(); 394 } 395 396 /// 397 string helperAlphaNum(size_t count = 1) @trusted { 398 static enum data = to!(dchar[])(['0', '1', '2', '3', '4', '5', '6', '7', 399 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 400 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 401 'y', 'z']); 402 return iota(count).map!(a => choice(data, this.rnd)).to!string(); 403 } 404 405 /// 406 string helperHexaDecimal(size_t count = 1) @trusted { 407 static enum data = to!(dchar[])(['0', '1', '2', '3', '4', '5', '6', 408 '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 409 'E', 'F']); 410 411 return iota(count).map!(a => choice(data, this.rnd)).to!string(); 412 } 413 414 /// 415 string passportNumber() { 416 return helperHexaDecimal(9); 417 } 418 419 /// 420 DateTime datePast(size_t years = 10, DateTime refDate = 421 cast(DateTime)Clock.currTime()) 422 { 423 return refDate + dur!"minutes"(-uniform(0, years * 365 * 24 * 60, 424 this.rnd)); 425 } 426 427 /// 428 DateTime dateFuture(size_t years = 10, DateTime refDate = 429 cast(DateTime)Clock.currTime()) 430 { 431 return refDate + dur!"minutes"(uniform(0, years * 365 * 24 * 60, 432 this.rnd)); 433 } 434 435 /// 436 DateTime dateBetween(DateTime begin, DateTime end) { 437 enforce(begin <= end, "begin must be <= end"); 438 Duration d = end - begin; 439 long hours = d.total!"hours"(); 440 return begin + dur!("hours")(uniform(0, hours, this.rnd)); 441 } 442 443 /// 444 string ukNationalInsuranceNumber() { 445 auto app = appender!string(); 446 447 static enum data = 448 to!(dchar[])( 449 "ABCEGHJKLMNPRSTUWXYZ" 450 ); 451 452 static enum suffix = 453 to!(dchar[])( 454 "ABCD" 455 ); 456 457 formattedWrite(app, "%s%s", choice(data, this.rnd), choice(data, this.rnd)); 458 formattedWrite(app, "%06d", uniform(0, 1_000_000, this.rnd)); 459 formattedWrite(app, "%02d", uniform(0, 100, this.rnd)); 460 formattedWrite(app, "%s", choice(suffix, this.rnd)); 461 return app.data; 462 } 463 464 /// 465 string nameGenderBinary() { 466 return choice(["Man", "Woman"], this.rnd); 467 } 468 469 `; 470 471 this.output ~= format("\tstatic IbanData ibanData = %s;", 472 buildIbanData() 473 ); 474 } else { 475 this.output = format(`/// 476 module faked.faker_%1$s; 477 478 import faked.base; 479 %3$s 480 481 /// 482 class Faker_%1$s : Faker%2$s { 483 @safe: 484 import std.random; 485 import std.array; 486 import std.format; 487 import std.conv : to; 488 489 /// 490 this(int seed) { 491 super(seed); 492 } 493 494 `, 495 toLower(locale), (fallback.empty || fallback == "en") ? "" 496 : "_" ~ fallback, 497 (fallback.empty || fallback == "en") ? "" 498 : format("import faked.faker_%s;", fallback) 499 ); 500 } 501 502 if(locale == "en") { 503 this.output ~= ` 504 /// 505 string digitBuild(string s, dchar sym = '#') { 506 auto app = appender!string(); 507 for(size_t idx = 0; idx < s.length; ++idx) { 508 dchar c = s[idx]; 509 if(c == sym) { 510 formattedWrite(app, "%d", uniform(0, 10, this.rnd)); 511 } else if(c == '[') { 512 ++idx; 513 size_t start = idx; 514 while(idx < s.length && s[idx] != ']') { 515 ++idx; 516 } 517 enforce(idx < s.length && s[idx] == ']'); 518 string[] ft = s[start .. idx].split("-"); 519 enforce(ft.length == 2); 520 int[] ftI = ft.map!(a => to!int(a)).array; 521 enforce(ft.length == 2); 522 int n = uniform(ftI[0], ftI[1], this.rnd); 523 formattedWrite(app, "%d", n); 524 } else if(c == '!') { 525 formattedWrite(app, "%d", uniform(2, 10, this.rnd)); 526 } else { 527 app.put(c); 528 } 529 } 530 return app.data; 531 } 532 533 /// 534 string replaceChars(string s) { 535 static enum alpha = to!(dchar[])("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 536 auto app = appender!string(); 537 foreach(dchar c; s) { 538 if(c == '#') { 539 formattedWrite(app, "%d", choice(alpha, this.rnd)); 540 } else { 541 app.put(c); 542 } 543 } 544 return app.data; 545 } 546 547 /// 548 string replaceSymbols(string str) { 549 static enum alpha = to!(dchar[])([ 550 'A','B','C','D','E','F','G','H','I','J','K','L', 551 'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z' 552 ]); 553 554 auto app = appender!string(); 555 556 foreach(dchar c; str) { 557 if(c == '#') { 558 formattedWrite(app, "%d", uniform(0, 10, this.rnd)); 559 } else if(c == '?') { 560 app.put(choice(alpha, this.rnd)); 561 } else if(c == '*') { 562 formattedWrite(app, "%s", 563 (uniform(0, 2) == 1) 564 ? to!string(choice(alpha, this.rnd)) 565 : to!string(uniform(0, 10, this.rnd)) 566 ); 567 } else { 568 app.put(c); 569 } 570 } 571 return app.data; 572 } 573 `; 574 } else { 575 } 576 } 577 578 void finish() { 579 this.output ~= "}\n"; 580 } 581 582 string getOverrideString(string name) { 583 return canFind(this.toOverride, name) ? "override " : ""; 584 } 585 586 string buildStringImpl(string name, string data, string retType = "string") { 587 string n = format("\t///\n\t%s%s %s() {\n" ~ 588 "\t\tstatic enum data = [\n\t\t%s\n\t\t];\n" ~ 589 "\t\treturn choice(data, this.rnd);\n" ~ 590 "\t}\n\n", this.getOverrideString(name), retType, name, data); 591 this.output ~= n; 592 return name; 593 } 594 595 string[] buildNameTitle(string data) { 596 string[] ret; 597 JSONValue js = parseJson(data); 598 599 foreach(key; ["descriptor", "job", "level"]) { 600 if(const(JSONValue)* value = key in js) { 601 string n = ("name_title_" ~ key).camelCase(); 602 ret ~= this.buildStringImpl(n, 603 value.array().map!(a => "\"" ~ a.str() ~ "\"") 604 .joiner(",\n\t\t").to!string() 605 ); 606 } 607 } 608 609 return ret; 610 } 611 612 string buildFinanceCurrency(string data) { 613 JSONValue js = parseJson(data); 614 auto jsO = js.object(); 615 return this.buildStringImpl("financeCurrency", 616 jsO.keys() 617 .map!(a => tuple(a, jsO[a])) 618 .filter!(a => "code" in a[1] && "symbol" in a[1]) 619 .map!(a => format("Currency(\"%s\", \"%s\", \"%s\")", 620 a[0], a[1]["code"].str(), a[1]["symbol"].str()) 621 ).joiner(",\n\t\t").to!string() 622 , "Currency" 623 ); 624 } 625 626 string[] buildIbanAndBic() { 627 string tmp =` 628 /// 629 string financeBIC() { 630 enum string[] vowels = ["A", "E", "I", "O", "U"]; 631 int prob = uniform(0, 100, this.rnd); 632 return this.replaceSymbols("???") ~ 633 choice(vowels, this.rnd) ~ 634 choice(ibanData.iso3166, this.rnd) ~ 635 this.replaceSymbols("?") ~ "1" ~ 636 (prob < 10 ? 637 this.replaceSymbols("?" ~ choice(vowels, this.rnd) ~ "?") : 638 prob < 40 ? 639 this.replaceSymbols("###") : ""); 640 } 641 642 /// 643 long mod97(string digitStr) { 644 long m = 0; 645 for(long i = 0; i < digitStr.length; i++) { 646 m = ((m * 10) + (digitStr[i] | 0)) % 97; 647 } 648 return m; 649 } 650 651 /// 652 string toDigitString(string str) { 653 import std.uni; 654 auto app = appender!string(); 655 foreach(dchar c; str) { 656 switch(c) { 657 case 'a': .. case 'z': 658 formattedWrite(app, "%s", Grapheme(toUpper(c))[0] - 55); 659 break; 660 case 'A': .. case 'Z': 661 formattedWrite(app, "%s", Grapheme(c)[0] - 55); 662 break; 663 default: 664 app.put(c); 665 break; 666 } 667 } 668 return app.data; 669 //return str.replace(/[A-Z]/gi, function(match) { 670 // return match.toUpperCase().charCodeAt(0) - 55; 671 //}); 672 } 673 674 /// TODO IBAN generation looks broken 675 string financeIBAN(bool fourSpace = false) { 676 auto ibanFormat = choice(ibanData.formats, this.rnd); 677 auto app = appender!string(); 678 string s; 679 int count = 0; 680 for(int b = 0; b < ibanFormat.bban.length; ++b) { 681 auto bban = ibanFormat.bban[b]; 682 long c = bban.count; 683 count += bban.count; 684 while(c > 0) { 685 if(bban.type == "a") { 686 //s += faker.random.arrayElement(ibanLib.alpha); 687 app.put(choice(ibanData.alpha, this.rnd)); 688 } else if (bban.type == "c") { 689 if (uniform(0, 100, this.rnd) < 80) { 690 formattedWrite(app, "%d", uniform(0, 10, this.rnd)); 691 //s += faker.random.number(9); 692 } else { 693 app.put(choice(ibanData.alpha, this.rnd)); 694 //s += faker.random.arrayElement(ibanLib.alpha); 695 } 696 } else { 697 if (c >= 3 && uniform(0, 101, this.rnd) < 30) { 698 //if (c >= 3 && faker.random.number(100) < 30) { 699 if (uniform(0, 2, this.rnd) == 1) { 700 //s += faker.random.arrayElement(ibanLib.pattern100); 701 app.put(choice(ibanData.pattern100, this.rnd)); 702 c -= 2; 703 } else { 704 //s += faker.random.arrayElement(ibanLib.pattern10); 705 app.put(choice(ibanData.pattern10, this.rnd)); 706 c--; 707 } 708 } else { 709 //s += faker.random.number(9); 710 formattedWrite(app, "%d", uniform(0, 10, this.rnd)); 711 } 712 } 713 c--; 714 } 715 s = app.data[0 .. count]; 716 } 717 auto checksum = 98 - mod97(toDigitString(s ~ ibanFormat.country ~ "00")); 718 string checksumStr; 719 if (checksum < 10) { 720 checksumStr = "0" ~ to!string(checksum); 721 } 722 auto iban = ibanFormat.country ~ checksumStr ~ s; 723 return fourSpace 724 ? format("%,4?s", ' ', iban) 725 : iban; 726 } 727 `; 728 729 this.output ~= tmp; 730 731 //return ["financeIBAN", "financeBIC"]; 732 return ["financeBIC", "financeIBAN"]; 733 } 734 735 string[] buildCreditCards(Sub sub) { 736 import jssplitter; 737 738 string[] ret; 739 740 string tmp = ` 741 /// 742 string fianaceCreditCardCVV() { 743 string ret; 744 for(int i = 0; i < 3; ++i) { 745 ret ~= to!string(uniform(0, 3, this.rnd)); 746 } 747 return ret; 748 } 749 `; 750 tmp ~= format(` 751 /// 752 string financeCreditCard() { 753 switch(uniform(0, %s, this.rnd)) { 754 `, sub.subs.length - 2); 755 int cnt = 0; 756 foreach(key, value; sub.subs) { 757 if(key == "laser" || key == "maestro") { 758 continue; 759 } 760 tmp ~= format("\t\t\tcase " ~ to!string(cnt++) 761 ~ ": \n\t\t\t\treturn financeCreditCard%s();\n", 762 to!string(toUpper(key[0])) ~ key[1 .. $].camelCase()); 763 } 764 tmp ~= ` 765 default: 766 assert(false); 767 } 768 assert(false); 769 } 770 771 `; 772 this.output ~= tmp; 773 774 foreach(key, value; sub.subs) { 775 TypeLines tl = jssplit(value.data); 776 if(tl.type == Type.digit) { 777 string fname = "financeCreditCard" ~ to!string(toUpper(key[0])) 778 ~ key[1 .. $].camelCase(); 779 tmp = format("\t///\n\t%sstring %s() {\n", 780 this.getOverrideString(fname), fname 781 782 ); 783 tmp ~= format("\t\tstatic enum data = [\n\t\t%(%s,\n\t\t%)\n\t\t];\n", 784 tl.lines 785 ); 786 tmp ~= "\t\treturn this.digitBuild(choice(data, this.rnd));\n"; 787 tmp ~= "\t}\n\n"; 788 789 this.output ~= tmp; 790 ret ~= fname; 791 } else { 792 writefln("failed on %s with type %s", key, tl.type); 793 } 794 795 } 796 return ret ~ ["fianaceCreditCardCVV", "financeCreditCard"]; 797 } 798 799 string[] buildCommerceProductName(string data) { 800 string[] ret; 801 JSONValue js = parseJson(data); 802 auto jsO = js.object(); 803 foreach(key; ["adjective", "material", "product"]) { 804 if(const(JSONValue)* value = key in js) { 805 string n = ("commerce_product_name_" ~ key).camelCase(); 806 ret ~= this.buildStringImpl(n, 807 value.array().map!(a => "\"" ~ a.str() ~ "\"") 808 .joiner(",\n\t\t").to!string() 809 ); 810 } 811 } 812 return ret; 813 } 814 815 string buildString(string name, string postfix, string[] lines) { 816 import std.utf : byUTF, replacementDchar; 817 string fname = name ~ "_" ~ postfix; 818 fname = fname.camelCase(); 819 820 //writefln("%s %s", fname, this.locale); 821 string[] nlines; 822 foreach(idx, ref line; lines) { 823 try { 824 string s = line.replace("\r\n", "\n").byUTF!dchar() 825 .filter!(it => it != replacementDchar) 826 .to!string(); 827 writefln("'%s'", s); 828 nlines ~= s; 829 } catch(Throwable t) { 830 writefln("%s %s", idx, line); 831 } 832 } 833 834 this.buildStringImpl(fname, 835 nlines.map!(a => "\"" ~ a ~ "\"") 836 .joiner(",\n\t\t") 837 .to!string() 838 ); 839 840 return fname; 841 } 842 843 string buildDigits(string name, string postfix, string[] lines) { 844 string fname = name ~ "_" ~ postfix; 845 fname = fname.camelCase(); 846 string n = format("\t///\n\t%sstring %s() {\n", 847 this.getOverrideString(fname), fname 848 ); 849 n ~= format("\t\tstatic enum data = [\n\t\t%(%s,\n\t\t%)\n\t\t];\n", 850 lines 851 ); 852 n ~= "\t\treturn this.digitBuild(choice(data, this.rnd));\n"; 853 n ~= "\t}\n\n"; 854 855 this.output ~= n; 856 return fname; 857 } 858 859 string buildCall(string name, string postfix, string[] lines) { 860 string fname = name ~ "_" ~ postfix; 861 fname = fname.camelCase(); 862 string n = format("\t///\n\t%sstring %s() {\n", this.getOverrideString(fname), 863 fname 864 ); 865 if(lines.length > 1) { 866 n ~= format("\t\tswitch(uniform(0, %d, this.rnd)) {\n", lines.length); 867 foreach(idx, l; lines) { 868 n ~= format("\t\t\tcase %d:\n", idx); 869 n ~= "\t\t\t\t" ~ buildCallImpl(l, name); 870 } 871 872 n ~= "\t\t\tdefault: assert(false);\n"; 873 n ~= "\t\t}\n"; 874 } else { 875 n ~= "\t\t" ~ buildCallImpl(lines[0], name); 876 } 877 n ~= "\t}\n\n"; 878 879 this.output ~= n; 880 return fname; 881 } 882 883 string buildCallImpl(string line, string prefix) { 884 string ret = "return format!\""; 885 bool inCall = false; 886 string tmp; 887 string[] calls; 888 while(!line.empty) { 889 if(inCall && line.front != '}') { 890 tmp ~= line.front; 891 line.popFront(); 892 } else if(inCall && line.front == '}') { 893 calls ~= tmp; 894 tmp = ""; 895 inCall = false; 896 line.popFront(); 897 ret ~= "%s"; 898 } else if(line.startsWith("#{")) { 899 assert(!inCall); 900 inCall = true; 901 line = line[2 .. $]; 902 } else { 903 ret ~= line.front; 904 line.popFront(); 905 } 906 } 907 ret ~= "\"("; 908 ret ~= to!string(calls 909 //.map!(a => prefix ~ "_" ~ to!char(toLower(a[0])) ~ a[1 ..$]) 910 .map!(a => callReplace(a, prefix)) 911 .map!(a => a ~ "()") 912 .joiner(", ")); 913 ret ~= ");\n"; 914 return ret; 915 } 916 917 string callReplace(string s, string prefix) { 918 if(s.indexOf(".") == -1) { 919 string t = (prefix ~ "_" ~ s.snakeCase()).camelCase(); 920 //string t = (prefix ~ to!char(toUpper(s[0])) ~ s[1 .. $]); 921 //t = t.camelCase(); 922 //writeln("nor ", s, " ", prefix, " ", t); 923 return t; 924 } else { 925 string t = s.snakeCase(); 926 t = t.replace(".", "_"); 927 t = t.camelCase(); 928 //writeln("dot ", t); 929 return t; 930 } 931 932 } 933 } 934 935 JSONValue parseJson(string input) { 936 input = input.strip(); 937 enum fStr = "module[\"exports\"] = "; 938 enum eStr = "};"; 939 if(!input.startsWith(fStr)) { 940 return parseJSON("{}"); 941 } 942 943 input = input[fStr.length .. $]; 944 945 if(!input.endsWith("};")) { 946 return parseJSON("{}"); 947 } 948 949 input = input[0 .. $ - 1]; 950 951 return parseJSON(input); 952 }