1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | if window?
|
13 | coffeecup = window.coffeecup = {}
|
14 | coffee = if CoffeeScript? then CoffeeScript else null
|
15 | else
|
16 | coffeecup = exports
|
17 | coffee = require 'coffee-script'
|
18 | compiler = require __dirname + '/compiler'
|
19 | compiler.setup coffeecup
|
20 |
|
21 | coffeecup.version = '0.3.12'
|
22 |
|
23 |
|
24 |
|
25 | coffeecup.doctypes =
|
26 | 'default': '<!DOCTYPE html>'
|
27 | '5': '<!DOCTYPE html>'
|
28 | 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
|
29 | 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
30 | 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
31 | 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
32 | '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
|
33 | 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
|
34 | 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
|
35 | 'ce': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ce-html-1.0-transitional.dtd">'
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | coffeescript_helpers = """
|
43 | var __slice = Array.prototype.slice;
|
44 | var __hasProp = Object.prototype.hasOwnProperty;
|
45 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
46 | var __extends = function(child, parent) {
|
47 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
|
48 | function ctor() { this.constructor = child; }
|
49 | ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype;
|
50 | return child; };
|
51 | var __indexOf = Array.prototype.indexOf || function(item) {
|
52 | for (var i = 0, l = this.length; i < l; i++) {
|
53 | if (this[i] === item) return i;
|
54 | } return -1; };
|
55 | """.replace /\n/g, ''
|
56 |
|
57 |
|
58 |
|
59 | elements =
|
60 |
|
61 |
|
62 | regular: 'a abbr address article aside audio b bdi bdo blockquote body button
|
63 | canvas caption cite code colgroup datalist dd del details dfn div dl dt em
|
64 | fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup
|
65 | html i iframe ins kbd label legend li map mark menu meter nav noscript object
|
66 | ol optgroup option output p pre progress q rp rt ruby s samp script section
|
67 | select small span strong style sub summary sup table tbody td textarea tfoot
|
68 | th thead time title tr u ul video'
|
69 |
|
70 |
|
71 | svg: 'a altGlyph altGlyphDef altGlyphItem animate animateColor animateMotion
|
72 | animateTransform circle clipPath color-profile cursor defs desc ellipse
|
73 | feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix
|
74 | feDiffuseLighting feDisplacementMap feDistantLight feFlood feFuncA feFuncB
|
75 | feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology
|
76 | feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence
|
77 | filter font font-face font-face-format font-face-name font-face-src
|
78 | font-face-uri foreignObject g glyph glyphRef hkern image line linearGradient
|
79 | marker mask metadata missing-glyph mpath path pattern polygon polyline
|
80 | radialGradient rect script set stop style svg symbol text textPath
|
81 | title tref tspan use view vkern'
|
82 |
|
83 |
|
84 | void: 'area base br col command embed hr img input keygen link meta param
|
85 | source track wbr'
|
86 |
|
87 | obsolete: 'applet acronym bgsound dir frameset noframes isindex listing
|
88 | nextid noembed plaintext rb strike xmp big blink center font marquee multicol
|
89 | nobr spacer tt'
|
90 |
|
91 | obsolete_void: 'basefont frame'
|
92 |
|
93 |
|
94 | merge_elements = (args...) ->
|
95 | result = []
|
96 | for a in args
|
97 | for element in elements[a].split ' '
|
98 | result.push element unless element in result
|
99 | result
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | coffeecup.tags = merge_elements 'regular', 'obsolete', 'void', 'obsolete_void',
|
105 | 'svg'
|
106 |
|
107 |
|
108 | coffeecup.self_closing = merge_elements 'void', 'obsolete_void'
|
109 |
|
110 |
|
111 |
|
112 |
|
113 | skeleton = (data = {}) ->
|
114 |
|
115 |
|
116 | data.format ?= off
|
117 |
|
118 |
|
119 |
|
120 | data.autoescape ?= off
|
121 |
|
122 |
|
123 | __cc =
|
124 | buffer: []
|
125 |
|
126 | esc: (txt) ->
|
127 | if data.autoescape then h(txt) else txt.toString()
|
128 |
|
129 | tabs: 0
|
130 |
|
131 | repeat: (string, count) -> Array(count + 1).join string
|
132 |
|
133 | indent: -> text @repeat(' ', @tabs) if data.format
|
134 |
|
135 |
|
136 | tag: (name, args) ->
|
137 | combo = [name]
|
138 | combo.push i for i in args
|
139 | tag.apply data, combo
|
140 |
|
141 | render_idclass: (str) ->
|
142 | classes = []
|
143 |
|
144 | for i in str.split '.'
|
145 | if '#' in i
|
146 | id = i.replace '#', ''
|
147 | else
|
148 | classes.push i unless i is ''
|
149 |
|
150 | text " id=\"#{id}\"" if id
|
151 |
|
152 | if classes.length > 0
|
153 | text " class=\""
|
154 | for c in classes
|
155 | text ' ' unless c is classes[0]
|
156 | text c
|
157 | text '"'
|
158 |
|
159 | render_attrs: (obj, prefix = '') ->
|
160 | for k, v of obj
|
161 |
|
162 | v = k if typeof v is 'boolean' and v
|
163 |
|
164 |
|
165 | v = "(#{v}).call(this);" if typeof v is 'function'
|
166 |
|
167 |
|
168 | if typeof v is 'object' and v not instanceof Array
|
169 |
|
170 | @render_attrs(v, prefix + k + '-')
|
171 |
|
172 | else if v
|
173 |
|
174 | text " #{prefix + k}=\"#{@esc(v)}\""
|
175 |
|
176 | render_contents: (contents, safe) ->
|
177 | safe ?= false
|
178 | switch typeof contents
|
179 | when 'string', 'number', 'boolean'
|
180 | text if safe then contents else @esc(contents)
|
181 | when 'function'
|
182 | text '\n' if data.format
|
183 | @tabs++
|
184 | result = contents.call data
|
185 | if typeof result is 'string'
|
186 | @indent()
|
187 | text if safe then result else @esc(result)
|
188 | text '\n' if data.format
|
189 | @tabs--
|
190 | @indent()
|
191 |
|
192 | render_tag: (name, idclass, attrs, inline, contents) ->
|
193 | @indent()
|
194 |
|
195 | text "<#{name}"
|
196 | @render_idclass(idclass) if idclass
|
197 | @render_attrs(attrs) if attrs
|
198 |
|
199 | text " #{inline}" if inline
|
200 |
|
201 | if name in @self_closing
|
202 | text ' />'
|
203 | text '\n' if data.format
|
204 | else
|
205 | text '>'
|
206 |
|
207 | @render_contents(contents)
|
208 |
|
209 | text "</#{name}>"
|
210 | text '\n' if data.format
|
211 |
|
212 | null
|
213 |
|
214 | tag = (name, args...) ->
|
215 | for a in args
|
216 | switch typeof a
|
217 | when 'function'
|
218 | contents = a
|
219 | when 'object'
|
220 | attrs = a
|
221 | when 'number', 'boolean'
|
222 | contents = a
|
223 | when 'string'
|
224 | if args.length is 1
|
225 | contents = a
|
226 | else
|
227 | if a is args[0]
|
228 | first = a.charAt(0)
|
229 | if first == '#' || first == '.'
|
230 | idclass = a.substr(0, a.indexOf(' '))
|
231 | inline = a.substr(a.indexOf(' ') + 1)
|
232 | if idclass == ''
|
233 | idclass = inline
|
234 | inline = undefined
|
235 | else
|
236 | inline = a
|
237 | inline = undefined if inline == ''
|
238 | else
|
239 | contents = a
|
240 |
|
241 | __cc.render_tag(name, idclass, attrs, inline, contents)
|
242 |
|
243 | cede = (f) ->
|
244 | temp_buffer = []
|
245 | old_buffer = __cc.buffer
|
246 | __cc.buffer = temp_buffer
|
247 | f()
|
248 | __cc.buffer = old_buffer
|
249 | temp_buffer.join ''
|
250 |
|
251 | h = (txt) ->
|
252 | txt.toString().replace(/&/g, '&')
|
253 | .replace(/</g, '<')
|
254 | .replace(/>/g, '>')
|
255 | .replace(/"/g, '"')
|
256 |
|
257 | doctype = (type = 'default') ->
|
258 | text __cc.doctypes[type]
|
259 | text '\n' if data.format
|
260 |
|
261 | text = (txt) ->
|
262 | __cc.buffer.push txt.toString()
|
263 | null
|
264 |
|
265 | comment = (cmt) ->
|
266 | text "<!--#{cmt}-->"
|
267 | text '\n' if data.format
|
268 |
|
269 | coffeescript = (param) ->
|
270 | switch typeof param
|
271 |
|
272 |
|
273 | when 'function'
|
274 | script "#{__cc.coffeescript_helpers}(#{param}).call(this);"
|
275 |
|
276 |
|
277 | when 'string'
|
278 | script type: 'text/coffeescript', -> param
|
279 |
|
280 |
|
281 | when 'object'
|
282 | param.type = 'text/coffeescript'
|
283 | script param
|
284 |
|
285 | stylus = (s) ->
|
286 | text '<style>'
|
287 | text '\n' if data.format
|
288 | data.stylus.render s, {compress: not data.format}, (err, css) ->
|
289 | if err then throw err
|
290 | text css
|
291 | text '</style>'
|
292 | text '\n' if data.format
|
293 |
|
294 |
|
295 | ie = (condition, contents) ->
|
296 | __cc.indent()
|
297 |
|
298 | text "<!--[if #{condition}]>"
|
299 | __cc.render_contents(contents)
|
300 | text "<![endif]-->"
|
301 | text '\n' if data.format
|
302 |
|
303 | null
|
304 |
|
305 |
|
306 |
|
307 | skeleton = skeleton.toString()
|
308 | .replace(/function\s*\(.*\)\s*\{/, '')
|
309 | .replace(/return null;\s*\}$/, '')
|
310 |
|
311 | skeleton = coffeescript_helpers + skeleton
|
312 |
|
313 |
|
314 | coffeecup.compile = (template, options = {}) ->
|
315 |
|
316 |
|
317 | if typeof template is 'function' then template = template.toString()
|
318 | else if typeof template is 'string' and coffee?
|
319 | template = coffee.compile template, bare: yes
|
320 | template = "function(){#{template}}"
|
321 |
|
322 |
|
323 |
|
324 |
|
325 | hardcoded_locals = ''
|
326 |
|
327 | if options.hardcode
|
328 | for k, v of options.hardcode
|
329 | if typeof v is 'function'
|
330 |
|
331 | hardcoded_locals += "var #{k} = function(){return (#{v}).apply(data, arguments);};"
|
332 | else hardcoded_locals += "var #{k} = #{JSON.stringify v};"
|
333 |
|
334 |
|
335 |
|
336 | if options.optimize and compiler?
|
337 | return compiler.compile template, hardcoded_locals, options
|
338 |
|
339 |
|
340 |
|
341 | tag_functions = ''
|
342 | tags_used = []
|
343 |
|
344 | for t in coffeecup.tags
|
345 | if template.indexOf(t) > -1 or hardcoded_locals.indexOf(t) > -1
|
346 | tags_used.push t
|
347 |
|
348 | tag_functions += "var #{tags_used.join ','};"
|
349 | for t in tags_used
|
350 | tag_functions += "#{t} = function(){return __cc.tag('#{t}', arguments);};"
|
351 |
|
352 |
|
353 | code = tag_functions + hardcoded_locals + skeleton
|
354 |
|
355 | code += "__cc.doctypes = #{JSON.stringify coffeecup.doctypes};"
|
356 | code += "__cc.coffeescript_helpers = #{JSON.stringify coffeescript_helpers};"
|
357 | code += "__cc.self_closing = #{JSON.stringify coffeecup.self_closing};"
|
358 |
|
359 |
|
360 |
|
361 | code += 'with(data.locals){' if options.locals
|
362 | code += "(#{template}).call(data);"
|
363 | code += '}' if options.locals
|
364 | code += "return __cc.buffer.join('');"
|
365 |
|
366 | new Function('data', code)
|
367 |
|
368 | cache = {}
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 | coffeecup.render = (template, data = {}, options = {}) ->
|
380 | data[k] = v for k, v of options
|
381 | data.cache ?= off
|
382 | data.stylus = require 'stylus'
|
383 |
|
384 |
|
385 |
|
386 | if data.optimize and not data.cache then data.optimize = no
|
387 |
|
388 | if data.cache and cache[template]? then tpl = cache[template]
|
389 | else if data.cache then tpl = cache[template] = coffeecup.compile(template, data)
|
390 | else tpl = coffeecup.compile(template, data)
|
391 | tpl(data)
|
392 |
|
393 | unless window?
|
394 | coffeecup.adapters =
|
395 |
|
396 | simple: coffeecup.render
|
397 | meryl: coffeecup.render
|
398 |
|
399 | express:
|
400 | TemplateError: class extends Error
|
401 | constructor: (@message) ->
|
402 | Error.call this, @message
|
403 | Error.captureStackTrace this, arguments.callee
|
404 | name: 'TemplateError'
|
405 |
|
406 | compile: (template, data) ->
|
407 |
|
408 | data.hardcode ?= {}
|
409 | data.hardcode.partial = ->
|
410 | text @partial.apply @, arguments
|
411 |
|
412 | TemplateError = @TemplateError
|
413 | try tpl = coffeecup.compile(template, data)
|
414 | catch e then throw new TemplateError "Error compiling #{data.filename}: #{e.message}"
|
415 |
|
416 | return ->
|
417 | try tpl arguments...
|
418 | catch e then throw new TemplateError "Error rendering #{data.filename}: #{e.message}"
|