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 }