poirot 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module Poirot
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.3"
3
3
  end
data/lib/poirot/view.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  module Poirot
2
2
  class View < Mustache
3
3
 
4
+ include Poirot::ViewHelper
5
+
4
6
  def initialize(view_context, template_source)
5
7
  @view_context = view_context
6
8
  @params = view_context.params || {}
@@ -0,0 +1,7 @@
1
+ module Poirot
2
+ module ViewHelper
3
+ def csrf_token
4
+ tag(:input, name: 'authenticity_token', type: 'hidden', value: form_authenticity_token)
5
+ end
6
+ end
7
+ end
data/lib/poirot.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'mustache'
2
2
  require 'poirot/handler'
3
+ require 'poirot/view_helper'
3
4
  require 'poirot/view'
4
5
  require 'poirot/asset_helper'
5
6
 
@@ -1,422 +1,536 @@
1
- /*
2
- mustache.js Logic-less templates in JavaScript
3
-
4
- See http://mustache.github.com/ for more info.
5
- */
6
-
7
- var Mustache = function() {
8
- var regexCache = {};
9
- var Renderer = function() {};
10
-
11
- Renderer.prototype = {
12
- otag: "{{",
13
- ctag: "}}",
14
- pragmas: {},
15
- buffer: [],
16
- pragmas_implemented: {
17
- "IMPLICIT-ITERATOR": true
18
- },
19
- context: {},
20
-
21
- render: function(template, context, partials, in_recursion) {
22
- // reset buffer & set context
23
- if(!in_recursion) {
24
- this.context = context;
25
- this.buffer = []; // TODO: make this non-lazy
26
- }
27
-
28
- // fail fast
29
- if(!this.includes("", template)) {
30
- if(in_recursion) {
31
- return template;
32
- } else {
33
- this.send(template);
34
- return;
35
- }
36
- }
37
-
38
- // get the pragmas together
39
- template = this.render_pragmas(template);
40
-
41
- // render the template
42
- var html = this.render_section(template, context, partials);
43
-
44
- // render_section did not find any sections, we still need to render the tags
45
- if (html === false) {
46
- html = this.render_tags(template, context, partials, in_recursion);
47
- }
48
-
49
- if (in_recursion) {
50
- return html;
51
- } else {
52
- this.sendLines(html);
53
- }
54
- },
55
-
56
- /*
57
- Sends parsed lines
58
- */
59
- send: function(line) {
60
- if(line !== "") {
61
- this.buffer.push(line);
62
- }
63
- },
1
+ /*!
2
+ * mustache.js - Logic-less {{mustache}} templates with JavaScript
3
+ * http://github.com/janl/mustache.js
4
+ */
5
+ var Mustache = (typeof module !== "undefined" && module.exports) || {};
6
+
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
+ };
64
27
 
65
- sendLines: function(text) {
66
- if (text) {
67
- var lines = text.split("\n");
68
- for (var i = 0; i < lines.length; i++) {
69
- this.send(lines[i]);
70
- }
71
- }
72
- },
73
-
74
- /*
75
- Looks for %PRAGMAS
76
- */
77
- render_pragmas: function(template) {
78
- // no pragmas
79
- if(!this.includes("%", template)) {
80
- return template;
28
+ var _toString = Object.prototype.toString;
29
+ var _isArray = Array.isArray;
30
+ var _forEach = Array.prototype.forEach;
31
+ var _trim = String.prototype.trim;
32
+
33
+ var isArray;
34
+ if (_isArray) {
35
+ isArray = _isArray;
36
+ } else {
37
+ isArray = function (obj) {
38
+ return _toString.call(obj) === "[object Array]";
39
+ };
40
+ }
41
+
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);
81
51
  }
52
+ };
53
+ }
54
+
55
+ var spaceRe = /^\s*$/;
56
+
57
+ function isWhitespace(string) {
58
+ return spaceRe.test(string);
59
+ }
60
+
61
+ var trim;
62
+ if (_trim) {
63
+ trim = function (string) {
64
+ return string == null ? "" : _trim.call(string);
65
+ };
66
+ } else {
67
+ var trimLeft, trimRight;
68
+
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
+ }
82
77
 
83
- var that = this;
84
- var regex = this.getCachedRegex("render_pragmas", function(otag, ctag) {
85
- return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
86
- });
78
+ trim = function (string) {
79
+ return string == null ? "" :
80
+ String(string).replace(trimLeft, "").replace(trimRight, "");
81
+ };
82
+ }
83
+
84
+ var escapeMap = {
85
+ "&": "&amp;",
86
+ "<": "&lt;",
87
+ ">": "&gt;",
88
+ '"': '&quot;',
89
+ "'": '&#39;'
90
+ };
87
91
 
