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 }