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.13'
|
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, idx in str.split '.' when i isnt ''
|
145 |
|
146 | if idx is 0 and i.indexOf('#') is 0
|
147 | id = i.slice(1)
|
148 | else
|
149 | classes.push i
|
150 |
|
151 | text " id=\"#{id}\"" if id
|
152 |
|
153 | if classes.length > 0
|
154 | text " class=\""
|
155 | for c in classes
|
156 | text ' ' unless c is classes[0]
|
157 | text c
|
158 | text '"'
|
159 |
|
160 | render_attrs: (obj, prefix = '') ->
|
161 | for k, v of obj
|
162 |
|
163 | v = k if typeof v is 'boolean' and v
|
164 |
|
165 |
|
166 | v = "(#{v}).call(this);" if typeof v is 'function'
|
167 |
|
168 |
|
169 | if typeof v is 'object' and v not instanceof Array
|
170 |
|
171 | @render_attrs(v, prefix + k + '-')
|
172 |
|
173 | else if v
|
174 |
|
175 | text " #{prefix + k}=\"#{@esc(v)}\""
|
176 |
|
177 | render_contents: (contents, safe) ->
|
178 | safe ?= false
|
179 | switch typeof contents
|
180 | when 'string', 'number', 'boolean'
|
181 | text if safe then contents else @esc(contents)
|
182 | when 'function'
|
183 | text '\n' if data.format
|
184 | @tabs++
|
185 | result = contents.call data
|
186 | if typeof result is 'string'
|
187 | @indent()
|
188 | text if safe then result else @esc(result)
|
189 | text '\n' if data.format
|
190 | @tabs--
|
191 | @indent()
|
192 |
|
193 | render_tag: (name, idclass, attrs, inline, contents) ->
|
194 | @indent()
|
195 |
|
196 | text "<#{name}"
|
197 | @render_idclass(idclass) if idclass
|
198 | @render_attrs(attrs) if attrs
|
199 |
|
200 | text " #{inline}" if inline
|
201 |
|
202 | if name in @self_closing
|
203 | text ' />'
|
204 | text '\n' if data.format
|
205 | else
|
206 | text '>'
|
207 |
|
208 | @render_contents(contents)
|
209 |
|
210 | text "</#{name}>"
|
211 | text '\n' if data.format
|
212 |
|
213 | null
|
214 |
|
215 | tag = (name, args...) ->
|
216 | for a in args
|
217 | switch typeof a
|
218 | when 'function'
|
219 | contents = a
|
220 | when 'object'
|
221 | attrs = a
|
222 | when 'number', 'boolean'
|
223 | contents = a
|
224 | when 'string'
|
225 | if args.length is 1
|
226 | contents = a
|
227 | else
|
228 | if a is args[0]
|
229 | first = a.charAt(0)
|
230 | if first == '#' || first == '.'
|
231 | idclass = a.substr(0, a.indexOf(' '))
|
232 | inline = a.substr(a.indexOf(' ') + 1)
|
233 | if idclass == ''
|
234 | idclass = inline
|
235 | inline = undefined
|
236 | else
|
237 | inline = a
|
238 | inline = undefined if inline == ''
|
239 | else
|
240 | contents = a
|
241 |
|
242 | __cc.render_tag(name, idclass, attrs, inline, contents)
|
243 |
|
244 | cede = (f) ->
|
245 | temp_buffer = []
|
246 | old_buffer = __cc.buffer
|
247 | __cc.buffer = temp_buffer
|
248 | f()
|
249 | __cc.buffer = old_buffer
|
250 | temp_buffer.join ''
|
251 |
|
252 | h = (txt) ->
|
253 | txt.toString().replace(/&/g, '&')
|
254 | .replace(/</g, '<')
|
255 | .replace(/>/g, '>')
|
256 | .replace(/"/g, '"')
|
257 |
|
258 | doctype = (type = 'default') ->
|
259 | text __cc.doctypes[type]
|
260 | text '\n' if data.format
|
261 |
|
262 | text = (txt) ->
|
263 | __cc.buffer.push txt.toString()
|
264 | null
|
265 |
|
266 | comment = (cmt) ->
|
267 | text "<!--#{cmt}-->"
|
268 | text '\n' if data.format
|
269 |
|
270 | coffeescript = (param) ->
|
271 | switch typeof param
|
272 |
|
273 |
|
274 | when 'function'
|
275 | script "#{__cc.coffeescript_helpers}(#{param}).call(this);"
|
276 |
|
277 |
|
278 | when 'string'
|
279 | script type: 'text/coffeescript', -> param
|
280 |
|
281 |
|
282 | when 'object'
|
283 | param.type = 'text/coffeescript'
|
284 | script param
|
285 |
|
286 | stylus = (s) ->
|
287 | throw new TemplateError('stylus is not available') unless data.stylus?
|
288 | text '<style>'
|
289 | text '\n' if data.format
|
290 | data.stylus.render s, {compress: not data.format}, (err, css) ->
|
291 | if err then throw err
|
292 | text css
|
293 | text '</style>'
|
294 | text '\n' if data.format
|
295 |
|
296 |
|
297 | ie = (condition, contents) ->
|
298 | __cc.indent()
|
299 |
|
300 | text "<!--[if #{condition}]>"
|
301 | __cc.render_contents(contents)
|
302 | text "<![endif]-->"
|
303 | text '\n' if data.format
|
304 |
|
305 | null
|
306 |
|
307 |
|
308 |
|
309 | skeleton = skeleton.toString()
|
310 | .replace(/function\s*\(.*\)\s*\{/, '')
|
311 | .replace(/return null;\s*\}$/, '')
|
312 |
|
313 | skeleton = coffeescript_helpers + skeleton
|
314 |
|
315 |
|
316 | coffeecup.compile = (template, options = {}) ->
|
317 |
|
318 |
|
319 | if typeof template is 'function' then template = template.toString()
|
320 | else if typeof template is 'string' and coffee?
|
321 | template = coffee.compile template, bare: yes
|
322 | template = "function(){#{template}}"
|
323 |
|
324 |
|
325 |
|
326 |
|
327 | hardcoded_locals = ''
|
328 |
|
329 | if options.hardcode
|
330 | for k, v of options.hardcode
|
331 | if typeof v is 'function'
|
332 |
|
333 | hardcoded_locals += "var #{k} = function(){return (#{v}).apply(data, arguments);};"
|
334 | else hardcoded_locals += "var #{k} = #{JSON.stringify v};"
|
335 |
|
336 |
|
337 |
|
338 | if options.optimize and compiler?
|
339 | return compiler.compile template, hardcoded_locals, options
|
340 |
|
341 |
|
342 |
|
343 | tag_functions = ''
|
344 | tags_used = []
|
345 |
|
346 | for t in coffeecup.tags
|
347 | if template.indexOf(t) > -1 or hardcoded_locals.indexOf(t) > -1
|
348 | tags_used.push t
|
349 |
|
350 | tag_functions += "var #{tags_used.join ','};"
|
351 | for t in tags_used
|
352 | tag_functions += "#{t} = function(){return __cc.tag('#{t}', arguments);};"
|
353 |
|
354 |
|
355 | code = tag_functions + hardcoded_locals + skeleton
|
356 |
|
357 | code += "__cc.doctypes = #{JSON.stringify coffeecup.doctypes};"
|
358 | code += "__cc.coffeescript_helpers = #{JSON.stringify coffeescript_helpers};"
|
359 | code += "__cc.self_closing = #{JSON.stringify coffeecup.self_closing};"
|
360 |
|
361 |
|
362 |
|
363 | code += 'with(data.locals){' if options.locals
|
364 | code += "(#{template}).call(data);"
|
365 | code += '}' if options.locals
|
366 | code += "return __cc.buffer.join('');"
|
367 |
|
368 | new Function('data', code)
|
369 |
|
370 | cache = {}
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 | coffeecup.render = (template, data = {}, options = {}) ->
|
382 | data[k] = v for k, v of options
|
383 | data.cache ?= off
|
384 |
|
385 | if not window?
|
386 | data.stylus = require 'stylus'
|
387 |
|
388 |
|
389 |
|
390 | if data.optimize and not data.cache then data.optimize = no
|
391 |
|
392 | if data.cache and cache[template]? then tpl = cache[template]
|
393 | else if data.cache then tpl = cache[template] = coffeecup.compile(template, data)
|
394 | else tpl = coffeecup.compile(template, data)
|
395 | tpl(data)
|
396 |
|
397 | unless window?
|
398 | coffeecup.adapters =
|
399 |
|
400 | simple: coffeecup.render
|
401 | meryl: coffeecup.render
|
402 |
|
403 | express:
|
404 | TemplateError: class extends Error
|
405 | constructor: (@message) ->
|
406 | Error.call this, @message
|
407 | Error.captureStackTrace this, arguments.callee
|
408 | name: 'TemplateError'
|
409 |
|
410 | compile: (template, data) ->
|
411 |
|
412 | data.hardcode ?= {}
|
413 | data.hardcode.partial = ->
|
414 | text @partial.apply @, arguments
|
415 |
|
416 | TemplateError = @TemplateError
|
417 | try tpl = coffeecup.compile(template, data)
|
418 | catch e then throw new TemplateError "Error compiling #{data.filename}: #{e.message}"
|
419 |
|
420 | return ->
|
421 | try tpl arguments...
|
422 | catch e then throw new TemplateError "Error rendering #{data.filename}: #{e.message}"
|