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