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