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 }