88
- return template.replace(regex, function(match, pragma, options) {
89
- if(!that.pragmas_implemented[pragma]) {
90
- throw({message:
91
- "This implementation of mustache doesn't understand the '" +
92
- pragma + "' pragma"});
93
- }
94
- that.pragmas[pragma] = {};
95
- if(options) {
96
- var opts = options.split("=");
97
- that.pragmas[pragma][opts[0]] = opts[1];
98
- }
99
- return "";
100
- // ignore unknown pragmas silently
101
- });
102
- },
103
-
104
- /*
105
- Tries to find a partial in the curent scope and render it
106
- */
107
- render_partial: function(name, context, partials) {
108
- name = this.trim(name);
109
- if(!partials || partials[name] === undefined) {
110
- throw({message: "unknown_partial '" + name + "'"});
111
- }
112
- if(typeof(context[name]) != "object") {
113
- return this.render(partials[name], context, partials, true);
114
- }
115
- return this.render(partials[name], context[name], partials, true);
116
- },
117
-
118
- /*
119
- Renders inverted (^) and normal (#) sections
120
- */
121
- render_section: function(template, context, partials) {
122
- if(!this.includes("#", template) && !this.includes("^", template)) {
123
- // did not render anything, there were no sections
124
- return false;
125
- }
92
+ function escapeHTML(string) {
93
+ return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
94
+ return escapeMap[s] || s;
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Adds the `template`, `line`, and `file` properties to the given error
100
+ * object and alters the message to provide more useful debugging information.
101
+ */
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
+ }
126
115
 
127
- var that = this;
116
+ e.template = template;
117
+ e.line = line;
118
+ e.file = file;
119
+ e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
128
120
 
129
- var regex = this.getCachedRegex("render_section", function(otag, ctag) {
130
- // This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
131
- return new RegExp(
132
- "^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1)
121
+ return e;
122
+ }
133
123
 
134
- otag + // {{
135
- "(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3)
136
- ctag + // }}
124
+ /**
125
+ * Looks up the value of the given `name` in the given context `stack`.
126
+ */
127
+ function lookup(name, stack, defaultValue) {
128
+ if (name === ".") {
129
+ return stack[stack.length - 1];
130
+ }
137
131
 
138
- "\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped
132
+ var names = name.split(".");
133
+ var lastIndex = names.length - 1;
134
+ var target = names[lastIndex];
139
135
 
140
- otag + // {{
141
- "\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag).
142
- ctag + // }}
136
+ var value, context, i = stack.length, j, localStack;
137
+ while (i) {
138
+ localStack = stack.slice(0);
139
+ context = stack[--i];
143
140
 
144
- "\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped.
141
+ j = 0;
142
+ while (j < lastIndex) {
143
+ context = context[names[j++]];
145
144
 
146
- "g");
147
- });
145
+ if (context == null) {
146
+ break;
147
+ }
148
148
 
149
+ localStack.push(context);
150
+ }
149
151
 
150
- // for each {{#foo}}{{/foo}} section do...
151
- return template.replace(regex, function(match, before, type, name, content, after) {
152
- // before contains only tags, no sections
153
- var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",
152
+ if (context && typeof context === "object" && target in context) {
153
+ value = context[target];
154
+ break;
155
+ }
156
+ }
154
157
 
155
- // after may contain both sections and tags, so use full rendering function
156
- renderedAfter = after ? that.render(after, context, partials, true) : "",
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]);
161
+ }
157
162
 
158
- // will be computed below
159
- renderedContent,
163
+ if (value == null) {
164
+ return defaultValue;
165
+ }
160
166
 
161
- value = that.find(name, context);
167
+ return value;
168
+ }
162
169
 
163
- if (type === "^") { // inverted section
164
- if (!value || that.is_array(value) && value.length === 0) {
165
- // false or empty list, render it
166
- renderedContent = that.render(content, context, partials, true);
167
- } else {
168
- renderedContent = "";
169
- }
170
- } else if (type === "#") { // normal section
171
- if (that.is_array(value)) { // Enumerable, Let's loop!
172
- renderedContent = that.map(value, function(row) {
173
- return that.render(content, that.create_context(row), partials, true);
174
- }).join("");
175
- } else if (that.is_object(value)) { // Object, Use it as subcontext!
176
- renderedContent = that.render(content, that.create_context(value),
177
- partials, true);
178
- } else if (typeof value === "function") {
179
- // higher order section
180
- renderedContent = value.call(context, content, function(text) {
181
- return that.render(text, context, partials, true);
182
- });
183
- } else if (value) { // boolean section
184
- renderedContent = that.render(content, context, partials, true);
185
- } else {
186
- renderedContent = "";
187
- }
188
- }
170
+ function renderSection(name, stack, callback, inverted) {
171
+ var buffer = "";
172
+ var value = lookup(name, stack);
189
173
 
190
- return renderedBefore + renderedContent + renderedAfter;
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();
191
186
  });
