quintype-liquid 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1532 @@
1
+ var Liquid = {
2
+
3
+ author: 'Matt McCray <darthapo@gmail.com>',
4
+ version: '1.3.2',
5
+
6
+ readTemplateFile: function(path) {
7
+ throw ("This liquid context does not allow includes.");
8
+ },
9
+
10
+ registerFilters: function(filters) {
11
+ Liquid.Template.registerFilter(filters);
12
+ },
13
+
14
+ parse: function(src) {
15
+ return Liquid.Template.parse(src);
16
+ }
17
+
18
+ };
19
+
20
+ Liquid.extensions = {};
21
+ Liquid.extensions.object = {};
22
+
23
+ Liquid.extensions.object.update = function(newObj) {
24
+ for (var p in newObj) {
25
+ this[p] = newObj[p];
26
+ }
27
+
28
+ return this;
29
+ };
30
+
31
+ Liquid.extensions.object.hasKey = function(arg) {
32
+ return !!this[arg];
33
+ };
34
+
35
+ Liquid.extensions.object.hasValue = function(arg) {
36
+ for (var p in this) {
37
+ if (this[p] == arg) return true;
38
+ }
39
+
40
+ return false;
41
+ };
42
+
43
+ Liquid.extensions.object.isEmpty = function(obj) {
44
+ if (!obj || Liquid.extensions.stringTools.strip(obj.toString()) === "") return true;
45
+ if (obj.length && obj.length > 0) return false;
46
+ if (typeof obj === 'number') return false;
47
+
48
+ for (var prop in obj) if (obj[prop]) return false;
49
+ return true;
50
+ };
51
+
52
+
53
+ Liquid.extensions.stringTools = {};
54
+ Liquid.extensions.stringTools.capitalize = function(str) {
55
+ return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
56
+ };
57
+
58
+ Liquid.extensions.stringTools.strip = function(str) {
59
+ return str.replace(/^\s+/, '').replace(/\s+$/, '');
60
+ };
61
+
62
+
63
+ Liquid.extensions.arrayTools = {};
64
+
65
+ Liquid.extensions.arrayTools.last = function(array) {
66
+ return array[array.length - 1];
67
+ };
68
+
69
+ Liquid.extensions.arrayTools.indexOf = function(array, obj) {
70
+ for (var i=0; i<array.length; i++) {
71
+ if (array[i] == obj) return i;
72
+ }
73
+ return -1;
74
+ };
75
+
76
+ Liquid.extensions.arrayTools.map = function(obj, fun) {
77
+ var len = obj.length;
78
+ if (typeof fun != "function")
79
+ throw 'Liquid.extensions.arrayTools.map requires first argument to be a function';
80
+
81
+ var res = new Array(len);
82
+ var thisp = arguments[2];
83
+ for (var i = 0; i < len; i++) {
84
+ if (i in obj)
85
+ res[i] = fun.call(thisp, obj[i], i, obj);
86
+ }
87
+ return res;
88
+ };
89
+
90
+ Liquid.extensions.arrayTools.flatten = function(array) {
91
+ var len = array.length;
92
+ var arr = [];
93
+ for (var i = 0; i < len; i++) {
94
+ if (array[i] instanceof Array) {
95
+ arr = arr.concat(array[i]);
96
+ } else {
97
+ arr.push(array[i]);
98
+ }
99
+ }
100
+
101
+ return arr;
102
+ };
103
+
104
+ Liquid.extensions.arrayTools.each = function(obj, fun) {
105
+ var len = obj.length;
106
+ if (typeof fun != "function") {
107
+ throw 'Liquid.extensions.arrayTools.each requires first argument to be a function';
108
+ }
109
+
110
+ var thisp = arguments[2];
111
+ for (var i = 0; i < len; i++) {
112
+ if (i in obj) {
113
+ fun.call(thisp, obj[i], i, obj);
114
+ }
115
+ }
116
+
117
+ return null;
118
+ };
119
+
120
+ Liquid.extensions.arrayTools.include = function(array, arg) {
121
+ var len = array.length;
122
+
123
+ return Liquid.extensions.arrayTools.indexOf(array, arg) >= 0;
124
+ for (var i = 0; i < len; i++) {
125
+ if (arg == array[i]) return true;
126
+ }
127
+
128
+ return false;
129
+ };
130
+ /* Simple JavaScript Inheritance
131
+ * By John Resig http://ejohn.org/
132
+ * MIT Licensed.
133
+ */
134
+ (function(){
135
+ var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
136
+
137
+ this.Class = function(){};
138
+
139
+ this.Class.extend = function(prop) {
140
+ var _super = this.prototype;
141
+
142
+ initializing = true;
143
+ var prototype = new this();
144
+ initializing = false;
145
+
146
+ for (var name in prop) {
147
+ prototype[name] = typeof prop[name] == "function" &&
148
+ typeof _super[name] == "function" && fnTest.test(prop[name]) ?
149
+ (function(name, fn){
150
+ return function() {
151
+ var tmp = this._super;
152
+
153
+ this._super = _super[name];
154
+
155
+ var ret = fn.apply(this, arguments);
156
+ this._super = tmp;
157
+
158
+ return ret;
159
+ };
160
+ })(name, prop[name]) :
161
+ prop[name];
162
+ }
163
+
164
+ function Class() {
165
+ if ( !initializing && this.init )
166
+ this.init.apply(this, arguments);
167
+ }
168
+
169
+ Class.prototype = prototype;
170
+
171
+ Class.prototype.constructor = Class;
172
+
173
+ Class.extend = arguments.callee;
174
+
175
+ return Class;
176
+ };
177
+ }).call(Liquid);
178
+
179
+ Liquid.Tag = Liquid.Class.extend({
180
+
181
+ init: function(tagName, markup, tokens) {
182
+ this.tagName = tagName;
183
+ this.markup = markup;
184
+ this.nodelist = this.nodelist || [];
185
+ this.parse(tokens);
186
+ },
187
+
188
+ parse: function(tokens) {
189
+ },
190
+
191
+ render: function(context) {
192
+ return '';
193
+ }
194
+
195
+ });
196
+ Liquid.Block = Liquid.Tag.extend({
197
+
198
+ init: function(tagName, markup, tokens){
199
+ this.blockName = tagName;
200
+ this.blockDelimiter = "end"+ this.blockName;
201
+ this._super(tagName, markup, tokens);
202
+ },
203
+
204
+ parse: function(tokens) {
205
+ if (!this.nodelist) this.nodelist = [];
206
+ this.nodelist.length = 0;
207
+
208
+ var token = tokens.shift();
209
+ tokens.push(''); // To ensure we don't lose the last token passed in...
210
+ while(tokens.length) {
211
+
212
+ if( /^\{\%/.test(token) ) { // It's a tag...
213
+ var tagParts = token.match(/^\{\%\s*(\w+)\s*(.*)?\%\}$/);
214
+
215
+ if(tagParts) {
216
+ if( this.blockDelimiter == tagParts[1] ) {
217
+ this.endTag();
218
+ return;
219
+ }
220
+ if( tagParts[1] in Liquid.Template.tags ) {
221
+ this.nodelist.push( new Liquid.Template.tags[tagParts[1]]( tagParts[1], tagParts[2], tokens ) );
222
+ } else {
223
+ this.unknownTag( tagParts[1], tagParts[2], tokens );
224
+ }
225
+ } else {
226
+ throw ( "Tag '"+ token +"' was not properly terminated with: %}");
227
+ }
228
+ } else if(/^\{\{/.test(token)) { // It's a variable...
229
+ this.nodelist.push( this.createVariable(token) );
230
+ } else { //if(token != '') {
231
+ this.nodelist.push( token );
232
+ } // Ignores tokens that are empty
233
+ token = tokens.shift(); // Assign the next token to loop again...
234
+ }
235
+
236
+ this.assertMissingDelimitation();
237
+ },
238
+
239
+ endTag: function() {},
240
+
241
+ unknownTag: function(tag, params, tokens) {
242
+ switch(tag) {
243
+ case 'else': throw (this.blockName +" tag does not expect else tag"); break;
244
+ case 'end': throw ("'end' is not a valid delimiter for "+ this.blockName +" tags. use "+ this.blockDelimiter); break;
245
+ default: throw ("Unknown tag: "+ tag);
246
+ }
247
+ },
248
+
249
+ createVariable: function(token) {
250
+ var match = token.match(/^\{\{(.*)\}\}$/);
251
+ if(match) { return new Liquid.Variable(match[1]); }
252
+ else { throw ("Variable '"+ token +"' was not properly terminated with: }}"); }
253
+ },
254
+
255
+ render: function(context) {
256
+ return this.renderAll(this.nodelist, context);
257
+ },
258
+
259
+ renderAll: function(list, context) {
260
+ return Liquid.extensions.arrayTools.map((list || []), function(token, i){
261
+ var output = '';
262
+ try { // hmmm... feels a little heavy
263
+ output = ( token['render'] ) ? token.render(context) : token;
264
+ } catch(e) {
265
+ output = context.handleError(e);
266
+ }
267
+ return output;
268
+ });
269
+ },
270
+
271
+ assertMissingDelimitation: function(){
272
+ throw (this.blockName +" tag was never closed");
273
+ }
274
+ });
275
+ Liquid.Document = Liquid.Block.extend({
276
+
277
+ init: function(tokens){
278
+ this.blockDelimiter = []; // [], really?
279
+ this.parse(tokens);
280
+ },
281
+
282
+ assertMissingDelimitation: function() {
283
+ }
284
+ });
285
+ Liquid.Strainer = Liquid.Class.extend({
286
+
287
+ init: function(context) {
288
+ this.context = context;
289
+ },
290
+
291
+ respondTo: function(methodName) {
292
+ methodName = methodName.toString();
293
+ if (methodName.match(/^__/)) return false;
294
+ if (Liquid.extensions.arrayTools.include(Liquid.Strainer.requiredMethods, methodName)) return false;
295
+ return (methodName in this);
296
+ }
297
+ });
298
+
299
+ Liquid.Strainer.filters = {};
300
+
301
+ Liquid.Strainer.globalFilter = function(filters) {
302
+ for (var f in filters) {
303
+ Liquid.Strainer.filters[f] = filters[f];
304
+ }
305
+ }
306
+
307
+ Liquid.Strainer.requiredMethods = ['respondTo', 'context'];
308
+
309
+ Liquid.Strainer.create = function(context) {
310
+ var strainer = new Liquid.Strainer(context);
311
+ for (var f in Liquid.Strainer.filters) {
312
+ strainer[f] = Liquid.Strainer.filters[f];
313
+ }
314
+ return strainer;
315
+ }
316
+ Liquid.Context = Liquid.Class.extend({
317
+
318
+ init: function(assigns, registers, rethrowErrors) {
319
+ this.scopes = [ assigns ? assigns : {} ];
320
+ this.registers = registers ? registers : {};
321
+ this.errors = [];
322
+ this.rethrowErrors = rethrowErrors;
323
+ this.strainer = Liquid.Strainer.create(this);
324
+ },
325
+
326
+ get: function(varname) {
327
+ return this.resolve(varname);
328
+ },
329
+
330
+ set: function(varname, value) {
331
+ this.scopes[0][varname] = value;
332
+ },
333
+
334
+ hasKey: function(key) {
335
+ return (this.resolve(key)) ? true : false;
336
+ },
337
+
338
+ push: function() {
339
+ var scpObj = {};
340
+ this.scopes.unshift(scpObj);
341
+ return scpObj // Is this right?
342
+ },
343
+
344
+ merge: function(newScope) {
345
+ return Liquid.extensions.object.update.call(this.scopes[0], newScope);
346
+ },
347
+
348
+ pop: function() {
349
+ if(this.scopes.length == 1){ throw "Context stack error"; }
350
+ return this.scopes.shift();
351
+ },
352
+
353
+ stack: function(lambda, bind) {
354
+ var result = null;
355
+ this.push();
356
+ try {
357
+ result = lambda.apply(bind ? bind : this.strainer);
358
+ } finally {
359
+ this.pop();
360
+ }
361
+ return result;
362
+ },
363
+
364
+ invoke: function(method, args) {
365
+ if( this.strainer.respondTo(method) ) {
366
+ var result = this.strainer[method].apply(this.strainer, args);
367
+ return result;
368
+ } else {
369
+ return (args.length == 0) ? null : args[0]; // was: $pick
370
+ }
371
+ },
372
+
373
+ resolve: function(key) {
374
+ switch(key) {
375
+ case null:
376
+ case 'nil':
377
+ case 'null':
378
+ case '':
379
+ return null;
380
+
381
+ case 'true':
382
+ return true;
383
+
384
+ case 'false':
385
+ return false;
386
+
387
+ case 'blank':
388
+ case 'empty':
389
+ return '';
390
+
391
+ default:
392
+ if((/^'(.*)'$/).test(key)) // Single quoted strings
393
+ { return key.replace(/^'(.*)'$/, '$1'); }
394
+
395
+ else if((/^"(.*)"$/).test(key)) // Double quoted strings
396
+ { return key.replace(/^"(.*)"$/, '$1'); }
397
+
398
+ else if((/^(\d+)$/).test(key)) // Integer...
399
+ { return parseInt( key.replace(/^(\d+)$/ , '$1') ); }
400
+
401
+ else if((/^(\d[\d\.]+)$/).test(key)) // Float...
402
+ { return parseFloat( key.replace(/^(\d[\d\.]+)$/, '$1') ); }
403
+
404
+ else if((/^\((\S+)\.\.(\S+)\)$/).test(key)) {// Ranges
405
+ var range = key.match(/^\((\S+)\.\.(\S+)\)$/),
406
+ left = parseInt(range[1]),
407
+ right = parseInt(range[2]),
408
+ arr = [];
409
+ if (isNaN(left) || isNaN(right)) {
410
+ left = range[1].charCodeAt(0);
411
+ right = range[2].charCodeAt(0);
412
+
413
+ var limit = right-left+1;
414
+ for (var i=0; i<limit; i++) arr.push(String.fromCharCode(i+left));
415
+ } else { // okay to make array
416
+ var limit = right-left+1;
417
+ for (var i=0; i<limit; i++) arr.push(i+left);
418
+ }
419
+ return arr;
420
+ } else {
421
+ var result = this.variable(key);
422
+ return result;
423
+ }
424
+ }
425
+ },
426
+
427
+ findVariable: function(key) {
428
+ for (var i=0; i < this.scopes.length; i++) {
429
+ var scope = this.scopes[i];
430
+ if( scope && typeof(scope[key]) !== 'undefined' ) {
431
+ var variable = scope[key];
432
+ if(typeof(variable) == 'function'){
433
+ variable = variable.apply(this);
434
+ scope[key] = variable;
435
+ }
436
+ if(variable && this._isObject(variable) && ('toLiquid' in variable)) {
437
+ variable = variable.toLiquid();
438
+ }
439
+ if(variable && this._isObject(variable) && ('setContext' in variable)){
440
+ variable.setContext(self);
441
+ }
442
+ return variable;
443
+ }
444
+ };
445
+ return null;
446
+ },
447
+
448
+ variable: function(markup) {
449
+ if(typeof markup != 'string') {
450
+ return null;
451
+ }
452
+
453
+ var parts = markup.match( /\[[^\]]+\]|(?:[\w\-]\??)+/g ),
454
+ firstPart = parts.shift(),
455
+ squareMatch = firstPart.match(/^\[(.*)\]$/);
456
+
457
+ if(squareMatch)
458
+ { firstPart = this.resolve( squareMatch[1] ); }
459
+
460
+ var object = this.findVariable(firstPart),
461
+ self = this;
462
+
463
+ if(object) {
464
+ Liquid.extensions.arrayTools.each(parts, function(part){
465
+ var squareMatch = part.match(/^\[(.*)\]$/);
466
+ if(squareMatch) {
467
+ var part = self.resolve( squareMatch[1] );
468
+ if( typeof(object[part]) == 'function'){ object[part] = object[part].apply(this); }// Array?
469
+ object = object[part];
470
+ if(self._isObject(object) && ('toLiquid' in object)){ object = object.toLiquid(); }
471
+ } else {
472
+ if( (self._isObject(object) || typeof(object) == 'hash') && (part in object)) {
473
+ var res = object[part];
474
+ if( typeof(res) == 'function'){ res = object[part] = res.apply(self) ; }
475
+ if(self._isObject(res) && ('toLiquid' in res)){ object = res.toLiquid(); }
476
+ else { object = res; }
477
+ }
478
+ else if( (/^\d+$/).test(part) ) {
479
+ var pos = parseInt(part);
480
+ if( typeof(object[pos]) == 'function') { object[pos] = object[pos].apply(self); }
481
+ if(self._isObject(object) && self._isObject(object[pos]) && ('toLiquid' in object[pos])) { object = object[pos].toLiquid(); }
482
+ else { object = object[pos]; }
483
+ }
484
+ else if( object && typeof(object[part]) == 'function' && Liquid.extensions.arrayTools.include(['length', 'size', 'first', 'last'], part) ) {
485
+ object = object[part].apply(part);
486
+ if('toLiquid' in object){ object = object.toLiquid(); }
487
+ }
488
+ else {
489
+ return object = null;
490
+ }
491
+ if(self._isObject(object) && ('setContext' in object)){ object.setContext(self); }
492
+ }
493
+ });
494
+ }
495
+ return object;
496
+ },
497
+
498
+ addFilters: function(filters) {
499
+ filters = Liquid.extensions.arrayTools.flatten(filters);
500
+ Liquid.extensions.arrayTools.each(filters, function(f){
501
+ if(!this._isObject(f)){ throw ("Expected object but got: "+ typeof(f)) }
502
+ this.strainer.addMethods(f);
503
+ });
504
+ },
505
+
506
+ handleError: function(err) {
507
+ this.errors.push(err);
508
+ if(this.rethrowErrors){ throw err; }
509
+ return "Liquid error: " + (err.message ? err.message : (err.description ? err.description : err));
510
+ },
511
+
512
+ _isObject: function(obj) {
513
+ return obj != null && typeof(obj) == 'object';
514
+ }
515
+
516
+ });
517
+ Liquid.Template = Liquid.Class.extend({
518
+
519
+ init: function() {
520
+ this.root = null;
521
+ this.registers = {};
522
+ this.assigns = {};
523
+ this.errors = [];
524
+ this.rethrowErrors = false;
525
+ },
526
+
527
+ parse: function(src) {
528
+ this.root = new Liquid.Document( Liquid.Template.tokenize(src) );
529
+ return this;
530
+ },
531
+
532
+ render: function() {
533
+ if(!this.root){ return ''; }
534
+ var args = {
535
+ ctx: arguments[0],
536
+ filters: arguments[1],
537
+ registers: arguments[2]
538
+ }
539
+ var context = null;
540
+
541
+ if(args.ctx instanceof Liquid.Context ) {
542
+ context = args.ctx;
543
+ this.assigns = context.assigns;
544
+ this.registers = context.registers;
545
+ } else {
546
+ if(args.ctx){
547
+ Liquid.extensions.object.update.call(this.assigns, args.ctx);
548
+ }
549
+ if(args.registers){
550
+ Liquid.extensions.object.update.call(this.registers, args.registers);
551
+ }
552
+ context = new Liquid.Context(this.assigns, this.registers, this.rethrowErrors)
553
+ }
554
+
555
+ if(args.filters){ context.addFilters(arg.filters); }
556
+
557
+ try {
558
+ return this.root.render(context).join('');
559
+ } finally {
560
+ this.errors = context.errors;
561
+ }
562
+ },
563
+
564
+ renderWithErrors: function() {
565
+ var savedRethrowErrors = this.rethrowErrors;
566
+ this.rethrowErrors = true;
567
+ var res = this.render.apply(this, arguments);
568
+ this.rethrowErrors = savedRethrowErrors;
569
+ return res;
570
+ }
571
+ });
572
+
573
+
574
+ Liquid.Template.tags = {};
575
+
576
+ Liquid.Template.registerTag = function(name, klass) {
577
+ Liquid.Template.tags[ name ] = klass;
578
+ }
579
+
580
+ Liquid.Template.registerFilter = function(filters) {
581
+ Liquid.Strainer.globalFilter(filters)
582
+ }
583
+
584
+ Liquid.Template.tokenize = function(src) {
585
+ var tokens = src.split( /(\{\%.*?\%\}|\{\{.*?\}\}?)/ );
586
+ if(tokens[0] == ''){ tokens.shift(); }
587
+ return tokens;
588
+ }
589
+
590
+
591
+ Liquid.Template.parse = function(src) {
592
+ return (new Liquid.Template()).parse(src);
593
+ }
594
+ Liquid.Variable = Liquid.Class.extend({
595
+
596
+ init: function(markup) {
597
+ this.markup = markup;
598
+ this.name = null;
599
+ this.filters = [];
600
+ var self = this;
601
+ var match = markup.match(/\s*("[^"]+"|'[^']+'|[^\s,|]+)/);
602
+ if( match ) {
603
+ this.name = match[1];
604
+ var filterMatches = markup.match(/\|\s*(.*)/);
605
+ if(filterMatches) {
606
+ var filters = filterMatches[1].split(/\|/);
607
+ Liquid.extensions.arrayTools.each(filters, function(f){
608
+ var matches = f.match(/\s*(\w+)/);
609
+ if(matches) {
610
+ var filterName = matches[1];
611
+ var filterArgs = [];
612
+ Liquid.extensions.arrayTools.each(Liquid.extensions.arrayTools.flatten((f.match(/(?:[:|,]\s*)("[^"]+"|'[^']+'|[^\s,|]+)/g) || [])), function(arg){
613
+ var cleanupMatch = arg.match(/^[\s|:|,]*(.*?)[\s]*$/);
614
+ if(cleanupMatch)
615
+ { filterArgs.push( cleanupMatch[1] );}
616
+ });
617
+ self.filters.push( [filterName, filterArgs] );
618
+ }
619
+ });
620
+ }
621
+ }
622
+ },
623
+
624
+ render: function(context) {
625
+ if(this.name == null){ return ''; }
626
+ var output = context.get(this.name);
627
+ Liquid.extensions.arrayTools.each(this.filters, function(filter) {
628
+ var filterName = filter[0],
629
+ filterArgs = Liquid.extensions.arrayTools.map((filter[1] || []), function(arg){
630
+ return context.get(arg);
631
+ });
632
+ filterArgs.unshift(output); // Push in input value into the first argument spot...
633
+ output = context.invoke(filterName, filterArgs);
634
+ });
635
+
636
+ return output;
637
+ }
638
+ });
639
+ Liquid.Condition = Liquid.Class.extend({
640
+
641
+ init: function(left, operator, right) {
642
+ this.left = left;
643
+ this.operator = operator;
644
+ this.right = right;
645
+ this.childRelation = null;
646
+ this.childCondition = null;
647
+ this.attachment = null;
648
+ },
649
+
650
+ evaluate: function(context) {
651
+ context = context || new Liquid.Context();
652
+ var result = this.interpretCondition(this.left, this.right, this.operator, context);
653
+ switch(this.childRelation) {
654
+ case 'or':
655
+ return (result || this.childCondition.evaluate(context));
656
+ case 'and':
657
+ return (result && this.childCondition.evaluate(context));
658
+ default:
659
+ return result;
660
+ }
661
+ },
662
+
663
+ or: function(condition) {
664
+ this.childRelation = 'or';
665
+ this.childCondition = condition;
666
+ },
667
+
668
+ and: function(condition) {
669
+ this.childRelation = 'and';
670
+ this.childCondition = condition;
671
+ },
672
+
673
+ attach: function(attachment) {
674
+ this.attachment = attachment;
675
+ return this.attachment;
676
+ },
677
+
678
+ isElse: false,
679
+
680
+ interpretCondition: function(left, right, op, context) {
681
+ if(!op)
682
+ { return context.get(left); }
683
+
684
+ left = context.get(left);
685
+ right = context.get(right);
686
+ op = Liquid.Condition.operators[op];
687
+ if(!op)
688
+ { throw ("Unknown operator "+ op); }
689
+
690
+ var results = op(left, right);
691
+ return results;
692
+ },
693
+
694
+ toString: function() {
695
+ return "<Condition "+ this.left +" "+ this.operator +" "+ this.right +">";
696
+ }
697
+
698
+ });
699
+
700
+ Liquid.Condition.operators = {
701
+ '==': function(l,r) { return (l == r); },
702
+ '=': function(l,r) { return (l == r); },
703
+ '!=': function(l,r) { return (l != r); },
704
+ '<>': function(l,r) { return (l != r); },
705
+ '<': function(l,r) { return (l < r); },
706
+ '>': function(l,r) { return (l > r); },
707
+ '<=': function(l,r) { return (l <= r); },
708
+ '>=': function(l,r) { return (l >= r); },
709
+
710
+ 'contains': function(l,r) {
711
+ if ( Object.prototype.toString.call(l) === '[object Array]' ) {
712
+ return Liquid.extensions.arrayTools.indexOf(l, r) >= 0;
713
+ } else {
714
+ return l.match(r);
715
+ }
716
+ },
717
+ 'hasKey': function(l,r) { return Liquid.extensions.object.hasKey.call(l, r); },
718
+ 'hasValue': function(l,r) { return Liquid.extensions.object.hasValue.call(l, r); }
719
+ }
720
+
721
+ Liquid.ElseCondition = Liquid.Condition.extend({
722
+
723
+ isElse: true,
724
+
725
+ evaluate: function(context) {
726
+ return true;
727
+ },
728
+
729
+ toString: function() {
730
+ return "<ElseCondition>";
731
+ }
732
+
733
+ });
734
+ Liquid.Drop = Liquid.Class.extend({
735
+ setContext: function(context) {
736
+ this.context = context;
737
+ },
738
+ beforeMethod: function(method) {
739
+
740
+ },
741
+ invokeDrop: function(method) {
742
+ var results = this.beforeMethod();
743
+ if( !results && (method in this) )
744
+ { results = this[method].apply(this); }
745
+ return results;
746
+ },
747
+ hasKey: function(name) {
748
+ return true;
749
+ }
750
+ });
751
+ var hackObjectEach = function(fun /*, thisp*/) {
752
+ if (typeof fun != "function")
753
+ throw 'Object.each requires first argument to be a function';
754
+
755
+ var i = 0;
756
+ var thisp = arguments[1];
757
+ for (var p in this) {
758
+ var value = this[p], pair = [p, value];
759
+ pair.key = p;
760
+ pair.value = value;
761
+ fun.call(thisp, pair, i, this);
762
+ i++;
763
+ }
764
+
765
+ return null;
766
+ };
767
+
768
+ Liquid.Template.registerTag( 'assign', Liquid.Tag.extend({
769
+
770
+ tagSyntax: /((?:\(?[\w\-\.\[\]]\)?)+)\s*=\s*(.+)/,
771
+
772
+ init: function(tagName, markup, tokens) {
773
+ var parts = markup.match(this.tagSyntax);
774
+ if( parts ) {
775
+ this.to = parts[1];
776
+ this.from = parts[2];
777
+ } else {
778
+ throw ("Syntax error in 'assign' - Valid syntax: assign [var] = [source]");
779
+ }
780
+ this._super(tagName, markup, tokens)
781
+ },
782
+ render: function(context) {
783
+ var value = new Liquid.Variable(this.from);
784
+ Liquid.extensions.arrayTools.last(context.scopes)[this.to.toString()] = value.render(context);
785
+ return '';
786
+ }
787
+ }));
788
+
789
+ Liquid.Template.registerTag( 'cache', Liquid.Block.extend({
790
+ tagSyntax: /(\w+)/,
791
+
792
+ init: function(tagName, markup, tokens) {
793
+ var parts = markup.match(this.tagSyntax)
794
+ if( parts ) {
795
+ this.to = parts[1];
796
+ } else {
797
+ throw ("Syntax error in 'cache' - Valid syntax: cache [var]");
798
+ }
799
+ this._super(tagName, markup, tokens);
800
+ },
801
+ render: function(context) {
802
+ var output = this._super(context);
803
+ Liquid.extensions.arrayTools.last(context.scopes)[this.to] = Liquid.extensions.arrayTools.flatten([output]).join('');
804
+ return '';
805
+ }
806
+ }));
807
+
808
+
809
+ Liquid.Template.registerTag( 'capture', Liquid.Block.extend({
810
+ tagSyntax: /(\w+)/,
811
+
812
+ init: function(tagName, markup, tokens) {
813
+ var parts = markup.match(this.tagSyntax)
814
+ if( parts ) {
815
+ this.to = parts[1];
816
+ } else {
817
+ throw ("Syntax error in 'capture' - Valid syntax: capture [var]");
818
+ }
819
+ this._super(tagName, markup, tokens);
820
+ },
821
+ render: function(context) {
822
+ var output = this._super(context);
823
+ Liquid.extensions.arrayTools.last(context.scopes)[this.to.toString()] = Liquid.extensions.arrayTools.flatten([output]).join('');
824
+ return '';
825
+ }
826
+ }));
827
+
828
+ Liquid.Template.registerTag( 'case', Liquid.Block.extend({
829
+
830
+ tagSyntax : /("[^"]+"|'[^']+'|[^\s,|]+)/,
831
+ tagWhenSyntax : /("[^"]+"|'[^']+'|[^\s,|]+)(?:(?:\s+or\s+|\s*\,\s*)("[^"]+"|'[^']+'|[^\s,|]+.*))?/,
832
+
833
+ init: function(tagName, markup, tokens) {
834
+ this.blocks = [];
835
+ this.nodelist = [];
836
+
837
+ var parts = markup.match(this.tagSyntax)
838
+ if( parts ) {
839
+ this.left = parts[1];
840
+ } else {
841
+ throw ("Syntax error in 'case' - Valid syntax: case [condition]");
842
+ }
843
+
844
+ this._super(tagName, markup, tokens);
845
+ },
846
+ unknownTag: function(tag, markup, tokens) {
847
+ switch(tag) {
848
+ case 'when':
849
+ this.recordWhenCondition(markup);
850
+ break;
851
+ case 'else':
852
+ this.recordElseCondition(markup);
853
+ break;
854
+ default:
855
+ this._super(tag, markup, tokens);
856
+ }
857
+
858
+ },
859
+ render: function(context) {
860
+ var self = this,
861
+ output = [],
862
+ execElseBlock = true;
863
+
864
+ context.stack(function(){
865
+ for (var i=0; i < self.blocks.length; i++) {
866
+ var block = self.blocks[i];
867
+ if( block.isElse ) {
868
+ if(execElseBlock == true){ output = Liquid.extensions.arrayTools.flatten([output, self.renderAll(block.attachment, context)]); }
869
+ return output;
870
+ } else if( block.evaluate(context) ) {
871
+ execElseBlock = false;
872
+ output = Liquid.extensions.arrayTools.flatten([output, self.renderAll(block.attachment, context)]);
873
+ }
874
+ };
875
+ });
876
+
877
+ return output;
878
+ },
879
+ recordWhenCondition: function(markup) {
880
+ while(markup) {
881
+ var parts = markup.match(this.tagWhenSyntax);
882
+ if(!parts) {
883
+ throw ("Syntax error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %} ");
884
+ }
885
+
886
+ markup = parts[2];
887
+
888
+ var block = new Liquid.Condition(this.left, '==', parts[1]);
889
+ this.blocks.push( block );
890
+ this.nodelist = block.attach([]);
891
+ }
892
+ },
893
+ recordElseCondition: function(markup) {
894
+ if( Liquid.extensions.stringTools.strip((markup || '')) != '') {
895
+ throw ("Syntax error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
896
+ }
897
+ var block = new Liquid.ElseCondition();
898
+ this.blocks.push(block);
899
+ this.nodelist = block.attach([]);
900
+ }
901
+ }));
902
+
903
+ Liquid.Template.registerTag( 'comment', Liquid.Block.extend({
904
+ render: function(context) {
905
+ return '';
906
+ }
907
+ }));
908
+
909
+ Liquid.Template.registerTag( 'cycle', Liquid.Tag.extend({
910
+
911
+ tagSimpleSyntax: /"[^"]+"|'[^']+'|[^\s,|]+/,
912
+ tagNamedSyntax: /("[^"]+"|'[^']+'|[^\s,|]+)\s*\:\s*(.*)/,
913
+
914
+ init: function(tag, markup, tokens) {
915
+ var matches, variables;
916
+ matches = markup.match(this.tagNamedSyntax);
917
+ if(matches) {
918
+ this.variables = this.variablesFromString(matches[2]);
919
+ this.name = matches[1];
920
+ } else {
921
+ matches = markup.match(this.tagSimpleSyntax);
922
+ if(matches) {
923
+ this.variables = this.variablesFromString(markup);
924
+ this.name = "'"+ this.variables.toString() +"'";
925
+ } else {
926
+ throw ("Syntax error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]");
927
+ }
928
+ }
929
+ this._super(tag, markup, tokens);
930
+ },
931
+
932
+ render: function(context) {
933
+ var self = this,
934
+ key = context.get(self.name),
935
+ output = '';
936
+
937
+ if(!context.registers['cycle']) {
938
+ context.registers['cycle'] = {};
939
+ }
940
+
941
+ if(!context.registers['cycle'][key]) {
942
+ context.registers['cycle'][key] = 0;
943
+ }
944
+
945
+ context.stack(function(){
946
+ var iter = context.registers['cycle'][key],
947
+ results = context.get( self.variables[iter] );
948
+ iter += 1;
949
+ if(iter == self.variables.length){ iter = 0; }
950
+ context.registers['cycle'][key] = iter;
951
+ output = results;
952
+ });
953
+
954
+ return output;
955
+ },
956
+
957
+ variablesFromString: function(markup) {
958
+ return Liquid.extensions.arrayTools.map(markup.split(','), function(varname){
959
+ var match = varname.match(/\s*("[^"]+"|'[^']+'|[^\s,|]+)\s*/);
960
+ return (match[1]) ? match[1] : null
961
+ });
962
+ }
963
+ }));
964
+
965
+ Liquid.Template.registerTag( 'for', Liquid.Block.extend({
966
+ tagSyntax: /(\w+)\s+in\s+((?:\(?[\w\-\.\[\]]\)?)+)/,
967
+
968
+ init: function(tag, markup, tokens) {
969
+ var matches = markup.match(this.tagSyntax);
970
+ if(matches) {
971
+ this.variableName = matches[1];
972
+ this.collectionName = matches[2];
973
+ this.name = this.variableName +"-"+ this.collectionName;
974
+ this.attributes = {};
975
+ var attrmarkup = markup.replace(this.tagSyntax, '');
976
+ var attMatchs = markup.match(/(\w*?)\s*\:\s*("[^"]+"|'[^']+'|[^\s,|]+)/g);
977
+ if(attMatchs) {
978
+ Liquid.extensions.arrayTools.each(attMatchs, function(pair){
979
+ pair = pair.split(":");
980
+ this.attributes[Liquid.extensions.stringTools.strip(pair[0])] = Liquid.extensions.stringTools.strip(pair[1]);
981
+ }, this);
982
+ }
983
+ } else {
984
+ throw ("Syntax error in 'for loop' - Valid syntax: for [item] in [collection]");
985
+ }
986
+ this._super(tag, markup, tokens);
987
+ },
988
+
989
+ render: function(context) {
990
+ var self = this,
991
+ output = [],
992
+ collection = (context.get(this.collectionName) || []),
993
+ range = [0, collection.length];
994
+
995
+ if(!context.registers['for']){ context.registers['for'] = {}; }
996
+
997
+ if(this.attributes['limit'] || this.attributes['offset']) {
998
+ var offset = 0,
999
+ limit = 0,
1000
+ rangeEnd = 0,
1001
+ segment = null;
1002
+
1003
+ if(this.attributes['offset'] == 'continue')
1004
+ { offset = context.registers['for'][this.name]; }
1005
+ else
1006
+ { offset = context.get( this.attributes['offset'] ) || 0; }
1007
+
1008
+ limit = context.get( this.attributes['limit'] );
1009
+
1010
+ rangeEnd = (limit) ? offset + limit + 1 : collection.length;
1011
+ range = [ offset, rangeEnd - 1 ];
1012
+
1013
+ context.registers['for'][this.name] = rangeEnd;
1014
+ }
1015
+
1016
+ segment = collection.slice(range[0], range[1]);
1017
+ if(!segment || segment.length == 0){ return ''; }
1018
+
1019
+ context.stack(function(){
1020
+ var length = segment.length;
1021
+
1022
+ Liquid.extensions.arrayTools.each(segment, function(item, index){
1023
+ context.set( self.variableName, item );
1024
+ context.set( 'forloop', {
1025
+ name: self.name,
1026
+ length: length,
1027
+ index: (index + 1),
1028
+ index0: index,
1029
+ rindex: (length - index),
1030
+ rindex0:(length - index - 1),
1031
+ first: (index == 0),
1032
+ last: (index == (length - 1))
1033
+ });
1034
+ output.push( (self.renderAll(self.nodelist, context) || []).join('') );
1035
+ });
1036
+ });
1037
+
1038
+ return Liquid.extensions.arrayTools.flatten([output]).join('');
1039
+ }
1040
+ }));
1041
+
1042
+ Liquid.Template.registerTag( 'if', Liquid.Block.extend({
1043
+
1044
+ tagSyntax: /("[^"]+"|'[^']+'|[^\s,|]+)\s*([=!<>a-z_]+)?\s*("[^"]+"|'[^']+'|[^\s,|]+)?/,
1045
+
1046
+ init: function(tag, markup, tokens) {
1047
+ this.nodelist = [];
1048
+ this.blocks = [];
1049
+ this.pushBlock('if', markup);
1050
+ this._super(tag, markup, tokens);
1051
+ },
1052
+
1053
+ unknownTag: function(tag, markup, tokens) {
1054
+ if( Liquid.extensions.arrayTools.include(['elsif', 'else'], tag) ) {
1055
+ this.pushBlock(tag, markup);
1056
+ } else {
1057
+ this._super(tag, markup, tokens);
1058
+ }
1059
+ },
1060
+
1061
+ render: function(context) {
1062
+ var self = this,
1063
+ output = '';
1064
+ context.stack(function(){
1065
+ for (var i=0; i < self.blocks.length; i++) {
1066
+ var block = self.blocks[i];
1067
+ if( block.evaluate(context) ) {
1068
+ output = self.renderAll(block.attachment, context);
1069
+ return;
1070
+ }
1071
+ };
1072
+ })
1073
+ return Liquid.extensions.arrayTools.flatten([output]).join('');
1074
+ },
1075
+
1076
+ pushBlock: function(tag, markup) {
1077
+ var block;
1078
+ if(tag == 'else') {
1079
+ block = new Liquid.ElseCondition();
1080
+ } else {
1081
+ var expressions = markup.split(/\b(and|or)\b/).reverse(),
1082
+ expMatches = expressions.shift().match( this.tagSyntax );
1083
+
1084
+ if(!expMatches){ throw ("Syntax Error in tag '"+ tag +"' - Valid syntax: "+ tag +" [expression]"); }
1085
+
1086
+ var condition = new Liquid.Condition(expMatches[1], expMatches[2], expMatches[3]);
1087
+
1088
+ while(expressions.length > 0) {
1089
+ var operator = expressions.shift(),
1090
+ expMatches = expressions.shift().match( this.tagSyntax );
1091
+ if(!expMatches){ throw ("Syntax Error in tag '"+ tag +"' - Valid syntax: "+ tag +" [expression]"); }
1092
+
1093
+ var newCondition = new Liquid.Condition(expMatches[1], expMatches[2], expMatches[3]);
1094
+ newCondition[operator](condition);
1095
+ condition = newCondition;
1096
+ }
1097
+
1098
+ block = condition;
1099
+ }
1100
+ block.attach([]);
1101
+ this.blocks.push(block);
1102
+ this.nodelist = block.attachment;
1103
+ }
1104
+ }));
1105
+
1106
+ Liquid.Template.registerTag( 'ifchanged', Liquid.Block.extend({
1107
+
1108
+ render: function(context) {
1109
+ var self = this,
1110
+ output = '';
1111
+ context.stack(function(){
1112
+ var results = self.renderAll(self.nodelist, context).join('');
1113
+ if(results != context.registers['ifchanged']) {
1114
+ output = results;
1115
+ context.registers['ifchanged'] = output;
1116
+ }
1117
+ });
1118
+ return output;
1119
+ }
1120
+ }));
1121
+
1122
+ Liquid.Template.registerTag( 'include', Liquid.Tag.extend({
1123
+
1124
+ tagSyntax: /((?:"[^"]+"|'[^']+'|[^\s,|]+)+)(\s+(?:with|for)\s+((?:"[^"]+"|'[^']+'|[^\s,|]+)+))?/,
1125
+
1126
+ init: function(tag, markup, tokens) {
1127
+ var matches = (markup || '').match(this.tagSyntax);
1128
+ if(matches) {
1129
+ this.templateName = matches[1];
1130
+ this.templateNameVar = this.templateName.substring(1, this.templateName.length - 1);
1131
+ this.variableName = matches[3];
1132
+ this.attributes = {};
1133
+
1134
+ var attMatchs = markup.match(/(\w*?)\s*\:\s*("[^"]+"|'[^']+'|[^\s,|]+)/g);
1135
+ if(attMatchs) {
1136
+ Liquid.extensions.arrayTools.each(attMatchs, function(pair){
1137
+ pair = pair.split(":");
1138
+ this.attributes[Liquid.extensions.stringTools.strip(pair[0])] = Liquid.extensions.stringTools.strip(pair[1]);
1139
+ }, this);
1140
+ }
1141
+ } else {
1142
+ throw ("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]");
1143
+ }
1144
+ this._super(tag, markup, tokens);
1145
+ },
1146
+
1147
+ render: function(context) {
1148
+ var self = this,
1149
+ source = Liquid.readTemplateFile( context.get(this.templateName) ),
1150
+ partial = Liquid.parse(source),
1151
+ variable = context.get((this.variableName || this.templateNameVar)),
1152
+ output = '';
1153
+ context.stack(function(){
1154
+ self.attributes.each = hackObjectEach;
1155
+ self.attributes.each(function(pair){
1156
+ context.set(pair.key, context.get(pair.value));
1157
+ })
1158
+
1159
+ if(variable instanceof Array) {
1160
+ output = Liquid.extensions.arrayTools.map(variable, function(variable){
1161
+ context.set( self.templateNameVar, variable );
1162
+ return partial.render(context);
1163
+ });
1164
+ } else {
1165
+ context.set(self.templateNameVar, variable);
1166
+ output = partial.render(context);
1167
+ }
1168
+ });
1169
+ output = Liquid.extensions.arrayTools.flatten([output]).join('');
1170
+ return output
1171
+ }
1172
+ }));
1173
+
1174
+ Liquid.Template.registerTag( 'unless', Liquid.Template.tags['if'].extend({
1175
+
1176
+ render: function(context) {
1177
+ var self = this,
1178
+ output = '';
1179
+ context.stack(function(){
1180
+ var block = self.blocks[0];
1181
+ if( !block.evaluate(context) ) {
1182
+ output = self.renderAll(block.attachment, context);
1183
+ return;
1184
+ }
1185
+ for (var i=1; i < self.blocks.length; i++) {
1186
+ var block = self.blocks[i];
1187
+ if( block.evaluate(context) ) {
1188
+ output = self.renderAll(block.attachment, context);
1189
+ return;
1190
+ }
1191
+ };
1192
+ })
1193
+ return Liquid.extensions.arrayTools.flatten([output]).join('');
1194
+ }
1195
+ }));
1196
+
1197
+ Liquid.Template.registerTag( 'raw', Liquid.Block.extend({
1198
+ parse: function(tokens) {
1199
+ if (!this.nodelist) this.nodelist = [];
1200
+ this.nodelist.length = 0;
1201
+
1202
+ var token = tokens.shift();
1203
+ tokens.push('');
1204
+ while(tokens.length) {
1205
+
1206
+ if( /^\{\%/.test(token) ) { // It's a tag...
1207
+ var tagParts = token.match(/^\{\%\s*(\w+)\s*(.*)?\%\}$/);
1208
+
1209
+ if(tagParts) {
1210
+ if( this.blockDelimiter == tagParts[1] ) {
1211
+ this.endTag();
1212
+ return;
1213
+ }
1214
+ }
1215
+ }
1216
+
1217
+ this.nodelist.push( token || '');
1218
+ token = tokens.shift(); // Assign the next token to loop again...
1219
+ }
1220
+ this.assertMissingDelimitation();
1221
+ },
1222
+
1223
+ render: function(context) {
1224
+ return this.nodelist.join('');
1225
+ }
1226
+ }));
1227
+ Liquid.Template.registerFilter({
1228
+
1229
+ _HTML_ESCAPE_MAP: {
1230
+ '&': '&amp;',
1231
+ '>': '&gt;',
1232
+ '<': '&lt;',
1233
+ '"': '&quot;',
1234
+ "'": '&#39;'
1235
+ },
1236
+
1237
+ size: function(iterable) {
1238
+ return (iterable['length']) ? iterable.length : 0;
1239
+ },
1240
+
1241
+ downcase: function(input) {
1242
+ return input.toString().toLowerCase();
1243
+ },
1244
+
1245
+ upcase: function(input) {
1246
+ return input.toString().toUpperCase();
1247
+ },
1248
+
1249
+ capitalize: function(input) {
1250
+ return Liquid.extensions.stringTools.capitalize(input.toString());
1251
+ },
1252
+
1253
+ escape: function(input) {
1254
+ var self = this;
1255
+ return input.replace(/[&<>"']/g, function(chr) {
1256
+ return self._HTML_ESCAPE_MAP[chr];
1257
+ });
1258
+ },
1259
+
1260
+ h: function(input) {
1261
+ var self = this;
1262
+ return input.replace(/[&<>"']/g, function(chr) {
1263
+ return self._HTML_ESCAPE_MAP[chr];
1264
+ });
1265
+ },
1266
+
1267
+ default: function(input, default_value) {
1268
+ return Liquid.extensions.object.isEmpty(input) ? default_value : input;
1269
+ },
1270
+
1271
+ truncate: function(input, length, string) {
1272
+ if(!input || input == ''){ return ''; }
1273
+ length = length || 50;
1274
+ string = string || "...";
1275
+
1276
+ var seg = input.slice(0, length);
1277
+ return (input.length > length ?
1278
+ input.slice(0, length) + string :
1279
+ input);
1280
+ },
1281
+
1282
+ truncatewords: function(input, words, string) {
1283
+ if(!input || input == ''){ return ''; }
1284
+ words = parseInt(words || 15);
1285
+ string = string || '...';
1286
+ var wordlist = input.toString().split(" "),
1287
+ l = Math.max((words), 0);
1288
+ return (wordlist.length > l) ? wordlist.slice(0,l).join(' ') + string : input;
1289
+ },
1290
+
1291
+ truncate_words: function(input, words, string) {
1292
+ if(!input || input == ''){ return ''; }
1293
+ words = parseInt(words || 15);
1294
+ string = string || '...';
1295
+ var wordlist = input.toString().split(" "),
1296
+ l = Math.max((words), 0);
1297
+ return (wordlist.length > l) ? wordlist.slice(0,l).join(' ') + string : input;
1298
+ },
1299
+
1300
+ strip_html: function(input) {
1301
+ return input.toString().replace(/<.*?>/g, '');
1302
+ },
1303
+
1304
+ strip_newlines: function(input) {
1305
+ return input.toString().replace(/\n/g, '')
1306
+ },
1307
+
1308
+ join: function(input, separator) {
1309
+ separator = separator || ' ';
1310
+ return input.join(separator);
1311
+ },
1312
+
1313
+ split: function(input, separator) {
1314
+ separator = separator || ' ';
1315
+ return input.split(separator);
1316
+ },
1317
+
1318
+ sort: function(input) {
1319
+ return input.sort();
1320
+ },
1321
+
1322
+ reverse: function(input) {
1323
+ return input.reverse();
1324
+ },
1325
+
1326
+ replace: function(input, string, replacement) {
1327
+ replacement = replacement || '';
1328
+ return input.toString().replace(new RegExp(string, 'g'), replacement);
1329
+ },
1330
+
1331
+ replace_first: function(input, string, replacement) {
1332
+ replacement = replacement || '';
1333
+ return input.toString().replace(new RegExp(string, ""), replacement);
1334
+ },
1335
+
1336
+ newline_to_br: function(input) {
1337
+ return input.toString().replace(/\n/g, "<br/>\n");
1338
+ },
1339
+
1340
+ date: function(input, format) {
1341
+ var date;
1342
+ if( input instanceof Date ){ date = input; }
1343
+ if(!(date instanceof Date) && input == 'now'){ date = new Date(); }
1344
+ if(!(date instanceof Date) && typeof(input) == 'number'){ date = new Date(input * 1000); }
1345
+ if(!(date instanceof Date) && typeof(input) == 'string'){ date = new Date(Date.parse(input));}
1346
+ if(!(date instanceof Date)){ return input; } // Punt
1347
+ return date.strftime(format);
1348
+ },
1349
+
1350
+ first: function(input) {
1351
+ return input[0];
1352
+ },
1353
+
1354
+ last: function(input) {
1355
+ input = input;
1356
+ return input[input.length -1];
1357
+ },
1358
+
1359
+ minus: function(input, number) {
1360
+ return (Number(input) || 0) - (Number(number) || 0);
1361
+ },
1362
+
1363
+ plus: function(input, number) {
1364
+ return (Number(input) || 0) + (Number(number) || 0);
1365
+ },
1366
+
1367
+ times: function(input, number) {
1368
+ return (Number(input) || 0) * (Number(number) || 0);
1369
+ },
1370
+
1371
+ divided_by: function(input, number) {
1372
+ return (Number(input) || 0) / (Number(number) || 0);
1373
+ },
1374
+
1375
+ modulo: function(input, number) {
1376
+ return (Number(input) || 0) % (Number(number) || 0);
1377
+ },
1378
+
1379
+ map: function(input, property) {
1380
+ input = input || [];
1381
+ var results = [];
1382
+ for (var i = 0; i < input.length; i++) {
1383
+ results.push(input[i][property]);
1384
+ }
1385
+ return results;
1386
+ },
1387
+ escape_once: function(input) {
1388
+ var self = this;
1389
+ return input.replace(/["><']|&(?!([a-zA-Z]+|(#\d+));)/g, function(chr) {
1390
+ return self._HTML_ESCAPE_MAP[chr];
1391
+ });
1392
+ },
1393
+
1394
+ remove: function(input, string) {
1395
+ return input.toString().replace(new RegExp(string, 'g'), '');
1396
+ },
1397
+
1398
+ remove_first: function(input, string) {
1399
+ return input.toString().replace(string, '');
1400
+ },
1401
+
1402
+ prepend: function(input, string) {
1403
+ return '' + (string || '').toString() + (input || '').toString();
1404
+ },
1405
+
1406
+ append: function(input, string) {
1407
+ return '' + (input || '').toString() + (string || '').toString();
1408
+ }
1409
+
1410
+ });
1411
+
1412
+
1413
+ if(!(new Date()).strftime) {(function(){
1414
+ Date.ext={};Date.ext.util={};Date.ext.util.xPad=function(x,pad,r){if(typeof (r)=="undefined"){r=10}for(;parseInt(x,10)<r&&r>1;r/=10){x=pad.toString()+x}return x.toString()};Date.prototype.locale="en-GB";if(document.getElementsByTagName("html")&&document.getElementsByTagName("html")[0].lang){Date.prototype.locale=document.getElementsByTagName("html")[0].lang}Date.ext.locales={};Date.ext.locales.en={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],x:"%d/%m/%y",X:"%T"};
1415
+ if(typeof JSON != 'undefined'){ Date.ext.locales['en-US'] = JSON.parse(JSON.stringify(Date.ext.locales.en)); } else { Date.ext.locales["en-US"]=Date.ext.locales.en;};
1416
+ Date.ext.locales["en-US"].c="%a %d %b %Y %r %Z";Date.ext.locales["en-US"].x="%D";Date.ext.locales["en-US"].X="%r";Date.ext.locales["en-GB"]=Date.ext.locales.en;Date.ext.locales["en-AU"]=Date.ext.locales["en-GB"];Date.ext.formats={a:function(d){return Date.ext.locales[d.locale].a[d.getDay()]},A:function(d){return Date.ext.locales[d.locale].A[d.getDay()]},b:function(d){return Date.ext.locales[d.locale].b[d.getMonth()]},B:function(d){return Date.ext.locales[d.locale].B[d.getMonth()]},c:"toLocaleString",C:function(d){return Date.ext.util.xPad(parseInt(d.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(d){return Date.ext.util.xPad(parseInt(Date.ext.util.G(d)/100,10),0)},G:function(d){var y=d.getFullYear();var V=parseInt(Date.ext.formats.V(d),10);var W=parseInt(Date.ext.formats.W(d),10);if(W>V){y++}else{if(W===0&&V>=52){y--}}return y},H:["getHours","0"],I:function(d){var I=d.getHours()%12;return Date.ext.util.xPad(I===0?12:I,0)},j:function(d){var ms=d-new Date(""+d.getFullYear()+"/1/1 GMT");ms+=d.getTimezoneOffset()*60000;var doy=parseInt(ms/60000/60/24,10)+1;return Date.ext.util.xPad(doy,0,100)},m:function(d){return Date.ext.util.xPad(d.getMonth()+1,0)},M:["getMinutes","0"],p:function(d){return Date.ext.locales[d.locale].p[d.getHours()>=12?1:0]},P:function(d){return Date.ext.locales[d.locale].P[d.getHours()>=12?1:0]},S:["getSeconds","0"],u:function(d){var dow=d.getDay();return dow===0?7:dow},U:function(d){var doy=parseInt(Date.ext.formats.j(d),10);var rdow=6-d.getDay();var woy=parseInt((doy+rdow)/7,10);return Date.ext.util.xPad(woy,0)},V:function(d){var woy=parseInt(Date.ext.formats.W(d),10);var dow1_1=(new Date(""+d.getFullYear()+"/1/1")).getDay();var idow=woy+(dow1_1>4||dow1_1<=1?0:1);if(idow==53&&(new Date(""+d.getFullYear()+"/12/31")).getDay()<4){idow=1}else{if(idow===0){idow=Date.ext.formats.V(new Date(""+(d.getFullYear()-1)+"/12/31"))}}return Date.ext.util.xPad(idow,0)},w:"getDay",W:function(d){var doy=parseInt(Date.ext.formats.j(d),10);var rdow=7-Date.ext.formats.u(d);var woy=parseInt((doy+rdow)/7,10);return Date.ext.util.xPad(woy,0,10)},y:function(d){return Date.ext.util.xPad(d.getFullYear()%100,0)},Y:"getFullYear",z:function(d){var o=d.getTimezoneOffset();var H=Date.ext.util.xPad(parseInt(Math.abs(o/60),10),0);var M=Date.ext.util.xPad(o%60,0);return(o>0?"-":"+")+H+M},Z:function(d){return d.toString().replace(/^.*\(([^)]+)\)$/,"$1")},"%":function(d){return"%"}};Date.ext.aggregates={c:"locale",D:"%m/%d/%y",h:"%b",n:"\n",r:"%I:%M:%S %p",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"};Date.ext.aggregates.z=Date.ext.formats.z(new Date());Date.ext.aggregates.Z=Date.ext.formats.Z(new Date());Date.ext.unsupported={};Date.prototype.strftime=function(fmt){if(!(this.locale in Date.ext.locales)){if(this.locale.replace(/-[a-zA-Z]+$/,"") in Date.ext.locales){this.locale=this.locale.replace(/-[a-zA-Z]+$/,"")}else{this.locale="en-GB"}}var d=this;while(fmt.match(/%[cDhnrRtTxXzZ]/)){fmt=fmt.replace(/%([cDhnrRtTxXzZ])/g,function(m0,m1){var f=Date.ext.aggregates[m1];return(f=="locale"?Date.ext.locales[d.locale][m1]:f)})}var str=fmt.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g,function(m0,m1){var f=Date.ext.formats[m1];if(typeof (f)=="string"){return d[f]()}else{if(typeof (f)=="function"){return f.call(d,d)}else{if(typeof (f)=="object"&&typeof (f[0])=="string"){return Date.ext.util.xPad(d[f[0]](),f[1])}else{return m1}}}});d=null;return str};
1417
+ })();}
1418
+ /*!
1419
+ * Cross-Browser Split 1.1.1
1420
+ * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
1421
+ * Available under the MIT License
1422
+ * ECMAScript compliant, uniform cross-browser split method
1423
+ */
1424
+
1425
+ /**
1426
+ * Splits a string into an array of strings using a regex or string separator. Matches of the
1427
+ * separator are not included in the result array. However, if `separator` is a regex that contains
1428
+ * capturing groups, backreferences are spliced into the result each time `separator` is matched.
1429
+ * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
1430
+ * cross-browser.
1431
+ * @param {String} str String to split.
1432
+ * @param {RegExp|String} separator Regex or string to use for separating the string.
1433
+ * @param {Number} [limit] Maximum number of items to include in the result array.
1434
+ * @returns {Array} Array of substrings.
1435
+ * @example
1436
+ *
1437
+ * // Basic use
1438
+ * split('a b c d', ' ');
1439
+ * // -> ['a', 'b', 'c', 'd']
1440
+ *
1441
+ * // With limit
1442
+ * split('a b c d', ' ', 2);
1443
+ * // -> ['a', 'b']
1444
+ *
1445
+ * // Backreferences in result array
1446
+ * split('..word1 word2..', /([a-z]+)(\d+)/i);
1447
+ * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
1448
+ */
1449
+ var split;
1450
+
1451
+ split = split || function (undef) {
1452
+
1453
+ var nativeSplit = String.prototype.split,
1454
+ compliantExecNpcg = /()??/.exec("")[1] === undef, // NPCG: nonparticipating capturing group
1455
+ self;
1456
+
1457
+ self = function (str, separator, limit) {
1458
+ if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
1459
+ return nativeSplit.call(str, separator, limit);
1460
+ }
1461
+ var output = [],
1462
+ flags = (separator.ignoreCase ? "i" : "") +
1463
+ (separator.multiline ? "m" : "") +
1464
+ (separator.extended ? "x" : "") + // Proposed for ES6
1465
+ (separator.sticky ? "y" : ""), // Firefox 3+
1466
+ lastLastIndex = 0,
1467
+ separator = new RegExp(separator.source, flags + "g"),
1468
+ separator2, match, lastIndex, lastLength;
1469
+ str += ""; // Type-convert
1470
+ if (!compliantExecNpcg) {
1471
+ separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
1472
+ }
1473
+ /* Values for `limit`, per the spec:
1474
+ * If undefined: 4294967295 // Math.pow(2, 32) - 1
1475
+ * If 0, Infinity, or NaN: 0
1476
+ * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
1477
+ * If negative number: 4294967296 - Math.floor(Math.abs(limit))
1478
+ * If other: Type-convert, then use the above rules
1479
+ */
1480
+ limit = limit === undef ?
1481
+ -1 >>> 0 : // Math.pow(2, 32) - 1
1482
+ limit >>> 0; // ToUint32(limit)
1483
+ while (match = separator.exec(str)) {
1484
+ lastIndex = match.index + match[0].length;
1485
+ if (lastIndex > lastLastIndex) {
1486
+ output.push(str.slice(lastLastIndex, match.index));
1487
+ if (!compliantExecNpcg && match.length > 1) {
1488
+ match[0].replace(separator2, function () {
1489
+ for (var i = 1; i < arguments.length - 2; i++) {
1490
+ if (arguments[i] === undef) {
1491
+ match[i] = undef;
1492
+ }
1493
+ }
1494
+ });
1495
+ }
1496
+ if (match.length > 1 && match.index < str.length) {
1497
+ Array.prototype.push.apply(output, match.slice(1));
1498
+ }
1499
+ lastLength = match[0].length;
1500
+ lastLastIndex = lastIndex;
1501
+ if (output.length >= limit) {
1502
+ break;
1503
+ }
1504
+ }
1505
+ if (separator.lastIndex === match.index) {
1506
+ separator.lastIndex++; // Avoid an infinite loop
1507
+ }
1508
+ }
1509
+ if (lastLastIndex === str.length) {
1510
+ if (lastLength || !separator.test("")) {
1511
+ output.push("");
1512
+ }
1513
+ } else {
1514
+ output.push(str.slice(lastLastIndex));
1515
+ }
1516
+ return output.length > limit ? output.slice(0, limit) : output;
1517
+ };
1518
+
1519
+ String.prototype.split = function (separator, limit) {
1520
+ return self(this, separator, limit);
1521
+ };
1522
+
1523
+ return self;
1524
+
1525
+ }();
1526
+
1527
+ if (typeof exports !== 'undefined') {
1528
+ if (typeof module !== 'undefined' && module.exports) {
1529
+ exports = module.exports = Liquid;
1530
+ }
1531
+ exports.Liquid = Liquid;
1532
+ }