perspectives 0.0.1

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.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +13 -0
  4. data/lib/generators/perspectives/install.rb +38 -0
  5. data/lib/generators/perspectives/scaffold/scaffold_generator.rb +54 -0
  6. data/lib/generators/perspectives/scaffold/templates/edit.mustache +6 -0
  7. data/lib/generators/perspectives/scaffold/templates/edit.rb +8 -0
  8. data/lib/generators/perspectives/scaffold/templates/form.mustache +25 -0
  9. data/lib/generators/perspectives/scaffold/templates/form.rb +27 -0
  10. data/lib/generators/perspectives/scaffold/templates/index.mustache +22 -0
  11. data/lib/generators/perspectives/scaffold/templates/index.rb +9 -0
  12. data/lib/generators/perspectives/scaffold/templates/new.mustache +5 -0
  13. data/lib/generators/perspectives/scaffold/templates/new.rb +7 -0
  14. data/lib/generators/perspectives/scaffold/templates/show.mustache +10 -0
  15. data/lib/generators/perspectives/scaffold/templates/show.rb +8 -0
  16. data/lib/generators/perspectives/scaffold/templates/tiny.mustache +8 -0
  17. data/lib/generators/perspectives/scaffold/templates/tiny.rb +8 -0
  18. data/lib/generators/perspectives/templates/application.js +13 -0
  19. data/lib/generators/perspectives/templates/rails/scaffold_controller/controller.rb +82 -0
  20. data/lib/perspectives/active_record.rb +17 -0
  21. data/lib/perspectives/base.rb +30 -0
  22. data/lib/perspectives/caching.rb +82 -0
  23. data/lib/perspectives/collection.rb +21 -0
  24. data/lib/perspectives/configuration.rb +14 -0
  25. data/lib/perspectives/context.rb +18 -0
  26. data/lib/perspectives/controller_additions.rb +142 -0
  27. data/lib/perspectives/forms/base.rb +5 -0
  28. data/lib/perspectives/forms/text_field.rb +15 -0
  29. data/lib/perspectives/forms.rb +7 -0
  30. data/lib/perspectives/memoization.rb +30 -0
  31. data/lib/perspectives/mustache_compiler.rb +33 -0
  32. data/lib/perspectives/params.rb +48 -0
  33. data/lib/perspectives/properties.rb +130 -0
  34. data/lib/perspectives/railtie.rb +42 -0
  35. data/lib/perspectives/rendering.rb +15 -0
  36. data/lib/perspectives/responder.rb +15 -0
  37. data/lib/perspectives/templating.rb +39 -0
  38. data/lib/perspectives/version.rb +3 -0
  39. data/lib/perspectives.rb +48 -0
  40. data/lib/rails/projections.json +12 -0
  41. data/lib/tasks/perspectives_tasks.rake +4 -0
  42. data/spec/dummy/README.rdoc +28 -0
  43. data/spec/dummy/Rakefile +6 -0
  44. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  45. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  46. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  47. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  48. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  49. data/spec/dummy/bin/bundle +3 -0
  50. data/spec/dummy/bin/rails +4 -0
  51. data/spec/dummy/bin/rake +4 -0
  52. data/spec/dummy/config/application.rb +23 -0
  53. data/spec/dummy/config/boot.rb +5 -0
  54. data/spec/dummy/config/database.yml +25 -0
  55. data/spec/dummy/config/environment.rb +5 -0
  56. data/spec/dummy/config/environments/development.rb +29 -0
  57. data/spec/dummy/config/environments/production.rb +80 -0
  58. data/spec/dummy/config/environments/test.rb +36 -0
  59. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  61. data/spec/dummy/config/initializers/inflections.rb +16 -0
  62. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  63. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  64. data/spec/dummy/config/initializers/session_store.rb +3 -0
  65. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  66. data/spec/dummy/config/locales/en.yml +23 -0
  67. data/spec/dummy/config/routes.rb +56 -0
  68. data/spec/dummy/config.ru +4 -0
  69. data/spec/dummy/log/test.log +0 -0
  70. data/spec/dummy/public/404.html +58 -0
  71. data/spec/dummy/public/422.html +58 -0
  72. data/spec/dummy/public/500.html +57 -0
  73. data/spec/dummy/public/favicon.ico +0 -0
  74. data/spec/lib/perspectives/properties_spec.rb +31 -0
  75. data/spec/lib/perspectives/templating_spec.rb +18 -0
  76. data/spec/lib/perspectives_spec.rb +5 -0
  77. data/spec/mustaches/users/simple_info.mustache +1 -0
  78. data/spec/rails_helper.rb +11 -0
  79. data/spec/spec_helper.rb +16 -0
  80. data/vendor/assets/javascripts/mustache-0.8.1.js +570 -0
  81. data/vendor/assets/javascripts/perspectives/forms/text_field.mustache +3 -0
  82. data/vendor/assets/javascripts/perspectives.js +152 -0
  83. data/vendor/assets/javascripts/perspectives_views.js +1 -0
  84. metadata +233 -0
