muj 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+