192
- },
193
-
194
- /*
195
- Replace {{foo}} and friends with values from our view
196
- */
197
- render_tags: function(template, context, partials, in_recursion) {
198
- // tit for tat
199
- var that = this;
200
-
201
-
202
-
203
- var new_regex = function() {
204
- return that.getCachedRegex("render_tags", function(otag, ctag) {
205
- return new RegExp(otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + ctag + "+", "g");
206
- });
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);
207
195
  };
196
+ buffer += value.call(scope, callback(), scopedRender) || "";
197
+ } else if (value) {
198
+ buffer += callback();
199
+ }
208
200
 
209
- var regex = new_regex();
210
- var tag_replace_callback = function(match, operator, name) {
211
- switch(operator) {
212
- case "!": // ignore comments
213
- return "";
214
- case "=": // set new delimiters, rebuild the replace regexp
215
- that.set_delimiters(name);
216
- regex = new_regex();
217
- return "";
218
- case ">": // render partial
219
- return that.render_partial(name, context, partials);
220
- case "{": // the triple mustache is unescaped
221
- return that.find(name, context);
222
- default: // escape the value
223
- return that.escape(that.find(name, context));
224
- }
225
- };
226
- var lines = template.split("\n");
227
- for(var i = 0; i < lines.length; i++) {
228
- lines[i] = lines[i].replace(regex, tag_replace_callback, this);
229
- if(!in_recursion) {
230
- this.send(lines[i]);
201
+ return buffer;
202
+ }
203
+
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
+ ];
231
+
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?
235
+
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);
231
242
  }
243
+ } else {
244
+ spaces = [];
232
245
  }
233
246
 