@@ -0,0 +1,16 @@
1
+ require 'perspectives'
2
+ require 'ostruct'
3
+ require 'pry'
4
+
5
+ # Load support files
6
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
7
+
8
+ RSpec.configure do |config|
9
+ config.color_enabled = true
10
+ end
11
+
12
+ Perspectives.configure do |c|
13
+ c.template_path = File.expand_path('../mustaches', __FILE__)
14
+ end
15
+
16
+ puts Perspectives.template_path
@@ -0,0 +1,570 @@
1
+ /*!
2
+ * mustache.js - Logic-less {{mustache}} templates with JavaScript
3
+ * http://github.com/janl/mustache.js
4
+ */
5
+
6
+ /*global define: false*/
7
+
8
+ (function (root, factory) {
9
+ if (typeof exports === "object" && exports) {
10
+ factory(exports); // CommonJS
11
+ } else {
12
+ var mustache = {};
13
+ factory(mustache);
14
+ if (typeof define === "function" && define.amd) {
15
+ define(mustache); // AMD
16
+ } else {
17
+ root.Mustache = mustache; // <script>
18
+ }
19
+ }
20
+ }(this, function (mustache) {
21
+
22
+ var whiteRe = /\s*/;
23
+ var spaceRe = /\s+/;
24
+ var nonSpaceRe = /\S/;
25
+ var eqRe = /\s*=/;
26
+ var curlyRe = /\s*\}/;
27
+ var tagRe = /#|\^|\/|>|\{|&|=|!/;
28
+
29
+ // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
30
+ // See https://github.com/janl/mustache.js/issues/189
31
+ var RegExp_test = RegExp.prototype.test;
32
+ function testRegExp(re, string) {
33
+ return RegExp_test.call(re, string);
34
+ }
35
+
36
+ function isWhitespace(string) {
37
+ return !testRegExp(nonSpaceRe, string);
38
+ }
39
+
40
+ var Object_toString = Object.prototype.toString;
41
+ var isArray = Array.isArray || function (object) {
42
+ return Object_toString.call(object) === '[object Array]';
43
+ };
44
+
45
+ function isFunction(object) {
46
+ return typeof object === 'function';
47
+ }
48
+
49
+ function escapeRegExp(string) {
50
+ return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
51
+ }
52
+
53
+ var entityMap = {
54
+ "&": "&amp;",
55
+ "<": "&lt;",
56
+ ">": "&gt;",
57
+ '"': '&quot;',
58
+ "'": '&#39;',
59
+ "/": '&#x2F;'
60
+ };
61
+
62
+ function escapeHtml(string) {
63
+ return String(string).replace(/[&<>"'\/]/g, function (s) {
64
+ return entityMap[s];
65
+ });
66
+ }
67
+
68
+ function escapeTags(tags) {
69
+ if (!isArray(tags) || tags.length !== 2) {
70
+ throw new Error('Invalid tags: ' + tags);
71
+ }
72
+
73
+ return [
74
+ new RegExp(escapeRegExp(tags[0]) + "\\s*"),
75
+ new RegExp("\\s*" + escapeRegExp(tags[1]))
76
+ ];
77
+ }
78
+
79
+ /**
80
+ * Breaks up the given `template` string into a tree of tokens. If the `tags`
81
+ * argument is given here it must be an array with two string values: the
82
+ * opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of
83
+ * course, the default is to use mustaches (i.e. mustache.tags).
84
+ *
85
+ * A token is an array with at least 4 elements. The first element is the
86
+ * mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag
87
+ * did not contain a symbol (i.e. {{myValue}}) this element is "name". For
88
+ * all template text that appears outside a symbol this element is "text".
89
+ *
90
+ * The second element of a token is its "value". For mustache tags this is
91
+ * whatever else was inside the tag besides the opening symbol. For text tokens
92
+ * this is the text itself.
93
+ *
94
+ * The third and fourth elements of the token are the start and end indices
95
+ * in the original template of the token, respectively.
96
+ *
97
+ * Tokens that are the root node of a subtree contain two more elements: an
98
+ * array of tokens in the subtree and the index in the original template at which
99
+ * the closing tag for that section begins.
100
+ */
101
+ function parseTemplate(template, tags) {
102
+ tags = tags || mustache.tags;
103
+ template = template || '';
104
+
105
+ if (typeof tags === 'string') {
106
+ tags = tags.split(spaceRe);
107
+ }
108
+
109
+ var tagRes = escapeTags(tags);
110
+ var scanner = new Scanner(template);
111
+
112
+ var sections = []; // Stack to hold section tokens
113
+ var tokens = []; // Buffer to hold the tokens
114
+ var spaces = []; // Indices of whitespace tokens on the current line
115
+ var hasTag = false; // Is there a {{tag}} on the current line?
116
+ var nonSpace = false; // Is there a non-space char on the current line?
117
+
118
+ // Strips all whitespace tokens array for the current line
119
+ // if there was a {{#tag}} on it and otherwise only space.
120
+ function stripSpace() {
121
+ if (hasTag && !nonSpace) {
122
+ while (spaces.length) {
123
+ delete tokens[spaces.pop()];
124
+ }
125
+ } else {
126
+ spaces = [];
127
+ }
128
+
129
+ hasTag = false;
130
+ nonSpace = false;
131
+ }
132
+
133
+ var start, type, value, chr, token, openSection;
134
+ while (!scanner.eos()) {
135
+ start = scanner.pos;
136
+
137
+ // Match any text between tags.
138
+ value = scanner.scanUntil(tagRes[0]);
139
+ if (value) {
140
+ for (var i = 0, len = value.length; i < len; ++i) {
141
+ chr = value.charAt(i);
142
+
143
+ if (isWhitespace(chr)) {
144
+ spaces.push(tokens.length);
145
+ } else {
146
+ nonSpace = true;
147
+ }
148
+
149
+ tokens.push(['text', chr, start, start + 1]);
150
+ start += 1;
151
+
152
+ // Check for whitespace on the current line.
153
+ if (chr === '\n') {
154
+ stripSpace();
155
+ }
156
+ }
157
+ }
158
+
159
+ // Match the opening tag.
160
+ if (!scanner.scan(tagRes[0])) break;
161
+ hasTag = true;
162
+
163
+ // Get the tag type.
164
+ type = scanner.scan(tagRe) || 'name';
165
+ scanner.scan(whiteRe);
166
+
167
+ // Get the tag value.
168
+ if (type === '=') {
169
+ value = scanner.scanUntil(eqRe);
170
+ scanner.scan(eqRe);
171
+ scanner.scanUntil(tagRes[1]);
172
+ } else if (type === '{') {
173
+ value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1])));
174
+ scanner.scan(curlyRe);
175
+ scanner.scanUntil(tagRes[1]);
176
+ type = '&';
177
+ } else {
178
+ value = scanner.scanUntil(tagRes[1]);
179
+ }
180
+
181
+ // Match the closing tag.
182
+ if (!scanner.scan(tagRes[1])) {
183
+ throw new Error('Unclosed tag at ' + scanner.pos);
184
+ }
185
+
186
+ token = [ type, value, start, scanner.pos ];
187
+ tokens.push(token);
188
+
189
+ if (type === '#' || type === '^') {
190
+ sections.push(token);
191
+ } else if (type === '/') {
192
+ // Check section nesting.
193
+ openSection = sections.pop();
194
+
195
+ if (!openSection) {
196
+ throw new Error('Unopened section "' + value + '" at ' + start);
197
+ }
198
+ if (openSection[1] !== value) {
199
+ throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
200
+ }
201
+ } else if (type === 'name' || type === '{' || type === '&') {
202
+ nonSpace = true;
203
+ } else if (type === '=') {
204
+ // Set the tags for the next time around.
205
+ tagRes = escapeTags(tags = value.split(spaceRe));
206
+ }
207
+ }
208
+
209
+ // Make sure there are no open sections when we're done.
210
+ openSection = sections.pop();
211
+ if (openSection) {
212
+ throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
213
+ }
214
+
215
+ return nestTokens(squashTokens(tokens));
216
+ }
217
+
218
+ /**
219
+ * Combines the values of consecutive text tokens in the given `tokens` array
220
+ * to a single token.
221
+ */
222
+ function squashTokens(tokens) {
223
+ var squashedTokens = [];
224
+
225
+ var token, lastToken;
226
+ for (var i = 0, len = tokens.length; i < len; ++i) {
227
+ token = tokens[i];
228
+
229
+ if (token) {
230
+ if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
231
+ lastToken[1] += token[1];
232
+ lastToken[3] = token[3];
233
+ } else {
234
+ squashedTokens.push(token);
235
+ lastToken = token;
236
+ }
237
+ }
238
+ }
239
+
240
+ return squashedTokens;
241
+ }
242
+
243
+ /**
244
+ * Forms the given array of `tokens` into a nested tree structure where
245
+ * tokens that represent a section have two additional items: 1) an array of
246
+ * all tokens that appear in that section and 2) the index in the original
247
+ * template that represents the end of that section.
248
+ */
249
+ function nestTokens(tokens) {
250
+ var nestedTokens = [];
251
+ var collector = nestedTokens;
252
+ var sections = [];
253
+
254
+ var token, section;
255
+ for (var i = 0, len = tokens.length; i < len; ++i) {
256
+ token = tokens[i];
257
+
258
+ switch (token[0]) {
259
+ case '#':
260
+ case '^':
261
+ collector.push(token);
262
+ sections.push(token);
263
+ collector = token[4] = [];
264
+ break;
265
+ case '/':
266
+ section = sections.pop();
267
+ section[5] = token[2];
268
+ collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
269
+ break;
270
+ default:
271
+ collector.push(token);
272
+ }
273
+ }
274
+
275
+ return nestedTokens;
276
+ }
277
+
278
+ /**
279
+ * A simple string scanner that is used by the template parser to find
280
+ * tokens in template strings.
281
+ */
282
+ function Scanner(string) {
283
+ this.string = string;
284
+ this.tail = string;
285
+ this.pos = 0;
286
+ }
287
+
288
+ /**
289
+ * Returns `true` if the tail is empty (end of string).
290
+ */
291
+ Scanner.prototype.eos = function () {
292
+ return this.tail === "";
293
+ };
294
+
295
+ /**
296
+ * Tries to match the given regular expression at the current position.
297
+ * Returns the matched text if it can match, the empty string otherwise.
298
+ */
299
+ Scanner.prototype.scan = function (re) {
300
+ var match = this.tail.match(re);
301
+
302
+ if (match && match.index === 0) {
303
+ var string = match[0];
304
+ this.tail = this.tail.substring(string.length);
305
+ this.pos += string.length;
306
+ return string;
307
+ }
308
+
309
+ return "";
310
+ };
311
+
312
+ /**
313
+ * Skips all text until the given regular expression can be matched. Returns
314
+ * the skipped string, which is the entire tail if no match can be made.
315
+ */
316
+ Scanner.prototype.scanUntil = function (re) {
317
+ var index = this.tail.search(re), match;
318
+
319
+ switch (index) {
320
+ case -1:
321
+ match = this.tail;
322
+ this.tail = "";
323
+ break;
324
+ case 0:
325
+ match = "";
326
+ break;
327
+ default:
328
+ match = this.tail.substring(0, index);
329
+ this.tail = this.tail.substring(index);
330
+ }
331
+
332
+ this.pos += match.length;
333
+
334
+ return match;
335
+ };
336
+
337
+ /**
338
+ * Represents a rendering context by wrapping a view object and
339
+ * maintaining a reference to the parent context.
340
+ */
341
+ function Context(view, parentContext) {
342
+ this.view = view == null ? {} : view;
343
+ this.cache = { '.': this.view };
344
+ this.parent = parentContext;
345
+ }
346
+
347
+ /**
348
+ * Creates a new context using the given view with this context
349
+ * as the parent.
350
+ */
351
+ Context.prototype.push = function (view) {
352
+ return new Context(view, this);
353
+ };
354
+
355
+ /**
356
+ * Returns the value of the given name in this context, traversing
357
+ * up the context hierarchy if the value is absent in this context's view.
358
+ */
359
+ Context.prototype.lookup = function (name) {
360
+ var value;
361
+ if (name in this.cache) {
362
+ value = this.cache[name];
363
+ } else {
364
+ var context = this;
365
+
366
+ while (context) {
367
+ if (name.indexOf('.') > 0) {
368
+ value = context.view;
369
+
370
+ var names = name.split('.'), i = 0;
371
+ while (value != null && i < names.length) {
372
+ value = value[names[i++]];
373
+ }
374
+ } else {
375
+ value = context.view[name];
376
+ }
377
+
378
+ if (value != null) break;
379
+
380
+ context = context.parent;
381
+ }
382
+
383
+ this.cache[name] = value;
384
+ }
385
+
386
+ if (isFunction(value)) {
387
+ value = value.call(this.view);
388
+ }
389
+
390
+ return value;
391
+ };
392
+
393
+ /**
394
+ * A Writer knows how to take a stream of tokens and render them to a
395
+ * string, given a context. It also maintains a cache of templates to
396
+ * avoid the need to parse the same template twice.
397
+ */
398
+ function Writer() {
399
+ this.cache = {};
400
+ }
401
+
402
+ /**
403
+ * Clears all cached templates in this writer.
404
+ */
405
+ Writer.prototype.clearCache = function () {
406
+ this.cache = {};
407
+ };
408
+
409
+ /**
410
+ * Parses and caches the given `template` and returns the array of tokens
411
+ * that is generated from the parse.
412
+ */
413
+ Writer.prototype.parse = function (template, tags) {
414
+ var cache = this.cache;
415
+ var tokens = cache[template];
416
+
417
+ if (tokens == null) {
418
+ tokens = cache[template] = parseTemplate(template, tags);
419
+ }
420
+
421
+ return tokens;
422
+ };
423
+
424
+ /**
425
+ * High-level method that is used to render the given `template` with
426
+ * the given `view`.
427
+ *
428
+ * The optional `partials` argument may be an object that contains the
429
+ * names and templates of partials that are used in the template. It may
430
+ * also be a function that is used to load partial templates on the fly
431
+ * that takes a single argument: the name of the partial.
432
+ */
433
+ Writer.prototype.render = function (template, view, partials) {
434
+ var tokens = this.parse(template);
435
+ var context = (view instanceof Context) ? view : new Context(view);
436
+ return this.renderTokens(tokens, context, partials, template);
437
+ };
438
+
439
+ /**
440
+ * Low-level method that renders the given array of `tokens` using
441
+ * the given `context` and `partials`.
442
+ *
443
+ * Note: The `originalTemplate` is only ever used to extract the portion
444
+ * of the original template that was contained in a higher-order section.
445
+ * If the template doesn't use higher-order sections, this argument may
446
+ * be omitted.
447
+ */
448
+ Writer.prototype.renderTokens = function (tokens, context, partials, originalTemplate) {
449
+ var buffer = '';
450
+
451
+ // This function is used to render an arbitrary template
452
+ // in the current context by higher-order sections.
453
+ var self = this;
454
+ function subRender(template) {
455
+ return self.render(template, context, partials);
456
+ }
457
+
458
+ var token, value;
459
+ for (var i = 0, len = tokens.length; i < len; ++i) {
460
+ token = tokens[i];
461
+
462
+ switch (token[0]) {
463
+ case '#':
464
+ value = context.lookup(token[1]);
465
+ if (!value) continue;
466
+
467
+ if (isArray(value)) {
468
+ for (var j = 0, jlen = value.length; j < jlen; ++j) {
469
+ buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
470
+ }
471
+ } else if (typeof value === 'object' || typeof value === 'string') {
472
+ buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);
473
+ } else if (isFunction(value)) {
474
+ if (typeof originalTemplate !== 'string') {
475
+ throw new Error('Cannot use higher-order sections without the original template');
476
+ }
477
+
478
+ // Extract the portion of the original template that the section contains.
479
+ value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);
480
+
481
+ if (value != null) buffer += value;
482
+ } else {
483
+ buffer += this.renderTokens(token[4], context, partials, originalTemplate);
484
+ }
485
+
486
+ break;
487
+ case '^':
488
+ value = context.lookup(token[1]);
489
+
490
+ // Use JavaScript's definition of falsy. Include empty arrays.
491
+ // See https://github.com/janl/mustache.js/issues/186
492
+ if (!value || (isArray(value) && value.length === 0)) {
493
+ buffer += this.renderTokens(token[4], context, partials, originalTemplate);
494
+ }
495
+
496
+ break;
497
+ case '>':
498
+ if (!partials) continue;
499
+ value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
500
+ if (value != null) buffer += this.renderTokens(this.parse(value), context, partials, value);
501
+ break;
502
+ case '&':
503
+ value = context.lookup(token[1]);
504
+ if (value != null) buffer += value;
505
+ break;
506
+ case 'name':
507
+ value = context.lookup(token[1]);
508
+ if (value != null) buffer += mustache.escape(value);
509
+ break;
510
+ case 'text':
511
+ buffer += token[1];
512
+ break;
513
+ }
514
+ }
515
+
516
+ return buffer;
517
+ };
518
+
519
+ mustache.name = "mustache.js";
520
+ mustache.version = "0.8.1";
521
+ mustache.tags = [ "{{", "}}" ];
522
+
523
+ // All high-level mustache.* functions use this writer.
524
+ var defaultWriter = new Writer();
525
+
526
+ /**
527
+ * Clears all cached templates in the default writer.
528
+ */
529
+ mustache.clearCache = function () {
530
+ return defaultWriter.clearCache();
531
+ };
532
+
533
+ /**
534
+ * Parses and caches the given template in the default writer and returns the
535
+ * array of tokens it contains. Doing this ahead of time avoids the need to
536
+ * parse templates on the fly as they are rendered.
537
+ */
538
+ mustache.parse = function (template, tags) {
539
+ return defaultWriter.parse(template, tags);
540
+ };
541
+
542
+ /**
543
+ * Renders the `template` with the given `view` and `partials` using the
544
+ * default writer.
545
+ */
546
+ mustache.render = function (template, view, partials) {
547
+ return defaultWriter.render(template, view, partials);
548
+ };
549
+
550
+ // This is here for backwards compatibility with 0.4.x.
551
+ mustache.to_html = function (template, view, partials, send) {
552
+ var result = mustache.render(template, view, partials);
553
+
554
+ if (isFunction(send)) {
555
+ send(result);
556
+ } else {
557
+ return result;
558
+ }
559
+ };
560
+
561
+ // Export the escaping function so that the user may override it.
562
+ // See https://github.com/janl/mustache.js/issues/244
563
+ mustache.escape = escapeHtml;
564
+
565
+ // Export these mainly for testing, but also for advanced usage.
566
+ mustache.Scanner = Scanner;
567
+ mustache.Context = Context;
568
+ mustache.Writer = Writer;
569
+
570
+ }));
@@ -0,0 +1,3 @@
1
+ <div><label for="{{field_id}}">{{name}}</label><br>
2
+ <input type="text" id="{{field_id}}" name="{{field_param}}" value="{{value}}" />
3
+ </div>