smt_rails 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module SmtRails
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
3
3
  end
@@ -2,535 +2,621 @@
2
2
  * mustache.js - Logic-less {{mustache}} templates with JavaScript
3
3
  * http://github.com/janl/mustache.js
4
4
  */
5
- var Mustache = (typeof module !== "undefined" && module.exports) || {};
6
5
 
7
- (function (exports) {
8
-
9
- exports.name = "mustache.js";
10
- exports.version = "0.5.0-dev";
11
- exports.tags = ["{{", "}}"];
12
- exports.parse = parse;
13
- exports.compile = compile;
14
- exports.render = render;
15
- exports.clearCache = clearCache;
16
-
17
- // This is here for backwards compatibility with 0.4.x.
18
- exports.to_html = function (template, view, partials, send) {
19
- var result = render(template, view, partials);
20
-
21
- if (typeof send === "function") {
22
- send(result);
23
- } else {
24
- return result;
25
- }
26
- };
6
+ /*global define: false*/
27
7
 
28
- var _toString = Object.prototype.toString;
29
- var _isArray = Array.isArray;
30
- var _forEach = Array.prototype.forEach;
31
- var _trim = String.prototype.trim;
8
+ var Mustache;
32
9
 
33
- var isArray;
34
- if (_isArray) {
35
- isArray = _isArray;
10
+ (function (exports) {
11
+ if (typeof module !== "undefined" && module.exports) {
12
+ module.exports = exports; // CommonJS
13
+ } else if (typeof define === "function") {
14
+ define(exports); // AMD
36
15
  } else {
37
- isArray = function (obj) {
38
- return _toString.call(obj) === "[object Array]";
39
- };
16
+ Mustache = exports; // <script>
40
17
  }
18
+ }((function () {
41
19
 
42
- var forEach;
43
- if (_forEach) {
44
- forEach = function (obj, callback, scope) {
45
- return _forEach.call(obj, callback, scope);
46
- };
47
- } else {
48
- forEach = function (obj, callback, scope) {
49
- for (var i = 0, len = obj.length; i < len; ++i) {
50
- callback.call(scope, obj[i], i, obj);
51
- }
52
- };
53
- }
20
+ var exports = {};
54
21
 
55
- var spaceRe = /^\s*$/;
22
+ exports.name = "mustache.js";
23
+ exports.version = "0.7.0";
24
+ exports.tags = ["{{", "}}"];
56
25
 
57
- function isWhitespace(string) {
58
- return spaceRe.test(string);
26
+ exports.Scanner = Scanner;
27
+ exports.Context = Context;
28
+ exports.Writer = Writer;
29
+
30
+ var whiteRe = /\s*/;
31
+ var spaceRe = /\s+/;
32
+ var nonSpaceRe = /\S/;
33
+ var eqRe = /\s*=/;
34
+ var curlyRe = /\s*\}/;
35
+ var tagRe = /#|\^|\/|>|\{|&|=|!/;
36
+
37
+ // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
38
+ // See https://github.com/janl/mustache.js/issues/189
39
+ function testRe(re, string) {
40
+ return RegExp.prototype.test.call(re, string);
59
41
  }
60
42
 
61
- var trim;
62
- if (_trim) {
63
- trim = function (string) {
64
- return string == null ? "" : _trim.call(string);
65
- };
66
- } else {
67
- var trimLeft, trimRight;
43
+ function isWhitespace(string) {
44
+ return !testRe(nonSpaceRe, string);
45
+ }
68
46
 
69
- if (isWhitespace("\xA0")) {
70
- trimLeft = /^\s+/;
71
- trimRight = /\s+$/;
72
- } else {
73
- // IE doesn't match non-breaking spaces with \s, thanks jQuery.
74
- trimLeft = /^[\s\xA0]+/;
75
- trimRight = /[\s\xA0]+$/;
76
- }
47
+ var isArray = Array.isArray || function (obj) {
48
+ return Object.prototype.toString.call(obj) === "[object Array]";
49
+ };
77
50
 
78
- trim = function (string) {
79
- return string == null ? "" :
80
- String(string).replace(trimLeft, "").replace(trimRight, "");
81
- };
51
+ function escapeRe(string) {
52
+ return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
82
53
  }
83
54
 
84
- var escapeMap = {
55
+ var entityMap = {
85
56
  "&": "&amp;",
86
57
  "<": "&lt;",
87
58
  ">": "&gt;",
88
59
  '"': '&quot;',
89
- "'": '&#39;'
60
+ "'": '&#39;',
61
+ "/": '&#x2F;'
90
62
  };
91
63
 
92
- function escapeHTML(string) {
93
- return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
94
- return escapeMap[s] || s;
64
+ function escapeHtml(string) {
65
+ return String(string).replace(/[&<>"'\/]/g, function (s) {
66
+ return entityMap[s];
95
67
  });
96
68
  }
97
69
 
70
+ // Export the escaping function so that the user may override it.
71
+ // See https://github.com/janl/mustache.js/issues/244
72
+ exports.escape = escapeHtml;
73
+
74
+ function Scanner(string) {
75
+ this.string = string;
76
+ this.tail = string;
77
+ this.pos = 0;
78
+ }
79
+
98
80
  /**
99
- * Adds the `template`, `line`, and `file` properties to the given error
100
- * object and alters the message to provide more useful debugging information.
81
+ * Returns `true` if the tail is empty (end of string).
101
82
  */
102
- function debug(e, template, line, file) {
103
- file = file || "<template>";
104
-
105
- var lines = template.split("\n"),
106
- start = Math.max(line - 3, 0),
107
- end = Math.min(lines.length, line + 3),
108
- context = lines.slice(start, end);
109
-
110
- var c;
111
- for (var i = 0, len = context.length; i < len; ++i) {
112
- c = i + start + 1;
113
- context[i] = (c === line ? " >> " : " ") + context[i];
114
- }
83
+ Scanner.prototype.eos = function () {
84
+ return this.tail === "";
85
+ };
115
86
 
116
- e.template = template;
117
- e.line = line;
118
- e.file = file;
119
- e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
87
+ /**
88
+ * Tries to match the given regular expression at the current position.
89
+ * Returns the matched text if it can match, the empty string otherwise.
90
+ */
91
+ Scanner.prototype.scan = function (re) {
92
+ var match = this.tail.match(re);
120
93
 
121
- return e;
122
- }
94
+ if (match && match.index === 0) {
95
+ this.tail = this.tail.substring(match[0].length);
96
+ this.pos += match[0].length;
97
+ return match[0];
98
+ }
99
+
100
+ return "";
101
+ };
123
102
 
124
103
  /**
125
- * Looks up the value of the given `name` in the given context `stack`.
104
+ * Skips all text until the given regular expression can be matched. Returns
105
+ * the skipped string, which is the entire tail if no match can be made.
126
106
  */
127
- function lookup(name, stack, defaultValue) {
128
- if (name === ".") {
129
- return stack[stack.length - 1];
107
+ Scanner.prototype.scanUntil = function (re) {
108
+ var match, pos = this.tail.search(re);
109
+
110
+ switch (pos) {
111
+ case -1:
112
+ match = this.tail;
113
+ this.pos += this.tail.length;
114
+ this.tail = "";
115
+ break;
116
+ case 0:
117
+ match = "";
118
+ break;
119
+ default:
120
+ match = this.tail.substring(0, pos);
121
+ this.tail = this.tail.substring(pos);
122
+ this.pos += pos;
130
123
  }
131
124
 
132
- var names = name.split(".");
133
- var lastIndex = names.length - 1;
134
- var target = names[lastIndex];
125
+ return match;
126
+ };
135
127
 
136
- var value, context, i = stack.length, j, localStack;
137
- while (i) {
138
- localStack = stack.slice(0);
139
- context = stack[--i];
128
+ function Context(view, parent) {
129
+ this.view = view;
130
+ this.parent = parent;
131
+ this.clearCache();
132
+ }
140
133
 
141
- j = 0;
142
- while (j < lastIndex) {
143
- context = context[names[j++]];
134
+ Context.make = function (view) {
135
+ return (view instanceof Context) ? view : new Context(view);
136
+ };
144
137
 
145
- if (context == null) {
146
- break;
147
- }
138
+ Context.prototype.clearCache = function () {
139
+ this._cache = {};
140
+ };
148
141
 
149
- localStack.push(context);
150
- }
142
+ Context.prototype.push = function (view) {
143
+ return new Context(view, this);
144
+ };
151
145
 
152
- if (context && typeof context === "object" && target in context) {
153
- value = context[target];
154
- break;
146
+ Context.prototype.lookup = function (name) {
147
+ var value = this._cache[name];
148
+
149
+ if (!value) {
150
+ if (name === ".") {
151
+ value = this.view;
152
+ } else {
153
+ var context = this;
154
+
155
+ while (context) {
156
+ if (name.indexOf(".") > 0) {
157
+ var names = name.split("."), i = 0;
158
+
159
+ value = context.view;
160
+
161
+ while (value && i < names.length) {
162
+ value = value[names[i++]];
163
+ }
164
+ } else {
165
+ value = context.view[name];
166
+ }
167
+
168
+ if (value != null) {
169
+ break;
170
+ }
171
+
172
+ context = context.parent;
173
+ }
155
174
  }
156
- }
157
175
 
158
- // If the value is a function, call it in the current context.
159
- if (typeof value === "function") {
160
- value = value.call(localStack[localStack.length - 1]);
176
+ this._cache[name] = value;
161
177
  }
162
178
 
163
- if (value == null) {
164
- return defaultValue;
179
+ if (typeof value === "function") {
180
+ value = value.call(this.view);
165
181
  }
166
182
 
167
183
  return value;
184
+ };
185
+
186
+ function Writer() {
187
+ this.clearCache();
168
188
  }
169
189
 
170
- function renderSection(name, stack, callback, inverted) {
171
- var buffer = "";
172
- var value = lookup(name, stack);
190
+ Writer.prototype.clearCache = function () {
191
+ this._cache = {};
192
+ this._partialCache = {};
193
+ };
194
+
195
+ Writer.prototype.compile = function (template, tags) {
196
+ var fn = this._cache[template];
173
197
 
174
- if (inverted) {
175
- // From the spec: inverted sections may render text once based on the
176
- // inverse value of the key. That is, they will be rendered if the key
177
- // doesn't exist, is false, or is an empty list.
178
- if (value == null || value === false || (isArray(value) && value.length === 0)) {
179
- buffer += callback();
180
- }
181
- } else if (isArray(value)) {
182
- forEach(value, function (value) {
183
- stack.push(value);
184
- buffer += callback();
185
- stack.pop();
186
- });
187
- } else if (typeof value === "object") {
188
- stack.push(value);
189
- buffer += callback();
190
- stack.pop();
191
- } else if (typeof value === "function") {
192
- var scope = stack[stack.length - 1];
193
- var scopedRender = function (template) {
194
- return render(template, scope);
195
- };
196
- buffer += value.call(scope, callback(), scopedRender) || "";
197
- } else if (value) {
198
- buffer += callback();
198
+ if (!fn) {
199
+ var tokens = exports.parse(template, tags);
200
+ fn = this._cache[template] = this.compileTokens(tokens, template);
199
201
  }
200
202
 
201
- return buffer;
202
- }
203
+ return fn;
204
+ };
203
205
 
204
- /**
205
- * Parses the given `template` and returns the source of a function that,
206
- * with the proper arguments, will render the template. Recognized options
207
- * include the following:
208
- *
209
- * - file The name of the file the template comes from (displayed in
210
- * error messages)
211
- * - tags An array of open and close tags the `template` uses. Defaults
212
- * to the value of Mustache.tags
213
- * - debug Set `true` to log the body of the generated function to the
214
- * console
215
- * - space Set `true` to preserve whitespace from lines that otherwise
216
- * contain only a {{tag}}. Defaults to `false`
217
- */
218
- function parse(template, options) {
219
- options = options || {};
220
-
221
- var tags = options.tags || exports.tags,
222
- openTag = tags[0],
223
- closeTag = tags[tags.length - 1];
224
-
225
- var code = [
226
- 'var buffer = "";', // output buffer
227
- "\nvar line = 1;", // keep track of source line number
228
- "\ntry {",
229
- '\nbuffer += "'
230
- ];
206
+ Writer.prototype.compilePartial = function (name, template, tags) {
207
+ var fn = this.compile(template, tags);
208
+ this._partialCache[name] = fn;
209
+ return fn;
210
+ };
231
211
 
232
- var spaces = [], // indices of whitespace in code on the current line
233
- hasTag = false, // is there a {{tag}} on the current line?
234
- nonSpace = false; // is there a non-space char on the current line?
212
+ Writer.prototype.compileTokens = function (tokens, template) {
213
+ var fn = compileTokens(tokens);
214
+ var self = this;
235
215
 
236
- // Strips all space characters from the code array for the current line
237
- // if there was a {{tag}} on it and otherwise only spaces.
238
- var stripSpace = function () {
239
- if (hasTag && !nonSpace && !options.space) {
240
- while (spaces.length) {
241
- code.splice(spaces.pop(), 1);
216
+ return function (view, partials) {
217
+ if (partials) {
218
+ if (typeof partials === "function") {
219
+ self._loadPartial = partials;
220
+ } else {
221
+ for (var name in partials) {
222
+ self.compilePartial(name, partials[name]);
223
+ }
242
224
  }
243
- } else {
244
- spaces = [];
245
225
  }
246
226
 
247
- hasTag = false;
248
- nonSpace = false;
227
+ return fn(self, Context.make(view), template);
249
228
  };
229
+ };
250
230
 
251
- var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
231
+ Writer.prototype.render = function (template, view, partials) {
232
+ return this.compile(template)(view, partials);
233
+ };
252
234
 
253
- var setTags = function (source) {
254
- tags = trim(source).split(/\s+/);
255
- nextOpenTag = tags[0];
256
- nextCloseTag = tags[tags.length - 1];
257
- };
235
+ Writer.prototype._section = function (name, context, text, callback) {
236
+ var value = context.lookup(name);
258
237
 
259
- var includePartial = function (source) {
260
- code.push(
261
- '";',
262
- updateLine,
263
- '\nvar partial = partials["' + trim(source) + '"];',
264
- '\nif (partial) {',
265
- '\n buffer += render(partial,stack[stack.length - 1],partials);',
266
- '\n}',
267
- '\nbuffer += "'
268
- );
269
- };
238
+ switch (typeof value) {
239
+ case "object":
240
+ if (isArray(value)) {
241
+ var buffer = "";
270
242
 
271
- var openSection = function (source, inverted) {
272
- var name = trim(source);
243
+ for (var i = 0, len = value.length; i < len; ++i) {
244
+ buffer += callback(this, context.push(value[i]));
245
+ }
273
246
 
274
- if (name === "") {
275
- throw debug(new Error("Section name may not be empty"), template, line, options.file);
247
+ return buffer;
276
248
  }
277
249
 
278
- sectionStack.push({name: name, inverted: inverted});
279
-
280
- code.push(
281
- '";',
282
- updateLine,
283
- '\nvar name = "' + name + '";',
284
- '\nvar callback = (function () {',
285
- '\n return function () {',
286
- '\n var buffer = "";',
287
- '\nbuffer += "'
288
- );
289
- };
250
+ return value ? callback(this, context.push(value)) : "";
251
+ case "function":
252
+ var self = this;
253
+ var scopedRender = function (template) {
254
+ return self.render(template, context);
255
+ };
290
256
 
291
- var openInvertedSection = function (source) {
292
- openSection(source, true);
293
- };
257
+ return value.call(context.view, text, scopedRender) || "";
258
+ default:
259
+ if (value) {
260
+ return callback(this, context);
261
+ }
262
+ }
294
263
 
295
- var closeSection = function (source) {
296
- var name = trim(source);
297
- var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
264
+ return "";
265
+ };
298
266
 
299
- if (!openName || name != openName) {
300
- throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
301
- }
267
+ Writer.prototype._inverted = function (name, context, callback) {
268
+ var value = context.lookup(name);
302
269
 
303
- var section = sectionStack.pop();
270
+ // Use JavaScript's definition of falsy. Include empty arrays.
271
+ // See https://github.com/janl/mustache.js/issues/186
272
+ if (!value || (isArray(value) && value.length === 0)) {
273
+ return callback(this, context);
274
+ }
304
275
 
305
- code.push(
306
- '";',
307
- '\n return buffer;',
308
- '\n };',
309
- '\n})();'
310
- );
276
+ return "";
277
+ };
311
278
 
312
- if (section.inverted) {
313
- code.push("\nbuffer += renderSection(name,stack,callback,true);");
314
- } else {
315
- code.push("\nbuffer += renderSection(name,stack,callback);");
279
+ Writer.prototype._partial = function (name, context) {
280
+ if (!(name in this._partialCache) && this._loadPartial) {
281
+ this.compilePartial(name, this._loadPartial(name));
282
+ }
283
+
284
+ var fn = this._partialCache[name];
285
+
286
+ return fn ? fn(context) : "";
287
+ };
288
+
289
+ Writer.prototype._name = function (name, context) {
290
+ var value = context.lookup(name);
291
+
292
+ if (typeof value === "function") {
293
+ value = value.call(context.view);
294
+ }
295
+
296
+ return (value == null) ? "" : String(value);
297
+ };
298
+
299
+ Writer.prototype._escaped = function (name, context) {
300
+ return exports.escape(this._name(name, context));
301
+ };
302
+
303
+ /**
304
+ * Calculates the bounds of the section represented by the given `token` in
305
+ * the original template by drilling down into nested sections to find the
306
+ * last token that is part of that section. Returns an array of [start, end].
307
+ */
308
+ function sectionBounds(token) {
309
+ var start = token[3];
310
+ var end = start;
311
+
312
+ var tokens;
313
+ while ((tokens = token[4]) && tokens.length) {
314
+ token = tokens[tokens.length - 1];
315
+ end = token[3];
316
+ }
317
+
318
+ return [start, end];
319
+ }
320
+
321
+ /**
322
+ * Low-level function that compiles the given `tokens` into a function
323
+ * that accepts three arguments: a Writer, a Context, and the template.
324
+ */
325
+ function compileTokens(tokens) {
326
+ var subRenders = {};
327
+
328
+ function subRender(i, tokens, template) {
329
+ if (!subRenders[i]) {
330
+ var fn = compileTokens(tokens);
331
+ subRenders[i] = function (writer, context) {
332
+ return fn(writer, context, template);
333
+ };
316
334
  }
317
335
 
318
- code.push('\nbuffer += "');
319
- };
336
+ return subRenders[i];
337
+ }
320
338
 
321
- var sendPlain = function (source) {
322
- code.push(
323
- '";',
324
- updateLine,
325
- '\nbuffer += lookup("' + trim(source) + '",stack,"");',
326
- '\nbuffer += "'
327
- );
328
- };
339
+ return function (writer, context, template) {
340
+ var buffer = "";
341
+ var token, sectionText;
329
342
 
330
- var sendEscaped = function (source) {
331
- code.push(
332
- '";',
333
- updateLine,
334
- '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
335
- '\nbuffer += "'
336
- );
337
- };
343
+ for (var i = 0, len = tokens.length; i < len; ++i) {
344
+ token = tokens[i];
338
345
 
339
- var line = 1, c, callback;
340
- for (var i = 0, len = template.length; i < len; ++i) {
341
- if (template.slice(i, i + openTag.length) === openTag) {
342
- i += openTag.length;
343
- c = template.substr(i, 1);
344
- updateLine = '\nline = ' + line + ';';
345
- nextOpenTag = openTag;
346
- nextCloseTag = closeTag;
347
- hasTag = true;
348
-
349
- switch (c) {
350
- case "!": // comment
351
- i++;
352
- callback = null;
353
- break;
354
- case "=": // change open/close tags, e.g. {{=<% %>=}}
355
- i++;
356
- closeTag = "=" + closeTag;
357
- callback = setTags;
346
+ switch (token[0]) {
347
+ case "#":
348
+ sectionText = template.slice.apply(template, sectionBounds(token));
349
+ buffer += writer._section(token[1], context, sectionText, subRender(i, token[4], template));
358
350
  break;
359
- case ">": // include partial
360
- i++;
361
- callback = includePartial;
351
+ case "^":
352
+ buffer += writer._inverted(token[1], context, subRender(i, token[4], template));
362
353
  break;
363
- case "#": // start section
364
- i++;
365
- callback = openSection;
354
+ case ">":
355
+ buffer += writer._partial(token[1], context);
366
356
  break;
367
- case "^": // start inverted section
368
- i++;
369
- callback = openInvertedSection;
357
+ case "&":
358
+ buffer += writer._name(token[1], context);
370
359
  break;
371
- case "/": // end section
372
- i++;
373
- callback = closeSection;
360
+ case "name":
361
+ buffer += writer._escaped(token[1], context);
374
362
  break;
375
- case "{": // plain variable
376
- closeTag = "}" + closeTag;
377
- // fall through
378
- case "&": // plain variable
379
- i++;
380
- nonSpace = true;
381
- callback = sendPlain;
363
+ case "text":
364
+ buffer += token[1];
382
365
  break;
383
- default: // escaped variable
384
- nonSpace = true;
385
- callback = sendEscaped;
386
366
  }
367
+ }
387
368
 
388
- var end = template.indexOf(closeTag, i);
369
+ return buffer;
370
+ };
371
+ }
389
372
 
390
- if (end === -1) {
391
- throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
373
+ /**
374
+ * Forms the given array of `tokens` into a nested tree structure where
375
+ * tokens that represent a section have a fifth item: an array that contains
376
+ * all tokens in that section.
377
+ */
378
+ function nestTokens(tokens) {
379
+ var tree = [];
380
+ var collector = tree;
381
+ var sections = [];
382
+ var token, section;
383
+
384
+ for (var i = 0; i < tokens.length; ++i) {
385
+ token = tokens[i];
386
+
387
+ switch (token[0]) {
388
+ case "#":
389
+ case "^":
390
+ token[4] = [];
391
+ sections.push(token);
392
+ collector.push(token);
393
+ collector = token[4];
394
+ break;
395
+ case "/":
396
+ if (sections.length === 0) {
397
+ throw new Error("Unopened section: " + token[1]);
392
398
  }
393
399
 
394
- var source = template.substring(i, end);
400
+ section = sections.pop();
395
401
 
396
- if (callback) {
397
- callback(source);
402
+ if (section[1] !== token[1]) {
403
+ throw new Error("Unclosed section: " + section[1]);
398
404
  }
399
405
 
400
- // Maintain line count for \n in source.
401
- var n = 0;
402
- while (~(n = source.indexOf("\n", n))) {
403
- line++;
404
- n++;
406
+ if (sections.length > 0) {
407
+ collector = sections[sections.length - 1][4];
408
+ } else {
409
+ collector = tree;
405
410
  }
411
+ break;
412
+ default:
413
+ collector.push(token);
414
+ }
415
+ }
416
+
417
+ // Make sure there were no open sections when we're done.
418
+ section = sections.pop();
406
419
 
407
- i = end + closeTag.length - 1;
408
- openTag = nextOpenTag;
409
- closeTag = nextCloseTag;
420
+ if (section) {
421
+ throw new Error("Unclosed section: " + section[1]);
422
+ }
423
+
424
+ return tree;
425
+ }
426
+
427
+ /**
428
+ * Combines the values of consecutive text tokens in the given `tokens` array
429
+ * to a single token.
430
+ */
431
+ function squashTokens(tokens) {
432
+ var token, lastToken;
433
+
434
+ for (var i = 0; i < tokens.length; ++i) {
435
+ token = tokens[i];
436
+
437
+ if (lastToken && lastToken[0] === "text" && token[0] === "text") {
438
+ lastToken[1] += token[1];
439
+ lastToken[3] = token[3];
440
+ tokens.splice(i--, 1); // Remove this token from the array.
410
441
  } else {
411
- c = template.substr(i, 1);
442
+ lastToken = token;
443
+ }
444
+ }
445
+ }
412
446
 
413
- switch (c) {
414
- case '"':
415
- case "\\":
416
- nonSpace = true;
417
- code.push("\\" + c);
418
- break;
419
- case "\r":
420
- // Ignore carriage returns.
421
- break;
422
- case "\n":
423
- spaces.push(code.length);
424
- code.push("\\n");
425
- stripSpace(); // Check for whitespace on the current line.
426
- line++;
427
- break;
428
- default:
429
- if (isWhitespace(c)) {
430
- spaces.push(code.length);
447
+ function escapeTags(tags) {
448
+ if (tags.length !== 2) {
449
+ throw new Error("Invalid tags: " + tags.join(" "));
450
+ }
451
+
452
+ return [
453
+ new RegExp(escapeRe(tags[0]) + "\\s*"),
454
+ new RegExp("\\s*" + escapeRe(tags[1]))
455
+ ];
456
+ }
457
+
458
+ /**
459
+ * Breaks up the given `template` string into a tree of token objects. If
460
+ * `tags` is given here it must be an array with two string values: the
461
+ * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of
462
+ * course, the default is to use mustaches (i.e. Mustache.tags).
463
+ */
464
+ exports.parse = function (template, tags) {
465
+ tags = tags || exports.tags;
466
+
467
+ var tagRes = escapeTags(tags);
468
+ var scanner = new Scanner(template);
469
+
470
+ var tokens = [], // Buffer to hold the tokens
471
+ spaces = [], // Indices of whitespace tokens on the current line
472
+ hasTag = false, // Is there a {{tag}} on the current line?
473
+ nonSpace = false; // Is there a non-space char on the current line?
474
+
475
+ // Strips all whitespace tokens array for the current line
476
+ // if there was a {{#tag}} on it and otherwise only space.
477
+ function stripSpace() {
478
+ if (hasTag && !nonSpace) {
479
+ while (spaces.length) {
480
+ tokens.splice(spaces.pop(), 1);
481
+ }
482
+ } else {
483
+ spaces = [];
484
+ }
485
+
486
+ hasTag = false;
487
+ nonSpace = false;
488
+ }
489
+
490
+ var start, type, value, chr;
491
+
492
+ while (!scanner.eos()) {
493
+ start = scanner.pos;
494
+ value = scanner.scanUntil(tagRes[0]);
495
+
496
+ if (value) {
497
+ for (var i = 0, len = value.length; i < len; ++i) {
498
+ chr = value.charAt(i);
499
+
500
+ if (isWhitespace(chr)) {
501
+ spaces.push(tokens.length);
431
502
  } else {
432
503
  nonSpace = true;
433
504
  }
434
505
 
435
- code.push(c);
506
+ tokens.push(["text", chr, start, start + 1]);
507
+ start += 1;
508
+
509
+ if (chr === "\n") {
510
+ stripSpace(); // Check for whitespace on the current line.
511
+ }
436
512
  }
437
513
  }
438
- }
439
514
 
440
- if (sectionStack.length != 0) {
441
- throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
442
- }
515
+ start = scanner.pos;
443
516
 
444
- // Clean up any whitespace from a closing {{tag}} that was at the end
445
- // of the template without a trailing \n.
446
- stripSpace();
517
+ // Match the opening tag.
518
+ if (!scanner.scan(tagRes[0])) {
519
+ break;
520
+ }
447
521
 
448
- code.push(
449
- '";',
450
- "\nreturn buffer;",
451
- "\n} catch (e) { throw {error: e, line: line}; }"
452
- );
522
+ hasTag = true;
523
+ type = scanner.scan(tagRe) || "name";
524
+
525
+ // Skip any whitespace between tag and value.
526
+ scanner.scan(whiteRe);
527
+
528
+ // Extract the tag value.
529
+ if (type === "=") {
530
+ value = scanner.scanUntil(eqRe);
531
+ scanner.scan(eqRe);
532
+ scanner.scanUntil(tagRes[1]);
533
+ } else if (type === "{") {
534
+ var closeRe = new RegExp("\\s*" + escapeRe("}" + tags[1]));
535
+ value = scanner.scanUntil(closeRe);
536
+ scanner.scan(curlyRe);
537
+ scanner.scanUntil(tagRes[1]);
538
+ type = "&";
539
+ } else {
540
+ value = scanner.scanUntil(tagRes[1]);
541
+ }
453
542
 
454
- // Ignore `buffer += "";` statements.
455
- var body = code.join("").replace(/buffer \+= "";\n/g, "");
543
+ // Match the closing tag.
544
+ if (!scanner.scan(tagRes[1])) {
545
+ throw new Error("Unclosed tag at " + scanner.pos);
546
+ }
547
+
548
+ tokens.push([type, value, start, scanner.pos]);
456
549
 
457
- if (options.debug) {
458
- if (typeof console != "undefined" && console.log) {
459
- console.log(body);
460
- } else if (typeof print === "function") {
461
- print(body);
550
+ if (type === "name" || type === "{" || type === "&") {
551
+ nonSpace = true;
552
+ }
553
+
554
+ // Set the tags for the next time around.
555
+ if (type === "=") {
556
+ tags = value.split(spaceRe);
557
+ tagRes = escapeTags(tags);
462
558
  }
463
559
  }
464
560
 
465
- return body;
466
- }
561
+ squashTokens(tokens);
562
+
563
+ return nestTokens(tokens);
564
+ };
565
+
566
+ // The high-level clearCache, compile, compilePartial, and render functions
567
+ // use this default writer.
568
+ var _writer = new Writer();
467
569
 
468
570
  /**
469
- * Used by `compile` to generate a reusable function for the given `template`.
571
+ * Clears all cached templates and partials in the default writer.
470
572
  */
471
- function _compile(template, options) {
472
- var args = "view,partials,stack,lookup,escapeHTML,renderSection,render";
473
- var body = parse(template, options);
474
- var fn = new Function(args, body);
475
-
476
- // This anonymous function wraps the generated function so we can do
477
- // argument coercion, setup some variables, and handle any errors
478
- // encountered while executing it.
479
- return function (view, partials) {
480
- partials = partials || {};
481
-
482
- var stack = [view]; // context stack
573
+ exports.clearCache = function () {
574
+ return _writer.clearCache();
575
+ };
483
576
 
484
- try {
485
- return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
486
- } catch (e) {
487
- throw debug(e.error, template, e.line, options.file);
488
- }
489
- };
490
- }
577
+ /**
578
+ * Compiles the given `template` to a reusable function using the default
579
+ * writer.
580
+ */
581
+ exports.compile = function (template, tags) {
582
+ return _writer.compile(template, tags);
583
+ };
491
584
 
492
- // Cache of pre-compiled templates.
493
- var _cache = {};
585
+ /**
586
+ * Compiles the partial with the given `name` and `template` to a reusable
587
+ * function using the default writer.
588
+ */
589
+ exports.compilePartial = function (name, template, tags) {
590
+ return _writer.compilePartial(name, template, tags);
591
+ };
494
592
 
495
593
  /**
496
- * Clear the cache of compiled templates.
594
+ * Compiles the given array of tokens (the output of a parse) to a reusable
595
+ * function using the default writer.
497
596
  */
498
- function clearCache() {
499
- _cache = {};
500
- }
597
+ exports.compileTokens = function (tokens, template) {
598
+ return _writer.compileTokens(tokens, template);
599
+ };
501
600
 
502
601
  /**
503
- * Compiles the given `template` into a reusable function using the given
504
- * `options`. In addition to the options accepted by Mustache.parse,
505
- * recognized options include the following:
506
- *
507
- * - cache Set `false` to bypass any pre-compiled version of the given
508
- * template. Otherwise, a given `template` string will be cached
509
- * the first time it is parsed
602
+ * Renders the `template` with the given `view` and `partials` using the
603
+ * default writer.
510
604
  */
511
- function compile(template, options) {
512
- options = options || {};
605
+ exports.render = function (template, view, partials) {
606
+ return _writer.render(template, view, partials);
607
+ };
513
608
 
514
- // Use a pre-compiled version from the cache if we have one.
515
- if (options.cache !== false) {
516
- if (!_cache[template]) {
517
- _cache[template] = _compile(template, options);
518
- }
609
+ // This is here for backwards compatibility with 0.4.x.
610
+ exports.to_html = function (template, view, partials, send) {
611
+ var result = exports.render(template, view, partials);
519
612
 
520
- return _cache[template];
613
+ if (typeof send === "function") {
614
+ send(result);
615
+ } else {
616
+ return result;
521
617
  }
618
+ };
522
619
 
523
- return _compile(template, options);
524
- }
525
-
526
- /**
527
- * High-level function that renders the given `template` using the given
528
- * `view` and `partials`. If you need to use any of the template options (see
529
- * `compile` above), you must compile in a separate step, and then call that
530
- * compiled function.
531
- */
532
- function render(template, view, partials) {
533
- return compile(template)(view, partials);
534
- }
620
+ return exports;
535
621
 
536
- })(Mustache);
622
+ }())));
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smt_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-07-14 00:00:00.000000000 Z
13
+ date: 2012-10-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
17
- requirement: &70333810363860 !ruby/object:Gem::Requirement
17
+ requirement: &70257902094220 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 3.1.0
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70333810363860
25
+ version_requirements: *70257902094220
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: tilt
28
- requirement: &70333810363340 !ruby/object:Gem::Requirement
28
+ requirement: &70257902093340 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 1.3.3
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70333810363340
36
+ version_requirements: *70257902093340
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: sprockets
39
- requirement: &70333810362860 !ruby/object:Gem::Requirement
39
+ requirement: &70257902130980 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: 2.0.3
45
45
  type: :runtime
46
46
  prerelease: false
47
- version_requirements: *70333810362860
47
+ version_requirements: *70257902130980
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: mustache
50
- requirement: &70333810362340 !ruby/object:Gem::Requirement
50
+ requirement: &70257902130440 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,7 +55,7 @@ dependencies:
55
55
  version: 0.99.4
56
56
  type: :runtime
57
57
  prerelease: false
58
- version_requirements: *70333810362340
58
+ version_requirements: *70257902130440
59
59
  description: Shared mustache templates for rails 3
60
60
  email:
61
61
  - contacts@railsware.com
@@ -93,12 +93,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
93
93
  - - ! '>='
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
+ segments:
97
+ - 0
98
+ hash: 1538246288557208580
96
99
  required_rubygems_version: !ruby/object:Gem::Requirement
97
100
  none: false
98
101
  requirements:
99
102
  - - ! '>='
100
103
  - !ruby/object:Gem::Version
101
104
  version: '0'
105
+ segments:
106
+ - 0
107
+ hash: 1538246288557208580
102
108
  requirements: []
103
109
  rubyforge_project:
104
110
  rubygems_version: 1.8.16