234
- if(in_recursion) {
235
- return lines.join("\n");
247
+ hasTag = false;
248
+ nonSpace = false;
249
+ };
250
+
251
+ var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
252
+
253
+ var setTags = function (source) {
254
+ tags = trim(source).split(/\s+/);
255
+ nextOpenTag = tags[0];
256
+ nextCloseTag = tags[tags.length - 1];
257
+ };
258
+
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
+ };
270
+
271
+ var openSection = function (source, inverted) {
272
+ var name = trim(source);
273
+
274
+ if (name === "") {
275
+ throw debug(new Error("Section name may not be empty"), template, line, options.file);
236
276
  }
237
- },
238
-
239
- set_delimiters: function(delimiters) {
240
- var dels = delimiters.split(" ");
241
- this.otag = this.escape_regex(dels[0]);
242
- this.ctag = this.escape_regex(dels[1]);
243
- },
244
-
245
- escape_regex: function(text) {
246
- // thank you Simon Willison
247
- if(!arguments.callee.sRE) {
248
- var specials = [
249
- '/', '.', '*', '+', '?', '|',
250
- '(', ')', '[', ']', '{', '}', '\\'
251
- ];
252
- arguments.callee.sRE = new RegExp(
253
- '(\\' + specials.join('|\\') + ')', 'g'
254
- );
277
+
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
+ };
290
+
291
+ var openInvertedSection = function (source) {
292
+ openSection(source, true);
293
+ };
294
+
295
+ var closeSection = function (source) {
296
+ var name = trim(source);
297
+ var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
298
+
299
+ if (!openName || name != openName) {
300
+ throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
255
301
  }
256
- return text.replace(arguments.callee.sRE, '\\$1');
257
- },
258
-
259
- /*
260
- find `name` in current `context`. That is find me a value
261
- from the view object
262
- */
263
- find: function(name, context) {
264
- name = this.trim(name);
265
-
266
- // Checks whether a value is thruthy or false or 0
267
- function is_kinda_truthy(bool) {
268
- return bool === false || bool === 0 || bool;
302
+
303
+ var section = sectionStack.pop();
304
+
305
+ code.push(
306
+ '";',
307
+ '\n return buffer;',
308
+ '\n };',
309
+ '\n})();'
310
+ );
311
+
312
+ if (section.inverted) {
313
+ code.push("\nbuffer += renderSection(name,stack,callback,true);");
314
+ } else {
315
+ code.push("\nbuffer += renderSection(name,stack,callback);");
269
316
  }
270
317
 
271
- var value;
272
-
273
- // check for dot notation eg. foo.bar
274
- if(name.match(/([a-z_]+)\./ig)){
275
- var childValue = this.walk_context(name, context);
276
- if(is_kinda_truthy(childValue)) {
277
- value = childValue;
318
+ code.push('\nbuffer += "');
319
+ };
320
+
321
+ var sendPlain = function (source) {
322
+ code.push(
323
+ '";',
324
+ updateLine,
325
+ '\nbuffer += lookup("' + trim(source) + '",stack,"");',
326
+ '\nbuffer += "'
327
+ );
328
+ };
329
+
330
+ var sendEscaped = function (source) {
331
+ code.push(
332
+ '";',
333
+ updateLine,
334
+ '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
335
+ '\nbuffer += "'
336
+ );
337
+ };
338
+
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;
358
+ break;
359
+ case ">": // include partial
360
+ i++;
361
+ callback = includePartial;
362
+ break;
363
+ case "#": // start section
364
+ i++;
365
+ callback = openSection;
366
+ break;
367
+ case "^": // start inverted section
368
+ i++;
369
+ callback = openInvertedSection;
370
+ break;
371
+ case "/": // end section
372
+ i++;
373
+ callback = closeSection;
374
+ break;
375
+ case "{": // plain variable
376
+ closeTag = "}" + closeTag;
377
+ // fall through
378
+ case "&": // plain variable
379
+ i++;
380
+ nonSpace = true;
381
+ callback = sendPlain;
382
+ break;
383
+ default: // escaped variable
384
+ nonSpace = true;
385
+ callback = sendEscaped;
278
386
  }
279
- }
280
- else{
281
- if(is_kinda_truthy(context[name])) {
282
- value = context[name];
283
- } else if(is_kinda_truthy(this.context[name])) {
284
- value = this.context[name];
387
+
388
+ var end = template.indexOf(closeTag, i);
389
+
390
+ if (end === -1) {
391
+ throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
285
392
  }
286
- }
287
393
 
288
- if(typeof value === "function") {
289
- return value.apply(context);
290
- }
291
- if(value !== undefined) {
292
- return value;
293
- }
294
- // silently ignore unkown variables
295
- return "";
296
- },
297
-
298
- walk_context: function(name, context){
299
- var path = name.split('.');
300
- // if the var doesn't exist in current context, check the top level context
301
- var value_context = (context[path[0]] != undefined) ? context : this.context;
302
- var value = value_context[path.shift()];
303
- while(value != undefined && path.length > 0){
304
- value_context = value;
305
- value = value[path.shift()];
306
- }
307
- // if the value is a function, call it, binding the correct context
308
- if(typeof value === "function") {
309
- return value.apply(value_context);
310
- }
311
- return value;
312
- },
313
-
314
- // Utility methods
315
-
316
- /* includes tag */
317
- includes: function(needle, haystack) {
318
- return haystack.indexOf(this.otag + needle) != -1;
319
- },
320
-
321
- /*
322
- Does away with nasty characters
323
- */
324
- escape: function(s) {
325
- s = String(s === null ? "" : s);
326
- return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
327
- switch(s) {
328
- case "&": return "&amp;";
329
- case '"': return '&quot;';
330
- case "'": return '&#39;';
331
- case "<": return "&lt;";
332
- case ">": return "&gt;";
333
- default: return s;
394
+ var source = template.substring(i, end);
395
+
396
+ if (callback) {
397
+ callback(source);
334
398
  }
335
- });
336
- },
337
399
 
338
- // by @langalex, support for arrays of strings
339
- create_context: function(_context) {
340
- if(this.is_object(_context)) {
341
- return _context;
342
- } else {
343
- var iterator = ".";
344
- if(this.pragmas["IMPLICIT-ITERATOR"]) {
345
- iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
400
+ // Maintain line count for \n in source.
401
+ var n = 0;
402
+ while (~(n = source.indexOf("\n", n))) {
403
+ line++;
404
+ n++;
346
405
  }
347
- var ctx = {};
348
- ctx[iterator] = _context;
349
- return ctx;
350
- }
351
- },
352
-
353
- is_object: function(a) {
354
- return a && typeof a == "object";
355
- },
356
-
357
- is_array: function(a) {
358
- return Object.prototype.toString.call(a) === '[object Array]';
359
- },
360
-
361
- /*
362
- Gets rid of leading and trailing whitespace
363
- */
364
- trim: function(s) {
365
- return s.replace(/^\s*|\s*$/g, "");
366
- },
367
-
368
- /*
369
- Why, why, why? Because IE. Cry, cry cry.
370
- */
371
- map: function(array, fn) {
372
- if (typeof array.map == "function") {
373
- return array.map(fn);
406
+
407
+ i = end + closeTag.length - 1;
408
+ openTag = nextOpenTag;
409
+ closeTag = nextCloseTag;
374
410
  } else {
375
- var r = [];
376
- var l = array.length;
377
- for(var i = 0; i < l; i++) {
378
- r.push(fn(array[i]));
411
+ c = template.substr(i, 1);
412
+
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);
431
+ } else {
432
+ nonSpace = true;
433
+ }
434
+
435
+ code.push(c);
379
436
  }
380
- return r;
381
437
  }
382
- },
438
+ }
383
439
 
384
- getCachedRegex: function(name, generator) {
385
- var byOtag = regexCache[this.otag];
386
- if (!byOtag) {
387
- byOtag = regexCache[this.otag] = {};
388
- }
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
+ }
389
443
 
390
- var byCtag = byOtag[this.ctag];
391
- if (!byCtag) {
392
- byCtag = byOtag[this.ctag] = {};
393
- }
444
+ // Clean up any whitespace from a closing {{tag}} that was at the end
445
+ // of the template without a trailing \n.
446
+ stripSpace();
394
447
 
395
- var regex = byCtag[name];
396
- if (!regex) {
397
- regex = byCtag[name] = generator(this.otag, this.ctag);
398
- }
448
+ code.push(
449
+ '";',
450
+ "\nreturn buffer;",
451
+ "\n} catch (e) { throw {error: e, line: line}; }"
452
+ );
399
453
 
400
- return regex;
454
+ // Ignore `buffer += "";` statements.
455
+ var body = code.join("").replace(/buffer \+= "";\n/g, "");
456
+
457
+ if (options.debug) {
458
+ if (typeof console != "undefined" && console.log) {
459
+ console.log(body);
460
+ } else if (typeof print === "function") {
461
+ print(body);
462
+ }
401
463
  }
402
- };
403
464
 
404
- return({
405
- name: "mustache.js",
406
- version: "0.4.0-dev",
407
-
408
- /*
409
- Turns a template and view into HTML
410
- */
411
- to_html: function(template, view, partials, send_fun) {
412
- var renderer = new Renderer();
413
- if(send_fun) {
414
- renderer.send = send_fun;
465
+ return body;
466
+ }
467
+
468
+ /**
469
+ * Used by `compile` to generate a reusable function for the given `template`.
470
+ */
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
483
+
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);
415
488
  }
416
- renderer.render(template, view || {}, partials);
417
- if(!send_fun) {
418
- return renderer.buffer.join("\n");
489
+ };
490
+ }
491
+
492
+ // Cache of pre-compiled templates.
493
+ var _cache = {};
494
+
495
+ /**
496
+ * Clear the cache of compiled templates.
497
+ */
498
+ function clearCache() {
499
+ _cache = {};
500
+ }
501
+
502
+ /**
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
510
+ */
511
+ function compile(template, options) {
512
+ options = options || {};
513
+
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);
419
518
  }
519
+
520
+ return _cache[template];
420
521
  }
421
- });
422
- }();
522
+
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
+ }
535
+
536
+ })(Mustache);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: poirot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
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-02-29 00:00:00.000000000 Z
13
+ date: 2012-04-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
17
- requirement: &70137361455900 !ruby/object:Gem::Requirement
17
+ requirement: &70162223413260 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>'
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '3'
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70137361455900
25
+ version_requirements: *70162223413260
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: mustache
28
- requirement: &70137361229580 !ruby/object:Gem::Requirement
28
+ requirement: &70162223412840 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70137361229580
36
+ version_requirements: *70162223412840
37
37
  description: mustaches are cool
38
38
  email:
39
39
  - oliver.nightingale1@gmail.com
@@ -52,6 +52,7 @@ files:
52
52
  - lib/poirot/handler.rb
53
53
  - lib/poirot/version.rb
54
54
  - lib/poirot/view.rb
55
+ - lib/poirot/view_helper.rb
55
56
  - poirot.gemspec
56
57
  - vendor/assets/javascripts/poirot-base/index.js
57
58
  - vendor/assets/javascripts/poirot-base/poirot.js