muj 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/Rakefile +2 -0
  2. data/bin/muj +59 -0
  3. data/lib/muj.rb +41 -0
  4. data/lib/mustache.js +422 -0
  5. metadata +88 -0
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks :name => 'muj'
data/bin/muj ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'muj'
5
+
6
+ $muj_cli_json = false
7
+
8
+ module Muj
9
+ class CLI
10
+ def self.parse_options(args)
11
+ opts = OptionParser.new do |opts|
12
+ opts.banner = "Usage: muj [--json] data.yml-or-json template_file.mjs"
13
+
14
+ opts.separator " "
15
+
16
+ opts.separator "Examples:"
17
+ opts.separator " $ muj data.json template.muj"
18
+ opts.separator " $ cat data.yml | muj - template.muj"
19
+ opts.separator " $ echo -e '{}' | muj - template.muj"
20
+
21
+ opts.separator " "
22
+
23
+ opts.on("-j","--json", "Skip YAML parser") { $muj_cli_json = true; ARGV.delete("-j"); ARGV.delete("--json") }
24
+
25
+ opts.on_tail("-h", "--help", "Show this message") do
26
+ puts opts
27
+ exit
28
+ end
29
+ end
30
+
31
+ opts.separator ""
32
+
33
+ opts.parse!(args)
34
+ end
35
+
36
+ def self.process_files(argv)
37
+ if argv.count != 2
38
+ exit(-1)
39
+ end
40
+ if argv.first == "-"
41
+ data = STDIN.read
42
+ else
43
+ data = File.open(argv.first).read
44
+ end
45
+ template = File.open(argv[1]).read
46
+ if $muj_cli_json
47
+ puts Muj.render_with_json(template,data)
48
+ else
49
+ puts Muj.render_with_yaml(template,data)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ ARGV << '-h' if ARGV.empty?
56
+
57
+ Muj::CLI.parse_options(ARGV)
58
+
59
+ Muj::CLI.process_files(ARGV)
data/lib/muj.rb ADDED
@@ -0,0 +1,41 @@
1
+ require 'tilt' unless defined? Tilt
2
+ require 'json' unless defined? JSON
3
+
4
+ module Tilt
5
+ class MujTemplate < Template
6
+ def initialize_engine
7
+ end
8
+
9
+ def prepare
10
+ end
11
+
12
+ def evaluate(scope, locals, &block)
13
+ Muj.eval(data,locals.to_json)
14
+ end
15
+ end
16
+ register 'muj', MujTemplate
17
+ end
18
+
19
+ module Muj
20
+
21
+ def self.eval(data,json)
22
+ require 'v8' unless defined? ::V8
23
+ cxt = ::V8::Context.new
24
+ cxt.eval('var locals='+json+'; for(var attrname in locals) {this[attrname] = locals[attrname]; }');
25
+ cxt.load(File.dirname(__FILE__)+"/mustache.js")
26
+ cxt.eval(data)
27
+ end
28
+
29
+ def self.render(str,locals={})
30
+ self.eval(str,locals.to_json)
31
+ end
32
+
33
+ def self.render_with_json(str,json)
34
+ self.eval(str,json)
35
+ end
36
+
37
+ def self.render_with_yaml(str,yml)
38
+ require 'yaml' unless defined? YAML
39
+ self.render(str,YAML.load(yml))
40
+ end
41
+ end
data/lib/mustache.js ADDED
@@ -0,0 +1,422 @@
1
+ /*
2
+ mustache.js — Logic-less templates in JavaScript
3
+
4
+ See http://mustache.github.com/ for more info.
5
+ */
6
+
7
+ var Mustache = function() {
8
+ var regexCache = {};
9
+ var Renderer = function() {};
10
+
11
+ Renderer.prototype = {
12
+ otag: "{{",
13
+ ctag: "}}",
14
+ pragmas: {},
15
+ buffer: [],
16
+ pragmas_implemented: {
17
+ "IMPLICIT-ITERATOR": true
18
+ },
19
+ context: {},
20
+
21
+ render: function(template, context, partials, in_recursion) {
22
+ // reset buffer & set context
23
+ if(!in_recursion) {
24
+ this.context = context;
25
+ this.buffer = []; // TODO: make this non-lazy
26
+ }
27
+
28
+ // fail fast
29
+ if(!this.includes("", template)) {
30
+ if(in_recursion) {
31
+ return template;
32
+ } else {
33
+ this.send(template);
34
+ return;
35
+ }
36
+ }
37
+
38
+ // get the pragmas together
39
+ template = this.render_pragmas(template);
40
+
41
+ // render the template
42
+ var html = this.render_section(template, context, partials);
43
+
44
+ // render_section did not find any sections, we still need to render the tags
45
+ if (html === false) {
46
+ html = this.render_tags(template, context, partials, in_recursion);
47
+ }
48
+
49
+ if (in_recursion) {
50
+ return html;
51
+ } else {
52
+ this.sendLines(html);
53
+ }
54
+ },
55
+
56
+ /*
57
+ Sends parsed lines
58
+ */
59
+ send: function(line) {
60
+ if(line !== "") {
61
+ this.buffer.push(line);
62
+ }
63
+ },
64
+
65
+ sendLines: function(text) {
66
+ if (text) {
67
+ var lines = text.split("\n");
68
+ for (var i = 0; i < lines.length; i++) {
69
+ this.send(lines[i]);
70
+ }
71
+ }
72
+ },
73
+
74
+ /*
75
+ Looks for %PRAGMAS
76
+ */
77
+ render_pragmas: function(template) {
78
+ // no pragmas
79
+ if(!this.includes("%", template)) {
80
+ return template;
81
+ }
82
+
83
+ var that = this;
84
+ var regex = this.getCachedRegex("render_pragmas", function(otag, ctag) {
85
+ return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
86
+ });
87
+
88
+ return template.replace(regex, function(match, pragma, options) {
89
+ if(!that.pragmas_implemented[pragma]) {
90
+ throw({message:
91
+ "This implementation of mustache doesn't understand the '" +
92
+ pragma + "' pragma"});
93
+ }
94
+ that.pragmas[pragma] = {};
95
+ if(options) {
96
+ var opts = options.split("=");
97
+ that.pragmas[pragma][opts[0]] = opts[1];
98
+ }
99
+ return "";
100
+ // ignore unknown pragmas silently
101
+ });
102
+ },
103
+
104
+ /*
105
+ Tries to find a partial in the curent scope and render it
106
+ */
107
+ render_partial: function(name, context, partials) {
108
+ name = this.trim(name);
109
+ if(!partials || partials[name] === undefined) {
110
+ throw({message: "unknown_partial '" + name + "'"});
111
+ }
112
+ if(typeof(context[name]) != "object") {
113
+ return this.render(partials[name], context, partials, true);
114
+ }
115
+ return this.render(partials[name], context[name], partials, true);
116
+ },
117
+
118
+ /*
119
+ Renders inverted (^) and normal (#) sections
120
+ */
121
+ render_section: function(template, context, partials) {
122
+ if(!this.includes("#", template) && !this.includes("^", template)) {
123
+ // did not render anything, there were no sections
124
+ return false;
125
+ }
126
+
127
+ var that = this;
128
+
129
+ var regex = this.getCachedRegex("render_section", function(otag, ctag) {
130
+ // This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
131
+ return new RegExp(
132
+ "^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1)
133
+
134
+ otag + // {{
135
+ "(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3)
136
+ ctag + // }}
137
+
138
+ "\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped
139
+
140
+ otag + // {{
141
+ "\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag).
142
+ ctag + // }}
143
+
144
+ "\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped.
145
+
146
+ "g");
147
+ });
148
+
149
+
150
+ // for each {{#foo}}{{/foo}} section do...
151
+ return template.replace(regex, function(match, before, type, name, content, after) {
152
+ // before contains only tags, no sections
153
+ var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",
154
+
155
+ // after may contain both sections and tags, so use full rendering function
156
+ renderedAfter = after ? that.render(after, context, partials, true) : "",
157
+
158
+ // will be computed below
159
+ renderedContent,
160
+
161
+ value = that.find(name, context);
162
+
163
+ if (type === "^") { // inverted section
164
+ if (!value || that.is_array(value) && value.length === 0) {
165
+ // false or empty list, render it
166
+ renderedContent = that.render(content, context, partials, true);
167
+ } else {
168
+ renderedContent = "";
169
+ }
170
+ } else if (type === "#") { // normal section
171
+ if (that.is_array(value)) { // Enumerable, Let's loop!
172
+ renderedContent = that.map(value, function(row) {
173
+ return that.render(content, that.create_context(row), partials, true);
174
+ }).join("");
175
+ } else if (that.is_object(value)) { // Object, Use it as subcontext!
176
+ renderedContent = that.render(content, that.create_context(value),
177
+ partials, true);
178
+ } else if (typeof value === "function") {
179
+ // higher order section
180
+ renderedContent = value.call(context, content, function(text) {
181
+ return that.render(text, context, partials, true);
182
+ });
183
+ } else if (value) { // boolean section
184
+ renderedContent = that.render(content, context, partials, true);
185
+ } else {
186
+ renderedContent = "";
187
+ }
188
+ }
189
+
190
+ return renderedBefore + renderedContent + renderedAfter;
191
+ });
192
+ },
193
+
194
+ /*
195
+ Replace {{foo}} and friends with values from our view
196
+ */
197
+ render_tags: function(template, context, partials, in_recursion) {
198
+ // tit for tat
199
+ var that = this;
200
+
201
+
202
+
203
+ var new_regex = function() {
204
+ return that.getCachedRegex("render_tags", function(otag, ctag) {
205
+ return new RegExp(otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + ctag + "+", "g");
206
+ });
207
+ };
208
+
209
+ var regex = new_regex();
210
+ var tag_replace_callback = function(match, operator, name) {
211
+ switch(operator) {
212
+ case "!": // ignore comments
213
+ return "";
214
+ case "=": // set new delimiters, rebuild the replace regexp
215
+ that.set_delimiters(name);
216
+ regex = new_regex();
217
+ return "";
218
+ case ">": // render partial
219
+ return that.render_partial(name, context, partials);
220
+ case "{": // the triple mustache is unescaped
221
+ return that.find(name, context);
222
+ default: // escape the value
223
+ return that.escape(that.find(name, context));
224
+ }
225
+ };
226
+ var lines = template.split("\n");
227
+ for(var i = 0; i < lines.length; i++) {
228
+ lines[i] = lines[i].replace(regex, tag_replace_callback, this);
229
+ if(!in_recursion) {
230
+ this.send(lines[i]);
231
+ }
232
+ }
233
+
234
+ if(in_recursion) {
235
+ return lines.join("\n");
236
+ }
237
+ },
238
+
239
+ set_delimiters: function(delimiters) {
240
+ var dels = delimiters.split(" ");
241
+ this.otag = this.escape_regex(dels[0]);
242
+ this.ctag = this.escape_regex(dels[1]);
243
+ },
244
+
245
+ escape_regex: function(text) {
246
+ // thank you Simon Willison
247
+ if(!arguments.callee.sRE) {
248
+ var specials = [
249
+ '/', '.', '*', '+', '?', '|',
250
+ '(', ')', '[', ']', '{', '}', '\\'
251
+ ];
252
+ arguments.callee.sRE = new RegExp(
253
+ '(\\' + specials.join('|\\') + ')', 'g'
254
+ );
255
+ }
256
+ return text.replace(arguments.callee.sRE, '\\$1');
257
+ },
258
+
259
+ /*
260
+ find `name` in current `context`. That is find me a value
261
+ from the view object
262
+ */
263
+ find: function(name, context) {
264
+ name = this.trim(name);
265
+
266
+ // Checks whether a value is thruthy or false or 0
267
+ function is_kinda_truthy(bool) {
268
+ return bool === false || bool === 0 || bool;
269
+ }
270
+
271
+ var value;
272
+
273
+ // check for dot notation eg. foo.bar
274
+ if(name.match(/([a-z_]+)\./ig)){
275
+ var childValue = this.walk_context(name, context);
276
+ if(is_kinda_truthy(childValue)) {
277
+ value = childValue;
278
+ }
279
+ }
280
+ else{
281
+ if(is_kinda_truthy(context[name])) {
282
+ value = context[name];
283
+ } else if(is_kinda_truthy(this.context[name])) {
284
+ value = this.context[name];
285
+ }
286
+ }
287
+
288
+ if(typeof value === "function") {
289
+ return value.apply(context);
290
+ }
291
+ if(value !== undefined) {
292
+ return value;
293
+ }
294
+ // silently ignore unkown variables
295
+ return "";
296
+ },
297
+
298
+ walk_context: function(name, context){
299
+ var path = name.split('.');
300
+ // if the var doesn't exist in current context, check the top level context
301
+ var value_context = (context[path[0]] != undefined) ? context : this.context;
302
+ var value = value_context[path.shift()];
303
+ while(value != undefined && path.length > 0){
304
+ value_context = value;
305
+ value = value[path.shift()];
306
+ }
307
+ // if the value is a function, call it, binding the correct context
308
+ if(typeof value === "function") {
309
+ return value.apply(value_context);
310
+ }
311
+ return value;
312
+ },
313
+
314
+ // Utility methods
315
+
316
+ /* includes tag */
317
+ includes: function(needle, haystack) {
318
+ return haystack.indexOf(this.otag + needle) != -1;
319
+ },
320
+
321
+ /*
322
+ Does away with nasty characters
323
+ */
324
+ escape: function(s) {
325
+ s = String(s === null ? "" : s);
326
+ return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
327
+ switch(s) {
328
+ case "&": return "&amp;";
329
+ case '"': return '&quot;';
330
+ case "'": return '&#39;';
331
+ case "<": return "&lt;";
332
+ case ">": return "&gt;";
333
+ default: return s;
334
+ }
335
+ });
336
+ },
337
+
338
+ // by @langalex, support for arrays of strings
339
+ create_context: function(_context) {
340
+ if(this.is_object(_context)) {
341
+ return _context;
342
+ } else {
343
+ var iterator = ".";
344
+ if(this.pragmas["IMPLICIT-ITERATOR"]) {
345
+ iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
346
+ }
347
+ var ctx = {};
348
+ ctx[iterator] = _context;
349
+ return ctx;
350
+ }
351
+ },
352
+
353
+ is_object: function(a) {
354
+ return a && typeof a == "object";
355
+ },
356
+
357
+ is_array: function(a) {
358
+ return Object.prototype.toString.call(a) === '[object Array]';
359
+ },
360
+
361
+ /*
362
+ Gets rid of leading and trailing whitespace
363
+ */
364
+ trim: function(s) {
365
+ return s.replace(/^\s*|\s*$/g, "");
366
+ },
367
+
368
+ /*
369
+ Why, why, why? Because IE. Cry, cry cry.
370
+ */
371
+ map: function(array, fn) {
372
+ if (typeof array.map == "function") {
373
+ return array.map(fn);
374
+ } else {
375
+ var r = [];
376
+ var l = array.length;
377
+ for(var i = 0; i < l; i++) {
378
+ r.push(fn(array[i]));
379
+ }
380
+ return r;
381
+ }
382
+ },
383
+
384
+ getCachedRegex: function(name, generator) {
385
+ var byOtag = regexCache[this.otag];
386
+ if (!byOtag) {
387
+ byOtag = regexCache[this.otag] = {};
388
+ }
389
+
390
+ var byCtag = byOtag[this.ctag];
391
+ if (!byCtag) {
392
+ byCtag = byOtag[this.ctag] = {};
393
+ }
394
+
395
+ var regex = byCtag[name];
396
+ if (!regex) {
397
+ regex = byCtag[name] = generator(this.otag, this.ctag);
398
+ }
399
+
400
+ return regex;
401
+ }
402
+ };
403
+
404
+ return({
405
+ name: "mustache.js",
406
+ version: "0.4.0-dev",
407
+
408
+ /*
409
+ Turns a template and view into HTML
410
+ */
411
+ to_html: function(template, view, partials, send_fun) {
412
+ var renderer = new Renderer();
413
+ if(send_fun) {
414
+ renderer.send = send_fun;
415
+ }
416
+ renderer.render(template, view || {}, partials);
417
+ if(!send_fun) {
418
+ return renderer.buffer.join("\n");
419
+ }
420
+ }
421
+ });
422
+ }();
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: muj
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Foy Savas
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-11-26 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: tilt
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: therubyracer
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :runtime
43
+ version_requirements: *id002
44
+ description: Sometimes one implementation (in Javascript) is just better. Also checkout muj-java via Maven.
45
+ email: foy@sav.as
46
+ executables:
47
+ - muj
48
+ extensions: []
49
+
50
+ extra_rdoc_files: []
51
+
52
+ files:
53
+ - Rakefile
54
+ - lib/muj.rb
55
+ - lib/mustache.js
56
+ - bin/muj
57
+ has_rdoc: true
58
+ homepage: http://github.com/foysavas/muj
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options: []
63
+
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.3.6
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Muj is Mustache.js for Ruby and your command line.
87
+ test_files: []
88
+