shared_mustache 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+