smt_rails 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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