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