Created
March 10, 2010 22:57
-
-
Save oz/328559 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# A very straightforward (and ugly :p) translation of mustache.js to | |
# coffee-script. 'should get faster by using CS's list comprehensions | |
# too! -- oz | |
# | |
# Generate mustache.js with: with coffee -c mustache.coffee | |
# Then clone janl's mustache.js, and run "rake spec" against the | |
# generated version. | |
# Current window object in DOM | |
root: exports ? this | |
Mustache: -> | |
Renderer: -> undefined | |
Renderer.prototype: { | |
otag: "{{" | |
ctag: "}}" | |
pragmas: {} | |
buffer: [] | |
pragmas_parsed: false | |
pragmas_implemented: { | |
"IMPLICIT-ITERATOR": true | |
} | |
render: (template, context, partials, in_recursion) -> | |
if -1 == template.indexOf @otag | |
if in_recursion | |
return template | |
else | |
this.send template | |
return | |
@buffer: [] unless in_recursion | |
template: this.render_pragmas template unless @pragmas_parsed | |
html: this.render_section template, context, partials | |
#this.render_tags html, context, partials, in_recursion if in_recursion | |
this.render_tags html, context, partials, in_recursion | |
# | |
# Send parsed lines | |
# | |
send: (line) -> @buffer.push line if line != "" | |
# | |
# Looks for %PRAGMAS | |
# | |
render_pragmas: (template) -> | |
@pragmas_parsed: true | |
# no pragmas | |
return template if -1 == template.indexOf @otag + "%" | |
that: this | |
regex: new RegExp @otag + "%([\\w_-]+) ?([\\w]+=[\\w]+)?" + @ctag | |
template.replace regex, (match, pragma, options) -> | |
throw({message: "This implementation of mustache doesn't understand " + | |
"the '" + pragma + "' pragma"}) unless that.pragmas_implemented[pragma] | |
that.pragmas[pragma]: {} | |
if options | |
opts: options.split "=" | |
that.pragmas[pragma][opts[0]]: opts[1] | |
"" | |
# ignore unknown pragmas silently | |
# | |
# Tries to find a partial in the global scope and render it | |
# | |
render_partial: (name, context, partials) -> | |
throw({message: "subcontext for '" + name + "' is not an object"}) if "object" isnt typeof(context[name]) | |
throw({message: "unknown_partial"}) if not partials or not partials[name] | |
this.render partials[name], context[name], partials, true | |
# | |
# Renders boolean and enumerable sections | |
# | |
render_section: (template, context, partials) -> | |
return template if -1 == template.indexOf @otag + "#" | |
that: this | |
# CSW - Added "+?" so it finds the tighest bound, not the widest | |
regex: new RegExp @otag + "\\#(.+)" + @ctag + | |
"\\s*([\\s\\S]+?)" + @otag + "\\/\\1" + @ctag + "\\s*", "mg" | |
# for each {{#foo}}{{/foo}} section do... | |
template.replace regex, (match, name, content) -> | |
value: that.find name, context | |
if that.is_array value # Enumerable, Let's loop! | |
that.map value, (row) -> | |
that.render content, that.merge(context, that.create_context(row)), partials, true | |
.join '' | |
else if value # boolean section | |
return that.render content, context, partials, true | |
else | |
return "" | |
# | |
# Replace {{foo}} and friends with values from our view | |
# | |
render_tags: (template, context, partials, in_recursion) -> | |
# tit for tat | |
that: this | |
new_regex: () -> | |
new RegExp that.otag + "(=|!|>|\\{|%)?([^\/#]+?)\\1?" + that.ctag + "+", "g" | |
regex: new_regex() | |
lines: template.split "\n" | |
lines_length: lines.length | |
i: 0 | |
while i < lines_length | |
lines[i]: lines[i].replace regex, (match, operator, name) -> | |
switch operator | |
# ignore comments | |
when "!" then return match | |
# set new delimiters, rebuild the replace regexp | |
when "=" | |
that.set_delimiters name | |
regex: new_regex() | |
return "" | |
# render partial | |
when ">" then return that.render_partial(name, context, partials) | |
# the triple mustache in unescaped | |
when "{" then return that.find name, context | |
else return that.escape that.find(name, context) | |
that.send lines[i] unless in_recursion | |
i++ | |
return lines.join("\n") if in_recursion | |
set_delimiters: (delimiters) -> | |
dels: delimiters.split " " | |
@otag: this.escape_regex dels[0] | |
@ctag: this.escape_regex dels[1] | |
# Must think of a better way to do this, I hate to use backticks here | |
escape_regex: (text) -> | |
# thank you Simon Willison | |
if !`arguments.callee`.sRE | |
specials: [ '/', '.', '*', '+', '?', '|', | |
'(', ')', '[', ']', '{', '}', '\\' ] | |
`arguments.callee`.sRE: new RegExp '(\\' + specials.join('|\\') + ')', 'g' | |
text.replace `arguments.callee`.sRE, '\\$1' | |
# | |
# find `name` in current `context`. That is find me a value | |
# from the view object | |
# | |
find: (name, context) -> | |
name: this.trim name | |
if "function" is typeof context[name] | |
return context[name].apply context | |
if context[name] isnt undefined | |
return context[name] | |
# silently ignore unkown variables | |
"" | |
# Utility methods | |
# | |
# Does away with nasty characters | |
# | |
escape: (s) -> | |
str = if s is null then "" else s | |
str.toString().replace /[&"<>\\]/g, (s) -> | |
switch s | |
when "&" then return "&" | |
when "\\"then return "\\\\" | |
when '"' then return '\"' | |
when "<" then return "<" | |
when ">" then return ">" | |
else return s; | |
# | |
# Merges all properties of object `b` into object `a`. | |
# `b.property` overwrites a.property` | |
# | |
merge: (a, b) -> | |
_new: {} | |
for k, v of a | |
_new[k] = v if a.hasOwnProperty k | |
for k, v of b | |
_new[k] = v if b.hasOwnProperty k | |
_new | |
# by @langalex, support for arrays of strings | |
create_context: (_context) -> | |
return _context if this.is_object _context | |
if @pragmas["IMPLICIT-ITERATOR"] | |
iterator: @pragmas["IMPLICIT-ITERATOR"].iterator || "." | |
ctx: {} | |
ctx[iterator]: _context | |
return ctx | |
undefined | |
is_object: (a) -> ( a and "object" is typeof a ) | |
# | |
# Thanks Doug Crockford | |
# JavaScript — The Good Parts lists an alternative that works better with | |
# frames. Frames can suck it, we use the simple version. | |
# | |
is_array: (a) -> (a and "object" is typeof a and Array is a.constructor) | |
# | |
# Gets rid of leading and trailing whitespace | |
# | |
trim: (s) -> s.replace /^\s*|\s*$/g, '' | |
# | |
# Why, why, why? Because IE. Cry, cry cry. | |
# | |
map: (array, fn) -> | |
return array.map fn if "function" is typeof array.map | |
r: [] | |
r.push fn(x) for x in array | |
r | |
} | |
{ | |
name: "mustache.js", | |
version: "0.2.3-dev" | |
to_html: (template, view, partials, send_fun) -> | |
renderer: new Renderer() | |
renderer.send: send_fun if send_fun | |
renderer.render template, view, partials | |
return renderer.buffer.join "\n" unless send_fun | |
} | |
# Export the Mustache object for CommonJS, or Browser's DOM. | |
root.Mustache: Mustache() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment