1 module fakerjsgenerator;
2 
3 import std.algorithm;
4 import std.array;
5 import std.stdio;
6 import std.process;
7 import std.format;
8 import std.conv : to;
9 import std.exception : enforce;
10 import std.file : dirEntries, SpanMode, isDir, readText, isFile, exists,
11        mkdirRecurse, read;
12 import std.algorithm;
13 import std..string;
14 import std.regex;
15 
16 import jssplitter;
17 import generator;
18 import iban;
19 
20 enum outputDir = "../source/faked/";
21 
22 void main() {
23 	cloneFaker();
24 	FakerData[] locales = scrapeFakers();
25     mkdirRecurse(outputDir);
26 
27     auto oldList = [
28         "af_ZA", "de", "en_CA", "en_US", /*"fa",*/ "ge", "ko", "nl", "pt_PT",
29         "sv", "zh_CN", /*"ar",*/ "de_AT", "en_AU", "en_GB", "en_ZA", "fr", "id_ID",
30         "lv", "nl_BE", "ro", "tr", "zh_TW", /*"az",*/ "de_CH", "en_AU_ocker",
31         "en_IE", "es", "fr_CA", "it", "nb_NO", "pl", "ru", "uk", "zu_ZA",
32         /*"cz",*/ "el", "en_BORK", "en_IND", /*"es_MX",*/ "fr_CH", "ja",
33         "pt_BR", /*"sk",*/ "vi"
34     ].sort.array;
35 
36 	auto list = [
37 		/*"az",*/ /*"ar",*/ /*"cz",*/ "de", "de_AT", "de_CH", "en_AU", "en_AU_ocker",
38 		"en_BORK", "en_CA", "en_GB", "en_IE", "en_IND", "en_US", "en_ZA", "es",
39 		/*"es_MX",*/ /*"fa",*/ "fi", "fr", "fr_CA", "fr_CH", "ge", "hy", "hr", "id_ID",
40 		"it", "ja", "ko", "nb_NO", "ne", "nl", "nl_BE", "pl", "pt_BR", "pt_PT",
41 		"ro", "ru", /*"sk",*/ "sv", "tr", "uk", "vi", "zh_CN", "zh_TW", "af_ZA",
42 		"el", "lv", "zu_ZA"
43 
44 	].sort.array;
45 
46 	writefln("LIST OLDLIST\n%s", setDifference(list, oldList));
47 	writefln("OLDLIST LIST\n%s", setDifference(oldList, list));
48 
49     auto en = locales.find!(a => a.locale == "en").front;
50     string[] methods = buildFile("en", en, []);
51     methods ~= ["addressLatitude", "addressLongitude", "financeAccount",
52         "financeRoutingNumber", "financeMask", "financeBitcoinAddress",
53         "phoneNumber", "commerceProductName", "companyCatchPhrase",
54         "companyBs", "internetUserName", "internetProtocol",
55         "internetDomainWord", "internetDomainName", "internetUrl",
56         "internetIPv4", "internetIPv6", "internetColor", "internetPassword",
57         "vehicle", "vehicleVin", "helperAlpha", "helperAlphaNum",
58         "helperHexaDecimal",
59     ];
60     methods = methods.sort.uniq.array;
61 
62     //writefln("%(%s\n%)", methods);
63     foreach(ll; list) {
64 	    auto f = locales.find!(a => a.locale == ll);
65         assert(!f.empty, ll);
66         buildFile(ll, f.front, methods);
67     }
68 
69     buildInstantionTest(list ~ "en", methods);
70 
71     writePackageFile(list);
72 }
73 
74 void writePackageFile(string[] list) {
75     auto f = File(outputDir ~ "package.d", "w");
76     auto ltw = f.lockingTextWriter();
77 
78     formattedWrite(ltw, "// generated by fakerjsgenerator\n");
79     formattedWrite(ltw, "module faked;\n\n");
80     formattedWrite(ltw, "///\npublic import faked.base;\n\n");
81     foreach(l; list) {
82         formattedWrite(ltw, "///\npublic import faked.faker_%s;\n\n", toLower(l));
83     }
84 }
85 
86 void buildInstantionTest(string[] list, string[] methods) {
87     auto f = File(outputDir ~ "test.d", "w");
88     auto ltw = f.lockingTextWriter();
89     formattedWrite(ltw, "// generated by fakerjsgenerator\n");
90     formattedWrite(ltw, "module faked.test;\n\nimport std.array : empty;\n\n");
91 
92     string calls = "\tfor(int i = 0; i < 30; ++i) {\n" ~
93         methods
94             .map!(a => format("\t\tassert(!faker.%s().empty);\n", a))
95             .joiner()
96             .to!string()
97         ~ "\n\t}\n";
98 
99     string ut = `unittest {
100 %s;
101     auto faker = new %s(%d);
102 %s}
103 
104 `;
105     foreach(idx, l; list) {
106         formattedWrite(ltw, ut,
107             l == "en"
108                 ? "\timport faked.base"
109                 : "\timport faked.faker_" ~ toLower(l),
110             l == "en" ? "Faker" : "Faker_" ~ toLower(l),
111             idx + 1, calls
112         );
113     }
114 }
115 
116 string[] buildFile(string ll, FakerData entry, string[] toOverride) {
117 	//writeln(f.front);
118     string[] methods;
119 	Generator gen = new Generator(ll, entry.fallback, toOverride);
120 	foreach(key, value; entry.data) {
121 		if(ignoreEntries.canFind(key)) {
122 			continue;
123 		}
124 		foreach(sub, svalue; value.subs) {
125 			Direct d = cast(Direct)svalue;
126 			if(d !is null) {
127                 if(key == "name" && sub == "title") {
128                     methods ~= gen.buildNameTitle(d.data);
129                     continue;
130                 }
131                 if(key == "finance" && sub == "currency") {
132                     methods ~= gen.buildFinanceCurrency(d.data);
133                     continue;
134                 }
135                 if(key == "commerce" && sub == "product_name") {
136                     methods ~= gen.buildCommerceProductName(d.data);
137                     continue;
138                 }
139 				if(d.data.canFind("concat(")) {
140 					writeln("\n\nTODO\nTODO\n\n");
141 					continue;
142 				}
143 				//write(key, ".", sub, " ");
144 				TypeLines tl = jssplit(d.data);
145 				//writeln(tl.type);
146 				if(tl.type == Type.strings) {
147 					methods ~= gen.buildString(key, sub, tl.lines);
148 				} else if(tl.type == Type.digit) {
149 					methods ~= gen.buildDigits(key, sub, tl.lines);
150 				} else if(tl.type == Type.call) {
151 					methods ~= gen.buildCall(key, sub, tl.lines);
152 				}
153 			} else {
154                 Sub s = cast(Sub)svalue;
155                 if(key == "finance" && sub == "credit_card" && s !is null) {
156                     methods ~= gen.buildCreditCards(s);
157                 }
158             }
159 		}
160 	}
161 
162     string fname = (ll == "en")
163         ? "base.d"
164         : "faker_" ~ toLower(ll) ~ ".d";
165 
166     if(ll == "en") {
167         methods ~= gen.buildIbanAndBic();
168         methods ~= ["addressLatitude", "addressLongitude", "financeAccount",
169                 "financeRoutingNumber", "financeMask", "financeBitcoinAddress",
170                 "loremSentance", "loremSentances", "loremParagraph",
171                 "loremParagraphs", "loremText", "phoneNumber"];
172     }
173 
174 	gen.finish();
175 
176 	auto fout = File(outputDir ~ fname, "w");
177     auto ltw = fout.lockingTextWriter();
178     formattedWrite(ltw, "// generated by fakerjsgenerator\n");
179 	fout.write(gen.output);
180 
181     //return methods.filter!(a => !a.empty).array;
182     return methods.sort.array;
183 }
184 
185 const fakerFolder = "faker.js";
186 
187 const string[] ignoreListLocals = ["fa"];
188 const string[] ignoreEntries = ["system"];
189 
190 class Data {
191 	override string toString() const {
192 		assert(false);
193 	}
194 }
195 
196 class Direct : Data {
197 	string data;
198 
199 	this(string d) {
200 		this.data = d;
201 	}
202 
203 	override string toString() const {
204 		return format("    Direct \n%s", this.data);
205 	}
206 }
207 
208 class Sub : Data {
209 	string index;
210 	Direct[string] subs;
211 
212 	override string toString() const {
213 		string ret;
214 		foreach(key, value; subs) {
215 			ret ~= format("\n            %s", key);
216 		}
217 		return ret;
218 	}
219 }
220 
221 struct Entry {
222 	string index;
223 	Data[string] subs;
224 
225 	void toString(scope void delegate(const(char)[]) sink) const {
226 		foreach(key, const(Data) value; this.subs) {
227 			formattedWrite(sink, "        %s:%s\n", key, value.toString());
228 		}
229 	}
230 }
231 
232 class FakerData {
233 	string locale;
234 	string fallback;
235 	Entry[string] data;
236 
237 	void toString(scope void delegate(const(char)[]) sink) const {
238 		formattedWrite(sink, "%s fallback %s\n", this.locale, fallback);
239 		foreach(key, value; this.data) {
240 			formattedWrite(sink, "    %s:\n", key);
241 			value.toString(sink);
242 		}
243 	}
244 }
245 
246 void cloneFaker() {
247 	import std.file : exists, rmdirRecurse;
248 
249 	if(exists(fakerFolder)) {
250         return;
251 	}
252 
253 	auto rslt = executeShell(
254             "git clone --depth=1 https://github.com/Marak/faker.js.git"
255         );
256 	enforce(rslt.status == 0,
257 		format("Failed to clone faker.js with message %s %s", rslt.status,
258 			rslt.output)
259 	);
260 }
261 
262 FakerData[] scrapeFakers() {
263 	FakerData[] ret;
264 	const fakerLocales = fakerFolder ~ "/lib/locales/";
265 
266 	outer: foreach(f; dirEntries(fakerLocales, SpanMode.shallow)) {
267 		foreach(ig; ignoreListLocals) {
268 			if(f.name.endsWith(ig)) {
269 				continue outer;
270 			}
271 		}
272 		ret ~= scrapeFaker(f.name);
273 		buildFallback(ret.back);
274 	}
275 	return ret;
276 }
277 
278 FakerData scrapeFaker(string foldername) {
279 	auto ret = new FakerData();
280 	string name = foldername[foldername.lastIndexOf("/") + 1 .. $];
281 	ret.locale = name;
282 
283 	foreach(f; dirEntries(foldername, SpanMode.shallow)) {
284 		Entry entry;
285 		if(f.name.endsWith("index.js")) {
286 			string iFile = "./" ~ f.name;
287 			assert(isFile(iFile));
288 			entry.index = readText(iFile);
289 			continue;
290 		}
291 		if(isDir(f.name)) {
292 			foreach(g; dirEntries(f, SpanMode.shallow)
293 					.filter!(a => !a.name.canFind("index.js")))
294 			{
295 				if(isFile(g.name)) {
296 					string s = g.name;
297 					s = s[s.lastIndexOf("/") + 1 .. $ - 3];
298 					auto t = cast(string)read(g.name);
299 					entry.subs[s] = new Direct(t);
300 				} else {
301 					auto sub = new Sub();
302 					sub.index = readText(g.name ~ "/index.js");
303 					foreach(h; dirEntries(g.name, SpanMode.shallow)
304 							.filter!(a => !a.name.canFind("index.js")))
305 					{
306 						string sh = h.name;
307 						sh = sh[sh.lastIndexOf("/") + 1 .. $ - 3];
308 						if(isFile(h)) {
309 							//writeln(sh, " ", h);
310 							sub.subs[sh] = new Direct(readText(h));
311 						}
312 					}
313 					entry.subs[g.name[g.name.lastIndexOf("/") + 1 .. $]] = sub;
314 				}
315 			}
316 		}
317 		ret.data[f.name[f.name.lastIndexOf("/") + 1 .. $]] = entry;
318 	}
319 	return ret;
320 }
321 
322 enum r = r"\{\s*locale:\s*'([a-zA-Z_]\+)'\s*,\s*localeFallback\s*:'([a-zA-Z_]\+)'\s*\}";
323 enum r2 = r"'(\w*)'";
324 auto re = regex(r2);
325 
326 void buildFallback(FakerData fd) {
327 	string t = fakerFolder ~ "/locale/" ~ fd.locale ~ ".js";
328 	if(!exists(t)) {
329 		return;
330 	}
331 
332 	string[] tt = readText(t).split("\n");
333 	foreach(l; tt) {
334 		enum pre = "var faker = new Faker(";
335 		if(l.startsWith(pre)) {
336 			string narrow = l[pre.length .. $ - 2];
337 			auto m = matchAll(narrow, re);
338 			if(!m.empty) {
339 				string f = m.front.hit.strip("'");
340 				assert(f == fd.locale,
341 					format("%s %s %s", fd.locale, f, t)
342 				);
343 				m.popFront();
344 				f = m.front.hit.strip("'");
345 				if(!m.empty) {
346 					fd.fallback = f;
347 				}
348 			}
349 		}
350 	}
351 }