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