1 module vayne.lib; 2 3 4 import std.algorithm; 5 import std.array; 6 import std.conv; 7 import std.format; 8 import std..string; 9 import std.utf; 10 import std.regex; 11 12 13 import vayne.value; 14 15 16 template bindVars(size_t i, alias Container, Vars...) { 17 static if(i < Vars.length) { 18 enum bindVars = Vars.length ? (__traits(identifier, Container) ~ "[\"" ~ __traits(identifier, Vars[i]) ~ "\"]=Value(Vars[" ~ i.to!string ~ "]);\n" ~ bindVars!(i + 1, Container, Vars)) : ""; 19 } else { 20 enum bindVars = ""; 21 } 22 } 23 24 25 void bindLibBasic(ref Value[string] globals) { 26 static long length(Value x) { 27 return cast(long)x.length; 28 } 29 30 static bool existsIn(Value[] args) { 31 auto value = args[0].get!string; 32 foreach (f; args[1..$]) 33 if(value == f.get!string) 34 return true; 35 return false; 36 } 37 38 static bool empty(Value x) { 39 return x.length == 0; 40 } 41 42 static Value keys(Value x) { 43 return x.keys(); 44 } 45 46 static Value values(Value x) { 47 return x.values(); 48 } 49 50 static Value front(Value x) { 51 return x[0]; 52 } 53 54 static Value back(Value x) { 55 return x[x.length - 1]; 56 } 57 58 static Value take(Value x, Value y) { 59 return x[0..min(x.length, y.get!long)]; 60 } 61 62 static Value get(Value x, Value key, Value def) { 63 Value result; 64 if (x.has(key, &result)) 65 return result; 66 return def; 67 } 68 69 static Value def(Value x, Value def) { 70 if ((x.type == Value.Type.Undefined) || (x.type == Value.Type.Null)) 71 return def; 72 return x; 73 } 74 75 static long tointeger(Value x) { 76 return x.get!long; 77 } 78 79 static double tofloat(Value x) { 80 return x.get!double; 81 } 82 83 static string tostring(Value x) { 84 return x.toString(); 85 } 86 87 static bool tobool(Value x) { 88 return x.get!bool; 89 } 90 91 static string type(Value x) { 92 final switch (x.type) with (Value.Type) { 93 case Undefined: 94 return "undefined"; 95 case Null: 96 return "null"; 97 case Bool: 98 return "bool"; 99 case Integer: 100 return "integer"; 101 case Float: 102 return "float"; 103 case Function: 104 return "function"; 105 case String: 106 return "string"; 107 case Array: 108 return "array"; 109 case AssocArray: 110 return "assocarray"; 111 case Object: 112 return "object"; 113 case Pointer: 114 return "pointer"; 115 } 116 } 117 118 static bool exists(Value x) { 119 // Json objects also have a type property that is not a string, so in the case our Value is also a Json we have to convert it to string first. 120 return x.type.to!string != "undefined"; 121 } 122 123 static string escape(Value[] args) { 124 auto value = args[0].get!string; 125 if (value.empty()) 126 return null; 127 128 foreach (f; args[1..$]) { 129 switch (f.get!string) { 130 case "html": 131 value = escapeHTML(value); 132 break; 133 case "js": 134 value = escapeJS(value); 135 break; 136 case "uri": 137 value = escapeURI(value); 138 break; 139 default: 140 assert("unimplemented escape filter: " ~ f.get!string); 141 break; 142 } 143 } 144 145 return value; 146 } 147 148 static string translate(Value[] args) { 149 return args[0].get!string; 150 } 151 152 static string replace(string x, string from, string to) { 153 return x.replace(from, to); 154 } 155 156 static string replaceAll(string x, string from, string to) { 157 return x.replaceAll(regex(from), to); 158 } 159 160 globals["length"] = Value(&length); 161 globals["empty"] = Value(&empty); 162 globals["keys"] = Value(&keys); 163 globals["values"] = Value(&values); 164 globals["front"] = Value(&front); 165 globals["back"] = Value(&back); 166 globals["take"] = Value(&take); 167 168 globals["integer"] = Value(&tointeger); 169 globals["float"] = Value(&tofloat); 170 globals["string"] = Value(&tostring); 171 globals["bool"] = Value(&tobool); 172 globals["type"] = Value(&type); 173 174 globals["get"] = Value(&get); 175 globals["has"] = Value((Value x, Value key) => x.has(key)); 176 globals["default"] = Value(&def); 177 178 globals["escape"] = Value(&escape); 179 180 globals["replace"] = Value(&replace); 181 globals["replaceAll"] = Value(&replaceAll); 182 globals["existsIn"] = Value(&existsIn); 183 184 globals["__escape"] = Value(&escape); 185 globals["__translate"] = Value(&translate); 186 } 187 188 189 void bindLibMath(ref Value[string] globals) { 190 import std.math; 191 192 static Value abs(Value x) { 193 final switch (x.type) with (Value.Type) { 194 case Undefined: 195 case Function: 196 case String: 197 case Array: 198 case AssocArray: 199 case Object: 200 case Pointer: 201 case Null: 202 case Bool: 203 return x; 204 case Integer: 205 return Value(std.math.abs(x.get!long)); 206 case Float: 207 return Value(std.math.abs(x.get!double)); 208 } 209 } 210 211 static Value min(Value[] args) { 212 size_t argMin; 213 214 foreach (i; 1..args.length) { 215 if (args[argMin].compareOp!">"(args[i])) 216 argMin = i; 217 } 218 219 return args[argMin]; 220 } 221 222 static Value max(Value[] args) { 223 size_t argMax; 224 225 foreach (i; 1..args.length) { 226 if (args[argMax].compareOp!"<"(args[i])) 227 argMax = i; 228 } 229 230 return args[argMax]; 231 } 232 233 globals["abs"] = Value(&abs); 234 globals["min"] = Value(&min); 235 globals["max"] = Value(&max); 236 } 237 238 239 void bindLibString(ref Value[string] globals) { 240 static string join(Value[] x) { 241 auto app = appender!string; 242 auto value = x[0]; 243 auto len = value.length; 244 auto seps = (x.length > 1) ? x[1].get!string : ","; 245 foreach (size_t i, v; value) { 246 app.put(v.get!string); 247 if (i + 1 != len) 248 app.put(seps); 249 } 250 return app.data; 251 } 252 253 static string[] split(Value[] x) { 254 auto value = x[0].get!string; 255 auto sep = (x.length > 1) ? x[1].get!string : " "; 256 257 return value.split(sep); 258 } 259 260 static string strip(Value x) { 261 return x.get!string.strip; 262 } 263 264 static string lower(Value x) { 265 return x.get!string.toLower(); 266 } 267 268 static string upper(Value x) { 269 return x.get!string.toUpper(); 270 } 271 272 static string capitalize(Value x) { 273 return x.get!string.capitalize(); 274 } 275 276 static long indexOf(Value[] args) { 277 if (!args[0].length) 278 return -1; 279 280 if (args[0].type == Value.Type.String) { 281 auto haystack = args[0].get!string; 282 auto needle = args[1].get!string; 283 auto start = (args.length > 2) ? args[2].get!size_t : 0; 284 return haystack.indexOf(needle, start); 285 } 286 287 foreach (size_t i, v; args[0]) { 288 if (v.compareOp!"=="(args[1])) 289 return cast(long)i; 290 } 291 292 return -1; 293 } 294 295 static string format(Value[] args) { 296 auto fmt = args[0].get!string; 297 auto spec = FormatSpec!char(fmt); 298 299 auto app = appender!string; 300 app.reserve(max(32, fmt.length + fmt.length >> 1)); 301 302 size_t arg = 1; 303 while (spec.writeUpToNextSpec(app)) { 304 if (arg >= args.length) 305 break; 306 307 auto value = args[arg++]; 308 final switch (value.type) with (Value.Type) { 309 case Undefined: 310 case Function: 311 case String: 312 case Array: 313 case AssocArray: 314 case Object: 315 case Pointer: 316 formatValue(&app, value.get!string, spec); 317 break; 318 case Null: 319 formatValue(&app, null, spec); 320 break; 321 case Bool: 322 formatValue(&app, value.get!bool, spec); 323 break; 324 case Integer: 325 formatValue(&app, value.get!long, spec); 326 break; 327 case Float: 328 formatValue(&app, value.get!double, spec); 329 break; 330 } 331 } 332 333 if (arg != args.length) 334 throw new Exception(std.format.format("number of arguments doesn't match number of format specifiers - expected %d, got %d", arg - 1, args.length - 1)); 335 return app.data; 336 } 337 338 globals["join"] = Value(&join); 339 globals["split"] = Value(&split); 340 globals["format"] = Value(&format); 341 globals["strip"] = Value(&strip); 342 globals["lower"] = Value(&lower); 343 globals["upper"] = Value(&upper); 344 globals["capitalize"] = Value(&capitalize); 345 globals["indexOf"] = Value(&indexOf); 346 } 347 348 349 void bindLibDefault(ref Value[string] globals) { 350 bindLibBasic(globals); 351 bindLibString(globals); 352 bindLibMath(globals); 353 } 354 355 356 string escapeHTML(string x) { 357 auto ptr = x.ptr; 358 const end = x.ptr + x.length; 359 360 while (ptr != end) { 361 auto ch = *ptr++; 362 if (ch > '>') continue; 363 364 switch (ch) { 365 case '"': 366 case '\'': 367 case '<': 368 case '>': 369 case '&': 370 const prefix = ptr - x.ptr - 1; 371 372 auto app = appender!string; 373 app.reserve(prefix + (x.length - prefix) * 2); 374 app.put(x.ptr[0..prefix]); 375 --ptr; 376 377 while (ptr != end) { 378 const pch = *ptr++; 379 switch (pch) { 380 case '"': 381 app.put("""); 382 break; 383 case '\'': 384 app.put("'"); 385 break; 386 case '<': 387 app.put("<"); 388 break; 389 case '>': 390 app.put(">"); 391 break; 392 case '&': 393 app.put("&"); 394 break; 395 default: 396 app.put(pch); 397 break; 398 } 399 } 400 return app.data; 401 default: 402 continue; 403 } 404 } 405 return x; 406 } 407 408 409 string escapeJS(string x) { 410 auto ptr = x.ptr; 411 const end = x.ptr + x.length; 412 413 while (ptr != end) { 414 auto ch = *ptr++; 415 if (ch > '\\') continue; 416 417 switch (ch) { 418 case '\\': 419 case '\'': 420 case '\"': 421 case '\r': 422 case '\n': 423 const prefix = ptr - x.ptr - 1; 424 425 auto app = appender!string; 426 app.reserve(prefix + 1 + (x.length - prefix) + (x.length - prefix) / 2); 427 app.put(x.ptr[0..prefix]); 428 --ptr; 429 430 while (ptr != end) { 431 const pch = *ptr++; 432 switch (pch) { 433 case '\\': 434 app.put(`\\`); 435 break; 436 case '\'': 437 app.put(`\'`); 438 break; 439 case '\"': 440 app.put(`\"`); 441 break; 442 case '\r': 443 break; 444 case '\n': 445 app.put(`\n`); 446 break; 447 default: 448 app.put(pch); 449 break; 450 } 451 } 452 return app.data; 453 default: 454 continue; 455 } 456 } 457 return x; 458 } 459 460 461 string escapeURI(string x) { 462 auto ptr = x.ptr; 463 const end = x.ptr + x.length; 464 465 while (ptr != end) { 466 auto ch = *ptr++; 467 468 switch (ch) { 469 case 'A': .. case 'Z': 470 case 'a': .. case 'z': 471 case '0': .. case '9': 472 case '-': case '_': case '.': case '~': 473 continue; 474 default: 475 const prefix = ptr - x.ptr - 1; 476 477 auto app = appender!string; 478 app.reserve(prefix + 1 + (x.length - prefix) + (x.length - prefix) / 2); 479 app.put(x.ptr[0..prefix]); 480 --ptr; 481 482 while (ptr != end) { 483 const pch = *ptr++; 484 switch (pch) { 485 case 'A': .. case 'Z': 486 case 'a': .. case 'z': 487 case '0': .. case '9': 488 case '-': case '_': case '.': case '~': 489 app.put(pch); 490 break; 491 default: 492 formattedWrite(&app, "%%%02X", pch); 493 break; 494 } 495 } 496 return app.data; 497 } 498 } 499 return x; 500 }