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 fianceAccount(size_t length = 8) {
97         string s;
98         foreach(i; 0 .. length) {
99             s ~= "#";
100         }
101         return digitBuild(s);
102     }
103 
104 	///
105     string fianceRoutingNumber() {
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.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 }