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 get(Value x, Value key, Value def) { 59 Value result; 60 if (x.has(key, &result)) 61 return result; 62 return def; 63 } 64 65 static Value def(Value x, Value def) { 66 if ((x.type == Value.Type.Undefined) || (x.type == Value.Type.Null)) 67 return def; 68 return x; 69 } 70 71 static long tointeger(Value x) { 72 return x.get!long; 73 } 74 75 static double tofloat(Value x) { 76 return x.get!double; 77 } 78 79 static string tostring(Value x) { 80 return x.toString(); 81 } 82 83 static bool tobool(Value x) { 84 return x.get!bool; 85 } 86 87 static string type(Value x) { 88 final switch (x.type) with (Value.Type) { 89 case Undefined: 90 return "undefined"; 91 case Null: 92 return "null"; 93 case Bool: 94 return "bool"; 95 case Integer: 96 return "integer"; 97 case Float: 98 return "float"; 99 case Function: 100 return "function"; 101 case String: 102 return "string"; 103 case Array: 104 return "array"; 105 case AssocArray: 106 return "assocarray"; 107 case Object: 108 return "object"; 109 case Pointer: 110 return "pointer"; 111 } 112 } 113 114 static bool exists(Value x) { 115 // 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. 116 return x.type.to!string != "undefined"; 117 } 118 119 static string escape(Value[] args) { 120 auto value = args[0].get!string; 121 122 foreach (f; args[1..$]) { 123 switch (f.get!string) { 124 case "html": 125 value = escapeHTML(value); 126 break; 127 case "js": 128 value = escapeJS(value); 129 break; 130 case "uri": 131 value = escapeURI(value); 132 break; 133 default: 134 assert("unimplemented escape filter: " ~ f.get!string); 135 break; 136 } 137 } 138 139 return value; 140 } 141 142 static string translate(Value[] args) { 143 return args[0].get!string; 144 } 145 146 static string replace(string x, string from, string to) { 147 return x.replace(from, to); 148 } 149 150 static string replaceAll(string x, string from, string to) { 151 return x.replaceAll(regex(from), to); 152 } 153 154 globals["length"] = Value(&length); 155 globals["empty"] = Value(&empty); 156 globals["keys"] = Value(&keys); 157 globals["values"] = Value(&values); 158 globals["front"] = Value(&front); 159 globals["back"] = Value(&back); 160 161 globals["integer"] = Value(&tointeger); 162 globals["float"] = Value(&tofloat); 163 globals["string"] = Value(&tostring); 164 globals["bool"] = Value(&tobool); 165 globals["type"] = Value(&type); 166 167 globals["get"] = Value(&get); 168 globals["has"] = Value((Value x, Value key) => x.has(key)); 169 globals["default"] = Value(&def); 170 171 globals["escape"] = Value(&escape); 172 173 globals["replace"] = Value(&replace); 174 globals["replaceAll"] = Value(&replaceAll); 175 globals["existsIn"] = Value(&existsIn); 176 177 globals["__escape"] = Value(&escape); 178 globals["__translate"] = Value(&translate); 179 } 180 181 182 void bindLibMath(ref Value[string] globals) { 183 import std.math; 184 185 static Value abs(Value x) { 186 final switch (x.type) with (Value.Type) { 187 case Undefined: 188 case Function: 189 case String: 190 case Array: 191 case AssocArray: 192 case Object: 193 case Pointer: 194 case Null: 195 case Bool: 196 return x; 197 case Integer: 198 return Value(std.math.abs(x.get!long)); 199 case Float: 200 return Value(std.math.abs(x.get!double)); 201 } 202 } 203 204 static Value min(Value[] args) { 205 size_t argMin; 206 207 foreach (i; 1..args.length) { 208 if (args[argMin].compareOp!">"(args[i])) 209 argMin = i; 210 } 211 212 return args[argMin]; 213 } 214 215 static Value max(Value[] args) { 216 size_t argMax; 217 218 foreach (i; 1..args.length) { 219 if (args[argMax].compareOp!"<"(args[i])) 220 argMax = i; 221 } 222 223 return args[argMax]; 224 } 225 226 globals["abs"] = Value(&abs); 227 globals["min"] = Value(&min); 228 globals["max"] = Value(&max); 229 } 230 231 232 void bindLibString(ref Value[string] globals) { 233 static string join(Value[] x) { 234 auto app = appender!string; 235 auto value = x[0]; 236 auto len = value.length; 237 auto seps = (x.length > 1) ? x[1].get!string : ","; 238 foreach (size_t i, v; value) { 239 app.put(v.get!string); 240 if (i + 1 != len) 241 app.put(seps); 242 } 243 return app.data; 244 } 245 246 static string[] split(Value[] x) { 247 auto value = x[0].get!string; 248 auto sep = (x.length > 1) ? x[1].get!string : " "; 249 250 return value.split(sep); 251 } 252 253 static string strip(Value x) { 254 return x.get!string.strip; 255 } 256 257 static string lower(Value x) { 258 return x.get!string.toLower(); 259 } 260 261 static string upper(Value x) { 262 return x.get!string.toUpper(); 263 } 264 265 static string capitalize(Value x) { 266 return x.get!string.capitalize(); 267 } 268 269 static long indexOf(Value[] args) { 270 if (!args[0].length) 271 return -1; 272 273 if (args[0].type == Value.Type.String) { 274 auto haystack = args[0].get!string; 275 auto needle = args[1].get!string; 276 auto start = (args.length > 2) ? args[2].get!size_t : 0; 277 return haystack.indexOf(needle, start); 278 } 279 280 foreach (size_t i, v; args[0]) { 281 if (v.compareOp!"=="(args[1])) 282 return cast(long)i; 283 } 284 285 return -1; 286 } 287 288 static string format(Value[] args) { 289 auto fmt = args[0].get!string; 290 auto spec = FormatSpec!char(fmt); 291 292 auto app = appender!string; 293 app.reserve(max(32, fmt.length + fmt.length >> 1)); 294 295 size_t arg = 1; 296 while (spec.writeUpToNextSpec(app)) { 297 if (arg >= args.length) 298 break; 299 300 auto value = args[arg++]; 301 final switch (value.type) with (Value.Type) { 302 case Undefined: 303 case Function: 304 case String: 305 case Array: 306 case AssocArray: 307 case Object: 308 case Pointer: 309 formatValue(&app, value.get!string, spec); 310 break; 311 case Null: 312 formatValue(&app, null, spec); 313 break; 314 case Bool: 315 formatValue(&app, value.get!bool, spec); 316 break; 317 case Integer: 318 formatValue(&app, value.get!long, spec); 319 break; 320 case Float: 321 formatValue(&app, value.get!double, spec); 322 break; 323 } 324 } 325 326 if (arg != args.length) 327 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)); 328 return app.data; 329 } 330 331 globals["join"] = Value(&join); 332 globals["split"] = Value(&split); 333 globals["format"] = Value(&format); 334 globals["strip"] = Value(&strip); 335 globals["lower"] = Value(&lower); 336 globals["upper"] = Value(&upper); 337 globals["capitalize"] = Value(&capitalize); 338 globals["indexOf"] = Value(&indexOf); 339 } 340 341 342 void bindLibDefault(ref Value[string] globals) { 343 bindLibBasic(globals); 344 bindLibString(globals); 345 bindLibMath(globals); 346 } 347 348 349 string escapeHTML(string x) { 350 auto app = appender!string; 351 app.reserve(8 + x.length + (x.length >> 1)); 352 353 foreach (ch; x.byDchar) { 354 switch (ch) { 355 case '"': 356 app.put("""); 357 break; 358 case '\'': 359 app.put("'"); 360 break; 361 case 'a': .. case 'z': 362 goto case; 363 case 'A': .. case 'Z': 364 goto case; 365 case '0': .. case '9': 366 goto case; 367 case ' ', '\t', '\n', '\r', '-', '_', '.', ':', ',', ';', 368 '#', '+', '*', '?', '=', '(', ')', '/', '!', 369 '%' , '{', '}', '[', ']', '$', '^', '~': 370 app.put(cast(char)ch); 371 break; 372 case '<': 373 app.put("<"); 374 break; 375 case '>': 376 app.put(">"); 377 break; 378 case '&': 379 app.put("&"); 380 break; 381 default: 382 formattedWrite(&app, "&#x%02X;", cast(uint)ch); 383 break; 384 } 385 } 386 return app.data; 387 } 388 389 390 string escapeJS(string x) { 391 auto app = appender!string; 392 app.reserve(x.length + (x.length >> 1)); 393 394 foreach (ch; x.byDchar) { 395 switch (ch) { 396 case '\\': 397 app.put(`\\`); 398 break; 399 case '\'': 400 app.put(`\'`); 401 break; 402 case '\"': 403 app.put(`\"`); 404 break; 405 case '\r': 406 break; 407 case '\n': 408 app.put(`\n`); 409 break; 410 default: 411 app.put(ch); 412 break; 413 } 414 } 415 return app.data; 416 } 417 418 419 string escapeURI(string x) { 420 auto app = appender!string; 421 app.reserve(8 + x.length + (x.length >> 1)); 422 423 foreach (i; 0..x.length) { 424 switch (x.ptr[i]) { 425 case 'A': .. case 'Z': 426 case 'a': .. case 'z': 427 case '0': .. case '9': 428 case '-': case '_': case '.': case '~': 429 app.put(x.ptr[i]); 430 break; 431 default: 432 formattedWrite(&app, "%%%02X", x.ptr[i]); 433 break; 434 } 435 } 436 437 return app.data; 438 }