shared_mustache 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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mustached_rails.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 HM Government
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # SharedMustache
2
+
3
+ Add the ability to share [mustache][mustache] templates in Rails and
4
+ JavaScript. They will be compiled using [hogan][hogan] for production and
5
+ then compiled into your JavaScript using the asset-pipeline.
6
+
7
+ In development they will be rendered into script blocks on the page.
8
+
9
+ ## Installation
10
+
11
+ Add 'shared_mustache' to your your application's Gemfile.
12
+
13
+ By default when you run `rake shared_mustache:compile` it will put the outputed
14
+ templates JavaScript at `Rails.root/app/assets/javascript/templates.js`. This
15
+ can be configured using an intializer.
16
+
17
+ You should add the following lines to your `application.js`:
18
+
19
+ //= require shared_mustache
20
+ //= require templates
21
+
22
+ At the bottom of your main layout you should add:
23
+
24
+ <%= render_mustache_templates if Rails.env.development? %>
25
+
26
+ Add `rake shared_mustache:compile` before the `assets:precompile` step of your
27
+ deploy script.
28
+
29
+ Currently it expects your views to be found under `app/views`.
30
+
31
+ ## Usage
32
+
33
+ Create mustache templates as you would erb partials. So for example at
34
+ `app/views/home/_row.mustache`. You can then render it in an `erb` template
35
+ using:
36
+
37
+ <%= render_mustache('home/row', context) %>
38
+
39
+ It tries to use the same conventions as the native render methods. Such that
40
+ you can ommit the controller if the mustache is in the same folder as the
41
+ currently executing controller.
42
+
43
+ In your JavaScript you should get access to jQuery methods to interact with the
44
+ mustache templates:
45
+
46
+ $.mustache('home/_row', context);
47
+
48
+
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "gem_publisher"
2
+
3
+ desc "Publish gem to RubyGems.org"
4
+ task :publish_gem do |t|
5
+ gem = GemPublisher.publish_if_updated("shared_mustache.gemspec", :rubygems)
6
+ puts "Published #{gem}" if gem
7
+ end
8
+
9
+ task :test do |t|
10
+ true
11
+ end
12
+
13
+ task :default => :test
data/jenkins.sh ADDED
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+ set -e
3
+ rm -f Gemfile.lock
4
+ bundle install --path "${HOME}/bundles/${JOB_NAME}"
5
+ bundle exec rake
6
+ bundle exec rake publish_gem
@@ -0,0 +1,15 @@
1
+ module SharedMustache
2
+ # Change config options in an initializer:
3
+ #
4
+ # SharedMustache::Config.template_file = 'app/assets/javascriopt/mustache_templates.js'
5
+ #
6
+ module Config
7
+ extend self
8
+
9
+ attr_writer :template_file
10
+
11
+ def template_file
12
+ @template_file ||= File.join(Rails.root, 'app', 'assets', 'javascripts', 'templates.js')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ module SharedMustache
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,22 @@
1
+ require 'execjs'
2
+
3
+ module SharedMustache
4
+ module Hogan
5
+ def self.compile(source)
6
+ context.eval("(typeof exports !== 'undefined' ? exports : Hogan).compile(#{source.inspect}, {asString: true})")
7
+ end
8
+
9
+ private
10
+
11
+ def self.context
12
+ @context ||= ExecJS.compile(hogan_source)
13
+ end
14
+
15
+ def self.hogan_source
16
+ hogan_path = File.expand_path('../../vendor/assets/javascripts/hogan-2.0.0.js', File.dirname(__FILE__))
17
+ File.read(hogan_path)
18
+ end
19
+ end
20
+ end
21
+
22
+
@@ -0,0 +1,12 @@
1
+ require 'shared_mustache/view_helpers'
2
+
3
+ module SharedMustache
4
+ class Railtie < Rails::Railtie
5
+ initializer "shared_mustache.view_helpers" do
6
+ ActionView::Base.send :include, ViewHelpers
7
+ end
8
+ rake_tasks do
9
+ Dir[File.expand_path('../tasks/*.rake', File.dirname(__FILE__))].each { |f| load f }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module SharedMustache
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,21 @@
1
+ require 'mustache'
2
+
3
+ module SharedMustache
4
+ module ViewHelpers
5
+ def render_mustache_templates
6
+ file_list = SharedMustache.file_list
7
+
8
+ script_tags = file_list.map do |file|
9
+ content_tag(:script, File.read(file), id: SharedMustache.file_name_to_id(file), type: 'text/mustache')
10
+ end
11
+ script_tags << javascript_include_tag("hogan-2.0.0.js")
12
+ script_tags.join('').html_safe
13
+ end
14
+
15
+ def render_mustache(template, options={})
16
+ view = Mustache.new
17
+ view.template = File.read(SharedMustache.find_template_path(template, params[:controller]))
18
+ view.render(options).html_safe
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ require 'shared_mustache/config'
2
+ require 'shared_mustache/engine'
3
+ require 'shared_mustache/railtie' if defined?(Rails)
4
+ require 'shared_mustache/version'
5
+
6
+ module SharedMustache
7
+ def self.view_dir
8
+ File.join(Rails.root, 'app', 'views')
9
+ end
10
+
11
+ def self.file_list
12
+ Dir[File.join(view_dir, '**', '*.mustache')]
13
+ end
14
+
15
+ def self.file_name_to_id(filename)
16
+ filename.gsub('.mustache', '').gsub("#{view_dir}/", '').gsub('/', '-')
17
+ end
18
+
19
+ def self.find_template_path(filename, controller)
20
+ directory = File.dirname(filename)
21
+ if directory == '.'
22
+ directory = controller
23
+ end
24
+
25
+ File.join(view_dir, directory, "_#{File.basename(filename)}.mustache")
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ require 'shared_mustache/config'
2
+ require 'shared_mustache/hogan'
3
+
4
+ namespace :shared_mustache do
5
+ desc 'Compiles all mustache templates down for production use'
6
+ task :compile do
7
+ file_list = SharedMustache.file_list
8
+
9
+ javascript = []
10
+ javascript << 'window.templates = {};'
11
+
12
+ file_list.each do |file|
13
+ template_id = SharedMustache.file_name_to_id(file)
14
+ compiled_template = SharedMustache::Hogan.compile(File.read(file))
15
+ javascript << "window.templates['#{template_id}'] = new Hogan.Template(#{compiled_template});"
16
+ end
17
+
18
+ File.open(SharedMustache::Config.template_file, 'w') { |f| f.write javascript.join("\n") }
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'shared_mustache/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "shared_mustache"
8
+ gem.version = SharedMustache::VERSION
9
+ gem.authors = ["Edd Sowden"]
10
+ gem.email = ["edd.sowden@digital.cabinet-office.gov.uk"]
11
+ gem.description = %q{Share mustache between Rails templates and the browser (using compiled hogan.js templates).}
12
+ gem.summary = %q{Share mustache between Rails templates and the browser (using compiled hogan.js templates).}
13
+ gem.homepage = ""
14
+
15
+ gem.add_dependency 'mustache', '~> 0.99.4'
16
+ gem.add_dependency 'execjs', '~> 1.2.4'
17
+
18
+ gem.add_development_dependency "gem_publisher", "~> 1.1.1"
19
+
20
+ gem.files = `git ls-files`.split($/)
21
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
22
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
23
+ gem.require_paths = ["lib"]
24
+ end
@@ -0,0 +1,576 @@
1
+ /*
2
+ * Copyright 2011 Twitter, Inc.
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+
17
+
18
+ var Hogan = {};
19
+
20
+ (function (Hogan, useArrayBuffer) {
21
+ Hogan.Template = function (renderFunc, text, compiler, options) {
22
+ this.r = renderFunc || this.r;
23
+ this.c = compiler;
24
+ this.options = options;
25
+ this.text = text || '';
26
+ this.buf = (useArrayBuffer) ? [] : '';
27
+ }
28
+
29
+ Hogan.Template.prototype = {
30
+ // render: replaced by generated code.
31
+ r: function (context, partials, indent) { return ''; },
32
+
33
+ // variable escaping
34
+ v: hoganEscape,
35
+
36
+ // triple stache
37
+ t: coerceToString,
38
+
39
+ render: function render(context, partials, indent) {
40
+ return this.ri([context], partials || {}, indent);
41
+ },
42
+
43
+ // render internal -- a hook for overrides that catches partials too
44
+ ri: function (context, partials, indent) {
45
+ return this.r(context, partials, indent);
46
+ },
47
+
48
+ // tries to find a partial in the curent scope and render it
49
+ rp: function(name, context, partials, indent) {
50
+ var partial = partials[name];
51
+
52
+ if (!partial) {
53
+ return '';
54
+ }
55
+
56
+ if (this.c && typeof partial == 'string') {
57
+ partial = this.c.compile(partial, this.options);
58
+ }
59
+
60
+ return partial.ri(context, partials, indent);
61
+ },
62
+
63
+ // render a section
64
+ rs: function(context, partials, section) {
65
+ var tail = context[context.length - 1];
66
+
67
+ if (!isArray(tail)) {
68
+ section(context, partials, this);
69
+ return;
70
+ }
71
+
72
+ for (var i = 0; i < tail.length; i++) {
73
+ context.push(tail[i]);
74
+ section(context, partials, this);
75
+ context.pop();
76
+ }
77
+ },
78
+
79
+ // maybe start a section
80
+ s: function(val, ctx, partials, inverted, start, end, tags) {
81
+ var pass;
82
+
83
+ if (isArray(val) && val.length === 0) {
84
+ return false;
85
+ }
86
+
87
+ if (typeof val == 'function') {
88
+ val = this.ls(val, ctx, partials, inverted, start, end, tags);
89
+ }
90
+
91
+ pass = (val === '') || !!val;
92
+
93
+ if (!inverted && pass && ctx) {
94
+ ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]);
95
+ }
96
+
97
+ return pass;
98
+ },
99
+
100
+ // find values with dotted names
101
+ d: function(key, ctx, partials, returnFound) {
102
+ var names = key.split('.'),
103
+ val = this.f(names[0], ctx, partials, returnFound),
104
+ cx = null;
105
+
106
+ if (key === '.' && isArray(ctx[ctx.length - 2])) {
107
+ return ctx[ctx.length - 1];
108
+ }
109
+
110
+ for (var i = 1; i < names.length; i++) {
111
+ if (val && typeof val == 'object' && names[i] in val) {
112
+ cx = val;
113
+ val = val[names[i]];
114
+ } else {
115
+ val = '';
116
+ }
117
+ }
118
+
119
+ if (returnFound && !val) {
120
+ return false;
121
+ }
122
+
123
+ if (!returnFound && typeof val == 'function') {
124
+ ctx.push(cx);
125
+ val = this.lv(val, ctx, partials);
126
+ ctx.pop();
127
+ }
128
+
129
+ return val;
130
+ },
131
+
132
+ // find values with normal names
133
+ f: function(key, ctx, partials, returnFound) {
134
+ var val = false,
135
+ v = null,
136
+ found = false;
137
+
138
+ for (var i = ctx.length - 1; i >= 0; i--) {
139
+ v = ctx[i];
140
+ if (v && typeof v == 'object' && key in v) {
141
+ val = v[key];
142
+ found = true;
143
+ break;
144
+ }
145
+ }
146
+
147
+ if (!found) {
148
+ return (returnFound) ? false : "";
149
+ }
150
+
151
+ if (!returnFound && typeof val == 'function') {
152
+ val = this.lv(val, ctx, partials);
153
+ }
154
+
155
+ return val;
156
+ },
157
+
158
+ // higher order templates
159
+ ho: function(val, cx, partials, text, tags) {
160
+ var compiler = this.c;
161
+ var options = this.options;
162
+ options.delimiters = tags;
163
+ var text = val.call(cx, text);
164
+ text = (text == null) ? String(text) : text.toString();
165
+ this.b(compiler.compile(text, options).render(cx, partials));
166
+ return false;
167
+ },
168
+
169
+ // template result buffering
170
+ b: (useArrayBuffer) ? function(s) { this.buf.push(s); } :
171
+ function(s) { this.buf += s; },
172
+ fl: (useArrayBuffer) ? function() { var r = this.buf.join(''); this.buf = []; return r; } :
173
+ function() { var r = this.buf; this.buf = ''; return r; },
174
+
175
+ // lambda replace section
176
+ ls: function(val, ctx, partials, inverted, start, end, tags) {
177
+ var cx = ctx[ctx.length - 1],
178
+ t = null;
179
+
180
+ if (!inverted && this.c && val.length > 0) {
181
+ return this.ho(val, cx, partials, this.text.substring(start, end), tags);
182
+ }
183
+
184
+ t = val.call(cx);
185
+
186
+ if (typeof t == 'function') {
187
+ if (inverted) {
188
+ return true;
189
+ } else if (this.c) {
190
+ return this.ho(t, cx, partials, this.text.substring(start, end), tags);
191
+ }
192
+ }
193
+
194
+ return t;
195
+ },
196
+
197
+ // lambda replace variable
198
+ lv: function(val, ctx, partials) {
199
+ var cx = ctx[ctx.length - 1];
200
+ var result = val.call(cx);
201
+
202
+ if (typeof result == 'function') {
203
+ result = coerceToString(result.call(cx));
204
+ if (this.c && ~result.indexOf("{\u007B")) {
205
+ return this.c.compile(result, this.options).render(cx, partials);
206
+ }
207
+ }
208
+
209
+ return coerceToString(result);
210
+ }
211
+
212
+ };
213
+
214
+ var rAmp = /&/g,
215
+ rLt = /</g,
216
+ rGt = />/g,
217
+ rApos =/\'/g,
218
+ rQuot = /\"/g,
219
+ hChars =/[&<>\"\']/;
220
+
221
+
222
+ function coerceToString(val) {
223
+ return String((val === null || val === undefined) ? '' : val);
224
+ }
225
+
226
+ function hoganEscape(str) {
227
+ str = coerceToString(str);
228
+ return hChars.test(str) ?
229
+ str
230
+ .replace(rAmp,'&amp;')
231
+ .replace(rLt,'&lt;')
232
+ .replace(rGt,'&gt;')
233
+ .replace(rApos,'&#39;')
234
+ .replace(rQuot, '&quot;') :
235
+ str;
236
+ }
237
+
238
+ var isArray = Array.isArray || function(a) {
239
+ return Object.prototype.toString.call(a) === '[object Array]';
240
+ };
241
+
242
+ })(typeof exports !== 'undefined' ? exports : Hogan);
243
+
244
+
245
+
246
+
247
+ (function (Hogan) {
248
+ // Setup regex assignments
249
+ // remove whitespace according to Mustache spec
250
+ var rIsWhitespace = /\S/,
251
+ rQuot = /\"/g,
252
+ rNewline = /\n/g,
253
+ rCr = /\r/g,
254
+ rSlash = /\\/g,
255
+ tagTypes = {
256
+ '#': 1, '^': 2, '/': 3, '!': 4, '>': 5,
257
+ '<': 6, '=': 7, '_v': 8, '{': 9, '&': 10
258
+ };
259
+
260
+ Hogan.scan = function scan(text, delimiters) {
261
+ var len = text.length,
262
+ IN_TEXT = 0,
263
+ IN_TAG_TYPE = 1,
264
+ IN_TAG = 2,
265
+ state = IN_TEXT,
266
+ tagType = null,
267
+ tag = null,
268
+ buf = '',
269
+ tokens = [],
270
+ seenTag = false,
271
+ i = 0,
272
+ lineStart = 0,
273
+ otag = '{{',
274
+ ctag = '}}';
275
+
276
+ function addBuf() {
277
+ if (buf.length > 0) {
278
+ tokens.push(new String(buf));
279
+ buf = '';
280
+ }
281
+ }
282
+
283
+ function lineIsWhitespace() {
284
+ var isAllWhitespace = true;
285
+ for (var j = lineStart; j < tokens.length; j++) {
286
+ isAllWhitespace =
287
+ (tokens[j].tag && tagTypes[tokens[j].tag] < tagTypes['_v']) ||
288
+ (!tokens[j].tag && tokens[j].match(rIsWhitespace) === null);
289
+ if (!isAllWhitespace) {
290
+ return false;
291
+ }
292
+ }
293
+
294
+ return isAllWhitespace;
295
+ }
296
+
297
+ function filterLine(haveSeenTag, noNewLine) {
298
+ addBuf();
299
+
300
+ if (haveSeenTag && lineIsWhitespace()) {
301
+ for (var j = lineStart, next; j < tokens.length; j++) {
302
+ if (!tokens[j].tag) {
303
+ if ((next = tokens[j+1]) && next.tag == '>') {
304
+ // set indent to token value
305
+ next.indent = tokens[j].toString()
306
+ }
307
+ tokens.splice(j, 1);
308
+ }
309
+ }
310
+ } else if (!noNewLine) {
311
+ tokens.push({tag:'\n'});
312
+ }
313
+
314
+ seenTag = false;
315
+ lineStart = tokens.length;
316
+ }
317
+
318
+ function changeDelimiters(text, index) {
319
+ var close = '=' + ctag,
320
+ closeIndex = text.indexOf(close, index),
321
+ delimiters = trim(
322
+ text.substring(text.indexOf('=', index) + 1, closeIndex)
323
+ ).split(' ');
324
+
325
+ otag = delimiters[0];
326
+ ctag = delimiters[1];
327
+
328
+ return closeIndex + close.length - 1;
329
+ }
330
+
331
+ if (delimiters) {
332
+ delimiters = delimiters.split(' ');
333
+ otag = delimiters[0];
334
+ ctag = delimiters[1];
335
+ }
336
+
337
+ for (i = 0; i < len; i++) {
338
+ if (state == IN_TEXT) {
339
+ if (tagChange(otag, text, i)) {
340
+ --i;
341
+ addBuf();
342
+ state = IN_TAG_TYPE;
343
+ } else {
344
+ if (text.charAt(i) == '\n') {
345
+ filterLine(seenTag);
346
+ } else {
347
+ buf += text.charAt(i);
348
+ }
349
+ }
350
+ } else if (state == IN_TAG_TYPE) {
351
+ i += otag.length - 1;
352
+ tag = tagTypes[text.charAt(i + 1)];
353
+ tagType = tag ? text.charAt(i + 1) : '_v';
354
+ if (tagType == '=') {
355
+ i = changeDelimiters(text, i);
356
+ state = IN_TEXT;
357
+ } else {
358
+ if (tag) {
359
+ i++;
360
+ }
361
+ state = IN_TAG;
362
+ }
363
+ seenTag = i;
364
+ } else {
365
+ if (tagChange(ctag, text, i)) {
366
+ tokens.push({tag: tagType, n: trim(buf), otag: otag, ctag: ctag,
367
+ i: (tagType == '/') ? seenTag - ctag.length : i + otag.length});
368
+ buf = '';
369
+ i += ctag.length - 1;
370
+ state = IN_TEXT;
371
+ if (tagType == '{') {
372
+ if (ctag == '}}') {
373
+ i++;
374
+ } else {
375
+ cleanTripleStache(tokens[tokens.length - 1]);
376
+ }
377
+ }
378
+ } else {
379
+ buf += text.charAt(i);
380
+ }
381
+ }
382
+ }
383
+
384
+ filterLine(seenTag, true);
385
+
386
+ return tokens;
387
+ }
388
+
389
+ function cleanTripleStache(token) {
390
+ if (token.n.substr(token.n.length - 1) === '}') {
391
+ token.n = token.n.substring(0, token.n.length - 1);
392
+ }
393
+ }
394
+
395
+ function trim(s) {
396
+ if (s.trim) {
397
+ return s.trim();
398
+ }
399
+
400
+ return s.replace(/^\s*|\s*$/g, '');
401
+ }
402
+
403
+ function tagChange(tag, text, index) {
404
+ if (text.charAt(index) != tag.charAt(0)) {
405
+ return false;
406
+ }
407
+
408
+ for (var i = 1, l = tag.length; i < l; i++) {
409
+ if (text.charAt(index + i) != tag.charAt(i)) {
410
+ return false;
411
+ }
412
+ }
413
+
414
+ return true;
415
+ }
416
+
417
+ function buildTree(tokens, kind, stack, customTags) {
418
+ var instructions = [],
419
+ opener = null,
420
+ token = null;
421
+
422
+ while (tokens.length > 0) {
423
+ token = tokens.shift();
424
+ if (token.tag == '#' || token.tag == '^' || isOpener(token, customTags)) {
425
+ stack.push(token);
426
+ token.nodes = buildTree(tokens, token.tag, stack, customTags);
427
+ instructions.push(token);
428
+ } else if (token.tag == '/') {
429
+ if (stack.length === 0) {
430
+ throw new Error('Closing tag without opener: /' + token.n);
431
+ }
432
+ opener = stack.pop();
433
+ if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) {
434
+ throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n);
435
+ }
436
+ opener.end = token.i;
437
+ return instructions;
438
+ } else {
439
+ instructions.push(token);
440
+ }
441
+ }
442
+
443
+ if (stack.length > 0) {
444
+ throw new Error('missing closing tag: ' + stack.pop().n);
445
+ }
446
+
447
+ return instructions;
448
+ }
449
+
450
+ function isOpener(token, tags) {
451
+ for (var i = 0, l = tags.length; i < l; i++) {
452
+ if (tags[i].o == token.n) {
453
+ token.tag = '#';
454
+ return true;
455
+ }
456
+ }
457
+ }
458
+
459
+ function isCloser(close, open, tags) {
460
+ for (var i = 0, l = tags.length; i < l; i++) {
461
+ if (tags[i].c == close && tags[i].o == open) {
462
+ return true;
463
+ }
464
+ }
465
+ }
466
+
467
+ Hogan.generate = function (tree, text, options) {
468
+ var code = 'var _=this;_.b(i=i||"");' + walk(tree) + 'return _.fl();';
469
+ if (options.asString) {
470
+ return 'function(c,p,i){' + code + ';}';
471
+ }
472
+
473
+ return new Hogan.Template(new Function('c', 'p', 'i', code), text, Hogan, options);
474
+ }
475
+
476
+ function esc(s) {
477
+ return s.replace(rSlash, '\\\\')
478
+ .replace(rQuot, '\\\"')
479
+ .replace(rNewline, '\\n')
480
+ .replace(rCr, '\\r');
481
+ }
482
+
483
+ function chooseMethod(s) {
484
+ return (~s.indexOf('.')) ? 'd' : 'f';
485
+ }
486
+
487
+ function walk(tree) {
488
+ var code = '';
489
+ for (var i = 0, l = tree.length; i < l; i++) {
490
+ var tag = tree[i].tag;
491
+ if (tag == '#') {
492
+ code += section(tree[i].nodes, tree[i].n, chooseMethod(tree[i].n),
493
+ tree[i].i, tree[i].end, tree[i].otag + " " + tree[i].ctag);
494
+ } else if (tag == '^') {
495
+ code += invertedSection(tree[i].nodes, tree[i].n,
496
+ chooseMethod(tree[i].n));
497
+ } else if (tag == '<' || tag == '>') {
498
+ code += partial(tree[i]);
499
+ } else if (tag == '{' || tag == '&') {
500
+ code += tripleStache(tree[i].n, chooseMethod(tree[i].n));
501
+ } else if (tag == '\n') {
502
+ code += text('"\\n"' + (tree.length-1 == i ? '' : ' + i'));
503
+ } else if (tag == '_v') {
504
+ code += variable(tree[i].n, chooseMethod(tree[i].n));
505
+ } else if (tag === undefined) {
506
+ code += text('"' + esc(tree[i]) + '"');
507
+ }
508
+ }
509
+ return code;
510
+ }
511
+
512
+ function section(nodes, id, method, start, end, tags) {
513
+ return 'if(_.s(_.' + method + '("' + esc(id) + '",c,p,1),' +
514
+ 'c,p,0,' + start + ',' + end + ',"' + tags + '")){' +
515
+ '_.rs(c,p,' +
516
+ 'function(c,p,_){' +
517
+ walk(nodes) +
518
+ '});c.pop();}';
519
+ }
520
+
521
+ function invertedSection(nodes, id, method) {
522
+ return 'if(!_.s(_.' + method + '("' + esc(id) + '",c,p,1),c,p,1,0,0,"")){' +
523
+ walk(nodes) +
524
+ '};';
525
+ }
526
+
527
+ function partial(tok) {
528
+ return '_.b(_.rp("' + esc(tok.n) + '",c,p,"' + (tok.indent || '') + '"));';
529
+ }
530
+
531
+ function tripleStache(id, method) {
532
+ return '_.b(_.t(_.' + method + '("' + esc(id) + '",c,p,0)));';
533
+ }
534
+
535
+ function variable(id, method) {
536
+ return '_.b(_.v(_.' + method + '("' + esc(id) + '",c,p,0)));';
537
+ }
538
+
539
+ function text(id) {
540
+ return '_.b(' + id + ');';
541
+ }
542
+
543
+ Hogan.parse = function(tokens, text, options) {
544
+ options = options || {};
545
+ return buildTree(tokens, '', [], options.sectionTags || []);
546
+ },
547
+
548
+ Hogan.cache = {};
549
+
550
+ Hogan.compile = function(text, options) {
551
+ // options
552
+ //
553
+ // asString: false (default)
554
+ //
555
+ // sectionTags: [{o: '_foo', c: 'foo'}]
556
+ // An array of object with o and c fields that indicate names for custom
557
+ // section tags. The example above allows parsing of {{_foo}}{{/foo}}.
558
+ //
559
+ // delimiters: A string that overrides the default delimiters.
560
+ // Example: "<% %>"
561
+ //
562
+ options = options || {};
563
+
564
+ var key = text + '||' + !!options.asString;
565
+
566
+ var t = this.cache[key];
567
+
568
+ if (t) {
569
+ return t;
570
+ }
571
+
572
+ t = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options);
573
+ return this.cache[key] = t;
574
+ };
575
+ })(typeof exports !== 'undefined' ? exports : Hogan);
576
+
@@ -0,0 +1,156 @@
1
+ /*
2
+
3
+ Copyright (c) 2012, Bradley Wright
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+ * Neither the name of the <organization> nor the
14
+ names of its contributors may be used to endorse or promote products
15
+ derived from this software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
21
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ */
29
+ /*
30
+ * mustache-loader.js - Mustache template loader to go with flask-mustache
31
+ *
32
+ * This depends on jQuery, and either:
33
+ * - Twitter's Hogan.js: https://github.com/twitter/hogan.js or
34
+ * - Mustache.js: https://github.com/janl/mustache.js
35
+ *
36
+ * Usage:
37
+ *
38
+ * $('#target').mustache('includes/_user.mustache', {user_name:'Jan'});
39
+ * var html = $.mustache('includes/_user.mustache', {user_name:'Jan'});
40
+ * $.mustacheAsFunction('includes/_user.mustache')({user_name:'Jan'});
41
+ */
42
+
43
+ /*jslint
44
+ browser: true,
45
+ white: true,
46
+ vars: true
47
+ */
48
+
49
+ /*globals jQuery */
50
+
51
+ // authored as a jQuery plugin
52
+ (function($) {
53
+ "use strict";
54
+
55
+ // this is a cached lookup table of templates
56
+ var cache = {};
57
+
58
+ var load = function(templateName) {
59
+ // this function takes names like: "includes/_user.mustache"
60
+ // and loads them from somewhere else.
61
+
62
+ // first we need to convert slashes to hyphens, since
63
+ // they're DOM valid
64
+ templateName = templateName.replace('/', '-');
65
+
66
+ // they can be cached as functions, or as strings.
67
+ // Strings are template content.
68
+ if (typeof cache[templateName] === 'undefined') {
69
+ if (document.getElementById(templateName)) {
70
+ // stupid hack to turn HTML-encoded templates into strings, see:
71
+ // http://stackoverflow.com/a/2419664/61435
72
+ cache[templateName] = $('<div />').html(
73
+ $(document.getElementById(templateName)).html().trim()).text();
74
+ }
75
+ else if (templates[templateName]){
76
+ cache[templateName] = templates[templateName];
77
+ }
78
+ }
79
+
80
+ return cache[templateName];
81
+ };
82
+
83
+ var compile = function(templateName) {
84
+ // returns a compiled template.
85
+ // only works with Hogan.js or if templates pre-compiled.
86
+ var templateContent = load(templateName),
87
+ template = null;
88
+
89
+ if (typeof templateContent === 'string' && window.Hogan) {
90
+ template = cache[templateName] = window.Hogan.compile(templateContent);
91
+ }
92
+ if (template === null) {
93
+ $.error("Couldn't compile template " + templateName);
94
+ }
95
+ return template;
96
+ };
97
+
98
+ var renderFunction = function(templateName) {
99
+ // returns a wrapped `render` function
100
+ // only works with Hogan.js or if templates pre-compiled.
101
+ var template = compile(templateName);
102
+
103
+ return function(context) {
104
+ return template.render(context);
105
+ };
106
+ };
107
+
108
+ var render = function(templateName, context) {
109
+
110
+ // first we need to try and load the template
111
+ var template = load(templateName);
112
+
113
+ if (typeof template === 'undefined') {
114
+ $.error('Unknown template ' + templateName);
115
+ }
116
+ // pre-compiled hogan templates are objects
117
+ else if (typeof template === 'object') {
118
+ // template has been pre-compiled, just render and return it
119
+ return template.render(context);
120
+ }
121
+
122
+ // template hasn't been pre-compiled yet
123
+ // so we need to do other things
124
+ if (window.Hogan) {
125
+ return window.Hogan.compile(template).render(context);
126
+ }
127
+
128
+ if (window.Mustache) {
129
+ return window.Mustache.render(template, context);
130
+ }
131
+
132
+ // we don't have Hogan or Mustache, so we need to bail
133
+ $.error('Must have either Hogan.js or Mustache.js to load string templates');
134
+ };
135
+
136
+ $.fn.mustache = function(templateName, context) {
137
+ // replaces the content of the passed in element with the content
138
+ // rendered by Mustache
139
+
140
+ return this.html(render(templateName, context));
141
+ };
142
+
143
+ $.mustache = function(templateName, context) {
144
+ // returns the compiled HTML
145
+
146
+ return render(templateName, context);
147
+ };
148
+
149
+ $.mustacheAsFunction = function(templateName) {
150
+ // returns a function that can be used to render the
151
+ // mustache template
152
+
153
+ return renderFunction(templateName);
154
+ };
155
+
156
+ }(jQuery));
@@ -0,0 +1,243 @@
1
+ /*
2
+ * Copyright 2011 Twitter, Inc.
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+ if(typeof window.Hogan === 'undefined'){
17
+ window.Hogan = {};
18
+ }
19
+
20
+ (function (Hogan, useArrayBuffer) {
21
+ Hogan.Template = function (renderFunc, text, compiler, options) {
22
+ this.r = renderFunc || this.r;
23
+ this.c = compiler;
24
+ this.options = options;
25
+ this.text = text || '';
26
+ this.buf = (useArrayBuffer) ? [] : '';
27
+ }
28
+
29
+ Hogan.Template.prototype = {
30
+ // render: replaced by generated code.
31
+ r: function (context, partials, indent) { return ''; },
32
+
33
+ // variable escaping
34
+ v: hoganEscape,
35
+
36
+ // triple stache
37
+ t: coerceToString,
38
+
39
+ render: function render(context, partials, indent) {
40
+ return this.ri([context], partials || {}, indent);
41
+ },
42
+
43
+ // render internal -- a hook for overrides that catches partials too
44
+ ri: function (context, partials, indent) {
45
+ return this.r(context, partials, indent);
46
+ },
47
+
48
+ // tries to find a partial in the curent scope and render it
49
+ rp: function(name, context, partials, indent) {
50
+ var partial = partials[name];
51
+
52
+ if (!partial) {
53
+ return '';
54
+ }
55
+
56
+ if (this.c && typeof partial == 'string') {
57
+ partial = this.c.compile(partial, this.options);
58
+ }
59
+
60
+ return partial.ri(context, partials, indent);
61
+ },
62
+
63
+ // render a section
64
+ rs: function(context, partials, section) {
65
+ var tail = context[context.length - 1];
66
+
67
+ if (!isArray(tail)) {
68
+ section(context, partials, this);
69
+ return;
70
+ }
71
+
72
+ for (var i = 0; i < tail.length; i++) {
73
+ context.push(tail[i]);
74
+ section(context, partials, this);
75
+ context.pop();
76
+ }
77
+ },
78
+
79
+ // maybe start a section
80
+ s: function(val, ctx, partials, inverted, start, end, tags) {
81
+ var pass;
82
+
83
+ if (isArray(val) && val.length === 0) {
84
+ return false;
85
+ }
86
+
87
+ if (typeof val == 'function') {
88
+ val = this.ls(val, ctx, partials, inverted, start, end, tags);
89
+ }
90
+
91
+ pass = (val === '') || !!val;
92
+
93
+ if (!inverted && pass && ctx) {
94
+ ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]);
95
+ }
96
+
97
+ return pass;
98
+ },
99
+
100
+ // find values with dotted names
101
+ d: function(key, ctx, partials, returnFound) {
102
+ var names = key.split('.'),
103
+ val = this.f(names[0], ctx, partials, returnFound),
104
+ cx = null;
105
+
106
+ if (key === '.' && isArray(ctx[ctx.length - 2])) {
107
+ return ctx[ctx.length - 1];
108
+ }
109
+
110
+ for (var i = 1; i < names.length; i++) {
111
+ if (val && typeof val == 'object' && names[i] in val) {
112
+ cx = val;
113
+ val = val[names[i]];
114
+ } else {
115
+ val = '';
116
+ }
117
+ }
118
+
119
+ if (returnFound && !val) {
120
+ return false;
121
+ }
122
+
123
+ if (!returnFound && typeof val == 'function') {
124
+ ctx.push(cx);
125
+ val = this.lv(val, ctx, partials);
126
+ ctx.pop();
127
+ }
128
+
129
+ return val;
130
+ },
131
+
132
+ // find values with normal names
133
+ f: function(key, ctx, partials, returnFound) {
134
+ var val = false,
135
+ v = null,
136
+ found = false;
137
+
138
+ for (var i = ctx.length - 1; i >= 0; i--) {
139
+ v = ctx[i];
140
+ if (v && typeof v == 'object' && key in v) {
141
+ val = v[key];
142
+ found = true;
143
+ break;
144
+ }
145
+ }
146
+
147
+ if (!found) {
148
+ return (returnFound) ? false : "";
149
+ }
150
+
151
+ if (!returnFound && typeof val == 'function') {
152
+ val = this.lv(val, ctx, partials);
153
+ }
154
+
155
+ return val;
156
+ },
157
+
158
+ // higher order templates
159
+ ho: function(val, cx, partials, text, tags) {
160
+ var compiler = this.c;
161
+ var options = this.options;
162
+ options.delimiters = tags;
163
+ var text = val.call(cx, text);
164
+ text = (text == null) ? String(text) : text.toString();
165
+ this.b(compiler.compile(text, options).render(cx, partials));
166
+ return false;
167
+ },
168
+
169
+ // template result buffering
170
+ b: (useArrayBuffer) ? function(s) { this.buf.push(s); } :
171
+ function(s) { this.buf += s; },
172
+ fl: (useArrayBuffer) ? function() { var r = this.buf.join(''); this.buf = []; return r; } :
173
+ function() { var r = this.buf; this.buf = ''; return r; },
174
+
175
+ // lambda replace section
176
+ ls: function(val, ctx, partials, inverted, start, end, tags) {
177
+ var cx = ctx[ctx.length - 1],
178
+ t = null;
179
+
180
+ if (!inverted && this.c && val.length > 0) {
181
+ return this.ho(val, cx, partials, this.text.substring(start, end), tags);
182
+ }
183
+
184
+ t = val.call(cx);
185
+
186
+ if (typeof t == 'function') {
187
+ if (inverted) {
188
+ return true;
189
+ } else if (this.c) {
190
+ return this.ho(t, cx, partials, this.text.substring(start, end), tags);
191
+ }
192
+ }
193
+
194
+ return t;
195
+ },
196
+
197
+ // lambda replace variable
198
+ lv: function(val, ctx, partials) {
199
+ var cx = ctx[ctx.length - 1];
200
+ var result = val.call(cx);
201
+
202
+ if (typeof result == 'function') {
203
+ result = coerceToString(result.call(cx));
204
+ if (this.c && ~result.indexOf("{\u007B")) {
205
+ return this.c.compile(result, this.options).render(cx, partials);
206
+ }
207
+ }
208
+
209
+ return coerceToString(result);
210
+ }
211
+
212
+ };
213
+
214
+ var rAmp = /&/g,
215
+ rLt = /</g,
216
+ rGt = />/g,
217
+ rApos =/\'/g,
218
+ rQuot = /\"/g,
219
+ hChars =/[&<>\"\']/;
220
+
221
+
222
+ function coerceToString(val) {
223
+ return String((val === null || val === undefined) ? '' : val);
224
+ }
225
+
226
+ function hoganEscape(str) {
227
+ str = coerceToString(str);
228
+ return hChars.test(str) ?
229
+ str
230
+ .replace(rAmp,'&amp;')
231
+ .replace(rLt,'&lt;')
232
+ .replace(rGt,'&gt;')
233
+ .replace(rApos,'&#39;')
234
+ .replace(rQuot, '&quot;') :
235
+ str;
236
+ }
237
+
238
+ var isArray = Array.isArray || function(a) {
239
+ return Object.prototype.toString.call(a) === '[object Array]';
240
+ };
241
+
242
+ })(typeof exports !== 'undefined' ? exports : Hogan);
243
+
@@ -0,0 +1 @@
1
+ //= require_tree ./shared_mustache
File without changes
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shared_mustache
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Edd Sowden
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2013-01-29 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mustache
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 0.99.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: execjs
28
+ requirement: &id002 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.2.4
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: gem_publisher
39
+ requirement: &id003 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 1.1.1
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *id003
48
+ description: Share mustache between Rails templates and the browser (using compiled hogan.js templates).
49
+ email:
50
+ - edd.sowden@digital.cabinet-office.gov.uk
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files: []
56
+
57
+ files:
58
+ - .gitignore
59
+ - Gemfile
60
+ - LICENSE.txt
61
+ - README.md
62
+ - Rakefile
63
+ - jenkins.sh
64
+ - lib/shared_mustache.rb
65
+ - lib/shared_mustache/config.rb
66
+ - lib/shared_mustache/engine.rb
67
+ - lib/shared_mustache/hogan.rb
68
+ - lib/shared_mustache/railtie.rb
69
+ - lib/shared_mustache/version.rb
70
+ - lib/shared_mustache/view_helpers.rb
71
+ - lib/tasks/shared_mustache.rake
72
+ - shared_mustache.gemspec
73
+ - vendor/assets/javascripts/hogan-2.0.0.js
74
+ - vendor/assets/javascripts/shared_mustache.js
75
+ - vendor/assets/javascripts/shared_mustache/mustache-loader.js
76
+ - vendor/assets/javascripts/shared_mustache/template-2.0.0.js
77
+ - vendor/assets/javascripts/templates.js
78
+ homepage: ""
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options: []
83
+
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 1469856526765400623
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 1469856526765400623
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project:
107
+ rubygems_version: 1.8.24
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Share mustache between Rails templates and the browser (using compiled hogan.js templates).
111
+ test_files: []
112
+