Skip to content

Instantly share code, notes, and snippets.

@oz
Created March 10, 2010 22:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save oz/328559 to your computer and use it in GitHub Desktop.
Save oz/328559 to your computer and use it in GitHub Desktop.
##
# 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 "&amp;"
when "\\"then return "\\\\"
when '"' then return '\"'
when "<" then return "&lt;"
when ">" then return "&gt;"
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