poirot 0.2.2 → 0.2.3

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 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