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