├── lib └── .gitignore ├── .npmignore ├── examples ├── express │ ├── views │ │ ├── partial.coffee │ │ ├── index.coffee │ │ ├── login.coffee │ │ └── layout.coffee │ └── app.coffee ├── browser │ ├── creme │ │ ├── coffeekup.js │ │ └── index.html │ ├── decaf │ │ ├── build │ │ ├── template.coffee │ │ ├── index.html │ │ └── template.js │ └── regular │ │ ├── template.coffee │ │ ├── build │ │ ├── index.coffee │ │ ├── index.js │ │ ├── index.html │ │ └── template.js ├── meryl │ ├── templates │ │ ├── index.coffee │ │ └── layout.coffee │ └── app.coffee └── zappa.coffee ├── .gitignore ├── bin └── coffeekup ├── Cakefile ├── LICENSE ├── package.json ├── src ├── cli.coffee └── coffeekup.coffee ├── CHANGELOG.md ├── benchmark.coffee ├── docs ├── docco.css ├── reference.md └── coffeekup.html ├── test.coffee └── README.md /lib/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | material/ 3 | -------------------------------------------------------------------------------- /examples/express/views/partial.coffee: -------------------------------------------------------------------------------- 1 | p 'Express partial' -------------------------------------------------------------------------------- /examples/browser/creme/coffeekup.js: -------------------------------------------------------------------------------- 1 | ../../../lib/coffeekup.js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | material/ 3 | node_modules/ 4 | lib/*.js -------------------------------------------------------------------------------- /examples/browser/decaf/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | coffeekup --js template.coffee 3 | 4 | -------------------------------------------------------------------------------- /examples/browser/decaf/template.coffee: -------------------------------------------------------------------------------- 1 | ul -> 2 | for guy in @stooges 3 | li guy 4 | -------------------------------------------------------------------------------- /examples/browser/regular/template.coffee: -------------------------------------------------------------------------------- 1 | ul -> 2 | for guy in @stooges 3 | li guy 4 | -------------------------------------------------------------------------------- /examples/meryl/templates/index.coffee: -------------------------------------------------------------------------------- 1 | h1 'Meryl example' 2 | ul -> 3 | for name in @people 4 | li name 5 | -------------------------------------------------------------------------------- /examples/browser/regular/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | coffeekup --js template.coffee 3 | coffee -c index.coffee 4 | 5 | -------------------------------------------------------------------------------- /examples/browser/regular/index.coffee: -------------------------------------------------------------------------------- 1 | $().ready -> 2 | $('body').append templates.template(stooges: ['moe', 'larry', 'curly']) 3 | -------------------------------------------------------------------------------- /examples/meryl/templates/layout.coffee: -------------------------------------------------------------------------------- 1 | doctype 5 2 | html -> 3 | head -> 4 | title 'Meryl example' 5 | body -> 6 | @render @content, @context 7 | -------------------------------------------------------------------------------- /bin/coffeekup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path') 4 | var fs = require('fs') 5 | var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib') 6 | 7 | require(lib + '/cli').run() 8 | -------------------------------------------------------------------------------- /examples/browser/regular/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | $().ready(function() { 3 | return $('body').append(templates.template({ 4 | stooges: ['moe', 'larry', 'curly'] 5 | })); 6 | }); 7 | }).call(this); 8 | -------------------------------------------------------------------------------- /examples/express/views/index.coffee: -------------------------------------------------------------------------------- 1 | @title = 'Chunky Bacon!' 2 | @canonical = 'http://chunky.bacon' 3 | 4 | h1 @title 5 | 6 | p 'This is the home page.' 7 | 8 | p "Let's count to 10: " 9 | 10 | p "#{i}..." for i in [1..10] 11 | 12 | partial 'partial', [1..10] -------------------------------------------------------------------------------- /examples/zappa.coffee: -------------------------------------------------------------------------------- 1 | require('zappa') -> 2 | enable 'default layout' 3 | 4 | get '/': -> 5 | @franks = ['miller', 'oz', 'sinatra', 'zappa'] 6 | render 'index' 7 | 8 | view index: -> 9 | @title = 'Zappa example' 10 | h1 @title 11 | ul -> 12 | for f in @franks 13 | li f 14 | -------------------------------------------------------------------------------- /examples/meryl/app.coffee: -------------------------------------------------------------------------------- 1 | meryl = require 'meryl' 2 | coffeekup = require '../../src/coffeekup' 3 | 4 | meryl.h 'GET /', (req, resp) -> 5 | people = ['bob', 'alice', 'meryl'] 6 | resp.render 'layout', content: 'index', context: {people: people} 7 | 8 | meryl.run 9 | templateDir: 'templates' 10 | templateExt: '.coffee' 11 | templateFunc: coffeekup.adapters.meryl 12 | 13 | console.log 'Listening on 3000...' 14 | -------------------------------------------------------------------------------- /examples/express/views/login.coffee: -------------------------------------------------------------------------------- 1 | @title = 'Log In' 2 | 3 | h1 @title 4 | 5 | p "A local var: #{ping}" 6 | p "A context var: #{@foo}" 7 | 8 | form action: '/', method: 'post', -> 9 | div class: 'field', -> 10 | label for: 'username', -> 'Username: ' 11 | input id: 'username', name: 'username' 12 | 13 | div class: 'field', -> 14 | label for: 'password', -> 'Password: ' 15 | input id: 'password', name: 'password' 16 | -------------------------------------------------------------------------------- /examples/express/app.coffee: -------------------------------------------------------------------------------- 1 | app = require('express').createServer() 2 | 3 | coffeekup = require '../../src/coffeekup' 4 | 5 | app.set 'view engine', 'coffee' 6 | app.register '.coffee', coffeekup.adapters.express 7 | 8 | app.get '/', (req, res) -> 9 | res.render 'index' 10 | 11 | app.get '/login', (req, res) -> 12 | res.render 'login', foo: 'bar', locals: {ping: 'pong'} 13 | 14 | app.get '/inline', (req, res) -> 15 | res.send coffeekup.render -> 16 | h1 'This is an inline template.' 17 | 18 | app.listen 3000 19 | 20 | console.log "Listening on 3000..." 21 | -------------------------------------------------------------------------------- /examples/browser/regular/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Client-side CoffeeKup (regular) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Client-side CoffeeKup (regular)

15 |

Writing your app and templates in CoffeeScript but pre-compiling everything on the server-side.

16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/browser/decaf/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Client-side CoffeeKup (decaf) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 |

Client-side CoffeeKup (decaf)

20 |

Consuming a template from a non-CoffeeScript app.

21 | 22 | 23 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | {spawn, exec} = require 'child_process' 2 | log = console.log 3 | 4 | task 'build', -> 5 | run 'coffee -o lib -c src/*.coffee' 6 | 7 | task 'test', -> require('./test').run() 8 | 9 | task 'bench', -> require('./benchmark').run() 10 | 11 | task 'docs', -> 12 | run 'docco src/coffeekup.coffee' 13 | 14 | run = (args...) -> 15 | for a in args 16 | switch typeof a 17 | when 'string' then command = a 18 | when 'object' 19 | if a instanceof Array then params = a 20 | else options = a 21 | when 'function' then callback = a 22 | 23 | command += ' ' + params.join ' ' if params? 24 | cmd = spawn '/bin/sh', ['-c', command], options 25 | cmd.stdout.on 'data', (data) -> process.stdout.write data 26 | cmd.stderr.on 'data', (data) -> process.stderr.write data 27 | process.on 'SIGHUP', -> cmd.kill() 28 | cmd.on 'exit', (code) -> callback() if callback? and code is 0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Maurice Machado 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coffeekup", 3 | "description": "Markup as CoffeeScript.", 4 | "version": "0.3.1edge", 5 | "author": "Maurice Machado ", 6 | "homepage": "http://coffeekup.org", 7 | "repository": {"type": "git", "url": "git://github.com/mauricemach/coffeekup.git"}, 8 | "dependencies": {"coffee-script": ">= 1.1.2"}, 9 | "devDependencies": {"jade": "0.13.0", "eco": "1.1.0-rc-1", "ejs": "0.4.3", "haml": "0.4.2"}, 10 | "keywords": ["template", "view", "coffeescript"], 11 | "bin": "./bin/coffeekup", 12 | "main": "./lib/coffeekup", 13 | "engines": {"node": ">= 0.4.7"}, 14 | "contributors": [ 15 | "Luis Pedro Coelho ", 16 | "Rachel Carvalho ", 17 | "Vladimir Dronnikov ", 18 | "James Campos ", 19 | "Martin Westergaard Lassen ", 20 | "Paul Harper ", 21 | "Colin Thomas-Arnold ", 22 | "Esa-Matti Suuronen ", 23 | "Jason King ", 24 | "Brandon Bloom ", 25 | "Nicholas Kinsey " 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /examples/browser/creme/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Client-side CoffeeKup (Crème) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 35 | 36 | 37 |

Client-side CoffeeKup (crème)

38 |

Doing everything on the client. Lots of flexibility but a fatty download.

39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/express/views/layout.coffee: -------------------------------------------------------------------------------- 1 | doctype 5 2 | html -> 3 | head -> 4 | meta charset: 'utf-8' 5 | 6 | title "#{@title} | My Site" if @title? 7 | meta(name: 'description', content: @description) if @description? 8 | link(rel: 'canonical', href: @canonical) if @canonical? 9 | 10 | link rel: 'icon', href: '/favicon.png' 11 | link rel: 'stylesheet', href: '/app.css' 12 | 13 | script src: 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js' 14 | script src: '/app.js' 15 | 16 | coffeescript -> 17 | $(document).ready -> 18 | alert 'hi!' 19 | 20 | style ''' 21 | header, nav, section, article, aside, footer {display: block} 22 | nav li {display: inline} 23 | nav.sub {float: right} 24 | #content {margin-left: 120px} 25 | ''' 26 | body -> 27 | header -> 28 | a href: '/', title: 'Home', -> 'Home' 29 | 30 | nav -> 31 | ul -> 32 | for item in ['About', 'Pricing', 'Contact'] 33 | li -> a href: "/#{item.toLowerCase()}", title: item, -> item 34 | 35 | li -> a href: '/about', title: 'About', -> 'About' 36 | li -> a href: '/pricing', title: 'Pricing', -> 'Pricing' 37 | li -> a href: '/contact', title: 'Contact Us', -> 'Contact Us' 38 | 39 | div id: 'content', -> 40 | @body 41 | 42 | footer -> 43 | p -> a href: '/privacy', -> 'Privacy Policy' 44 | -------------------------------------------------------------------------------- /src/cli.coffee: -------------------------------------------------------------------------------- 1 | coffeekup = require './coffeekup' 2 | fs = require 'fs' 3 | path = require 'path' 4 | puts = console.log 5 | {OptionParser} = require 'coffee-script/lib/optparse' 6 | 7 | argv = process.argv[2..] 8 | options = null 9 | 10 | handle_error = (err) -> console.log err.stack if err 11 | 12 | compile = (input_path, output_directory, js, namespace = 'templates') -> 13 | fs.readFile input_path, 'utf-8', (err, contents) -> 14 | handle_error err 15 | 16 | name = path.basename input_path, path.extname(input_path) 17 | 18 | if not js 19 | output = coffeekup.render contents, options 20 | ext = '.html' 21 | else 22 | func = coffeekup.compile contents, options 23 | output = """ 24 | (function(){ 25 | this.#{namespace} || (this.#{namespace} = {}); 26 | this.#{namespace}[#{JSON.stringify name}] = #{func}; 27 | }).call(this); 28 | """ 29 | ext = '.js' 30 | 31 | write input_path, name, output, output_directory, ext 32 | 33 | write = (input_path, name, contents, output_directory, ext) -> 34 | filename = name + ext 35 | dir = output_directory or path.dirname input_path 36 | path.exists dir, (exists) -> 37 | unless exists then fs.mkdirSync dir, 0777 38 | 39 | output_path = path.join dir, filename 40 | contents = ' ' if contents.length <= 0 41 | fs.writeFile output_path, contents, (err) -> 42 | handle_error err 43 | puts contents if options.print 44 | puts "Compiled #{input_path}" if options.watch 45 | 46 | usage = ''' 47 | Usage: 48 | coffeekup [options] path/to/template.coffee 49 | ''' 50 | 51 | switches = [ 52 | ['--js', 'compile template to js function'] 53 | ['-n', '--namespace [name]', 'global object holding the templates (default: "templates")'] 54 | ['-w', '--watch', 'watch templates for changes, and recompile'] 55 | ['-o', '--output [dir]', 'set the directory for compiled html'] 56 | ['-p', '--print', 'print the compiled html to stdout'] 57 | ['-f', '--format', 'apply line breaks and indentation to html output'] 58 | ['-u', '--utils', 'add helper locals (currently only "render")'] 59 | ['-v', '--version', 'display CoffeeKup version'] 60 | ['-h', '--help', 'display this help message'] 61 | ] 62 | 63 | @run = -> 64 | parser = new OptionParser switches, usage 65 | options = parser.parse argv 66 | args = options.arguments 67 | delete options.arguments 68 | 69 | puts parser.help() if options.help or argv.length is 0 70 | puts coffeekup.version if options.version 71 | if options.utils 72 | options.locals ?= {} 73 | options.locals.render = (file) -> 74 | contents = fs.readFileSync file, 'utf-8' 75 | coffeekup.render contents, options 76 | 77 | if args.length > 0 78 | file = args[0] 79 | 80 | if options.watch 81 | fs.watchFile file, {persistent: true, interval: 500}, (curr, prev) -> 82 | return if curr.size is prev.size and curr.mtime.getTime() is prev.mtime.getTime() 83 | compile file, options.output, options.js, options.namespace 84 | 85 | compile file, options.output, options.js, options.namespace 86 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | **v0.3.1edge** (unreleased): 2 | 3 | **v0.3.1** (2011-09-29): 4 | 5 | - Fixed #71: indexOf not supported in IE7 (thanks @jaekwon). 6 | - Added better error reporting to express adapter. [benekastah] 7 | - Added `yield`: see `/docs/reference.md`. Closes #16 (thanks @pyrotechnick, @colinta and @smathy). 8 | - Added prefixed attributes: `data: {icon: 'foo.png'}` renders as `data-icon="foo.png"`. [colinta] 9 | - Added functions as attribute values: `onclick: -> alert 'hi'`. Closes #79 (thanks @smathy). 10 | 11 | **v0.3.0** (2011-09-04): 12 | 13 | - Fixed #64 (`hardcode` option masked in express adapter). [smathy] 14 | - Added missing elements from the HTML 5 spec (both valid and obsolete). Closes #66 (thanks @aeosynth). 15 | - Added compile to js option to CLI. Closes #58. [rachel-carvalho] 16 | - Fixed #69 (`coffeekup -w` only compiles upon first change). [rachel-carvalho] 17 | 18 | **v0.3.0beta** (2011-07-27): 19 | 20 | - Added documentation: API reference at `/docs/reference.md` and annotated source at `/docs/coffeekup.html`. 21 | 22 | - Added id/class shortcuts: `div '#id.class.class2', 'contents'` (thanks @aeosynth and @payload). 23 | 24 | - Added IE conditional comments: `ie 'lt IE8', -> 'IE 7 or less stuff'` (thanks @aeosynth). 25 | 26 | - Added `ck.adapters.express` which allows `partial 'foo'` instead of `text @partial 'foo'` - see `/examples/express` (thanks @cushman). 27 | 28 | - Added `coffeescript src: 'file.coffee'` and `coffeescript 'string'` - see reference (thanks @henrikh). 29 | 30 | - Changed the template param format to align with Express and other templating engines. Now `tpl(foo: 'bar')` makes `foo` accessible as `h1 @foo`. `context` is not used anymore. 31 | 32 | - `tpl(locals: {foo: 'bar'})` now always implemented through the `with` keyword (past behavior with `dynamic_locals: true`). 33 | 34 | - `tpl(hardcode: {foo: 'bar'})` will hardcode `foo` into the compiled template's body (past behavior with `dynamic_locals: false`). 35 | 36 | - Fixed: `coffeescript -> code()` now correctly adds CoffeeScript helpers to the output. 37 | 38 | - Changed: using `.call(this);` instead of `();` in js generated by `coffeescript`. 39 | 40 | - Fixed: correctly handle numbers and booleans when used as tag contents or attribute values. 41 | 42 | - Fixed #50: "`coffeekup -w` quits on syntax error". 43 | 44 | - Added: doctypes now editable at `coffeekup.doctypes`, tags at `coffeekup.tags`, and self-closing tags at `coffeekup.self_closing`. 45 | 46 | - Added the `ce` doctype. 47 | 48 | - Changed: using `doctypes['default']` instead of `doctypes['5']` by default. 49 | 50 | - Changed: in `coffeekup.render`, option `cache` is now `false` by default. 51 | 52 | - Added a third optional param to `render`. You can pass an object with options and they will be merged with the main object param. 53 | 54 | - Removed ck_* locals, now all implementation inside the `__ck` object. 55 | 56 | **v0.2.3** (2011-05-06): 57 | 58 | - Compatible with npm 1.x. 59 | - Converting any ampersands (instead of /&(?!\w+;/) to & when escaping html. 60 | - New CLI option -o / --output [DIR] (specifies a directory to compile into). 61 | - Self-closing tags are now: 'area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'link', 'meta' and 'param'. 62 | 63 | **v0.2.2** (2011-01-05): 64 | 65 | - Updated to CoffeeScript 1.0.0 and node 0.2.6/0.3.3. 66 | 67 | **v0.2.1** (2010-11-23): 68 | 69 | - Updated to CoffeeScript 0.9.5 and node 0.2.5/0.3.1. 70 | - Fixed string templates compilation in opera. 71 | 72 | **v0.2.0** (2010-11-09): 73 | 74 | - Huge performance gains, now among the fastest. See `cake benchmark`. 75 | - Compile templates into standalone functions with `coffeekup.compile`. 76 | - Option `format` to add line breaks and indentation to output. 77 | - Escape HTML automatically with the `autoescape` option, or manually with the `h` local. 78 | - CLI behaviour closer to CoffeeScript's: compiles to `filename.html` by default, can watch and recompile with `-w`. 79 | - CLI `-u`/`--utils` option to make build-time utility locals available to templates (currently only `render`). 80 | 81 | -------------------------------------------------------------------------------- /benchmark.coffee: -------------------------------------------------------------------------------- 1 | coffeekup = require './src/coffeekup' 2 | jade = require 'jade' 3 | ejs = require 'ejs' 4 | eco = require 'eco' 5 | haml = require 'haml' 6 | log = console.log 7 | 8 | data = 9 | title: 'test' 10 | inspired: no 11 | users: [ 12 | {email: 'house@gmail.com', name: 'house'} 13 | {email: 'cuddy@gmail.com', name: 'cuddy'} 14 | {email: 'wilson@gmail.com', name: 'wilson'} 15 | ] 16 | 17 | coffeekup_template = -> 18 | doctype 5 19 | html lang: 'en', -> 20 | head -> 21 | meta charset: 'utf-8' 22 | title @title 23 | style ''' 24 | body {font-family: "sans-serif"} 25 | section, header {display: block} 26 | ''' 27 | body -> 28 | section -> 29 | header -> 30 | h1 @title 31 | if @inspired 32 | p 'Create a witty example' 33 | else 34 | p 'Go meta' 35 | ul -> 36 | for user in @users 37 | li user.name 38 | li -> a href: "mailto:#{user.email}", -> user.email 39 | 40 | coffeekup_string_template = """ 41 | doctype 5 42 | html lang: 'en', -> 43 | head -> 44 | meta charset: 'utf-8' 45 | title @title 46 | style ''' 47 | body {font-family: "sans-serif"} 48 | section, header {display: block} 49 | ''' 50 | body -> 51 | section -> 52 | header -> 53 | h1 @title 54 | if @inspired 55 | p 'Create a witty example' 56 | else 57 | p 'Go meta' 58 | ul -> 59 | for user in @users 60 | li user.name 61 | li -> a href: "mailto:\#{user.email}", -> user.email 62 | """ 63 | 64 | coffeekup_compiled_template = coffeekup.compile coffeekup_template 65 | 66 | jade_template = ''' 67 | !!! 5 68 | html(lang="en") 69 | head 70 | meta(charset="utf-8") 71 | title= title 72 | style 73 | | body {font-family: "sans-serif"} 74 | | section, header {display: block} 75 | body 76 | section 77 | header 78 | h1= title 79 | - if (inspired) 80 | p Create a witty example 81 | - else 82 | p Go meta 83 | ul 84 | - each user in users 85 | li= user.name 86 | li 87 | a(href="mailto:"+user.email)= user.email 88 | ''' 89 | 90 | jade_compiled_template = jade.compile jade_template 91 | 92 | ejs_template = ''' 93 | 94 | 95 | 96 | 97 | <%= title %> 98 | 102 | 103 | 104 |
105 |
106 |

<%= title %>

107 |
108 | <% if (inspired) { %> 109 |

Create a witty example

110 | <% } else { %> 111 |

Go meta

112 | <% } %> 113 |
    114 | <% for (user in users) { %> 115 |
  • <%= user.name %>
  • 116 |
  • <%= user.email %>
  • 117 | <% } %> 118 |
119 |
120 | 121 | 122 | ''' 123 | 124 | eco_template = ''' 125 | 126 | 127 | 128 | 129 | <%= @title %> 130 | 134 | 135 | 136 |
137 |
138 |

<%= @title %>

139 |
140 | <% if @inspired: %> 141 |

Create a witty example

142 | <% else: %> 143 |

Go meta

144 | <% end %> 145 |
    146 | <% for user in @users: %> 147 |
  • <%= user.name %>
  • 148 |
  • <%= user.email %>
  • 149 | <% end %> 150 |
151 |
152 | 153 | 154 | ''' 155 | 156 | haml_template = ''' 157 | !!! 5 158 | %html{lang: "en"} 159 | %head 160 | %meta{charset: "utf-8"} 161 | %title= title 162 | :css 163 | body {font-family: "sans-serif"} 164 | section, header {display: block} 165 | %body 166 | %section 167 | %header 168 | %h1= title 169 | :if inspired 170 | %p Create a witty example 171 | :if !inspired 172 | %p Go meta 173 | %ul 174 | :each user in users 175 | %li= user.name 176 | %li 177 | %a{href: "mailto:#{user.email}"}= user.email 178 | ''' 179 | 180 | haml_template_compiled = haml(haml_template) 181 | 182 | benchmark = (title, code) -> 183 | start = new Date 184 | for i in [1..5000] 185 | code() 186 | log "#{title}: #{new Date - start} ms" 187 | 188 | @run = -> 189 | benchmark 'CoffeeKup (precompiled)', -> coffeekup_compiled_template data 190 | benchmark 'Jade (precompiled)', -> jade_compiled_template data 191 | benchmark 'haml-js (precompiled)', -> haml_template_compiled data 192 | benchmark 'Eco', -> eco.render eco_template, data 193 | 194 | console.log '\n' 195 | 196 | benchmark 'CoffeeKup (function, cache on)', -> coffeekup.render coffeekup_template, data, cache: on 197 | benchmark 'CoffeeKup (string, cache on)', -> coffeekup.render coffeekup_string_template, data, cache: on 198 | benchmark 'Jade (cache on)', -> jade.render jade_template, locals: data, cache: on, filename: 'test' 199 | benchmark 'ejs (cache on)', -> ejs.render ejs_template, locals: data, cache: on, filename: 'test' 200 | 201 | console.log '\n' 202 | 203 | benchmark 'CoffeeKup (function, cache off)', -> coffeekup.render coffeekup_template, data 204 | benchmark 'CoffeeKup (string, cache off)', -> coffeekup.render coffeekup_string_template, data, cache: off 205 | benchmark 'Jade (cache off)', -> jade.render jade_template, locals: data 206 | benchmark 'haml-js', -> haml.render haml_template, locals: data 207 | benchmark 'ejs (cache off)', -> ejs.render ejs_template, locals: data 208 | -------------------------------------------------------------------------------- /docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Layout and Typography ----------------------------*/ 2 | body { 3 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 4 | font-size: 15px; 5 | line-height: 22px; 6 | color: #252519; 7 | margin: 0; padding: 0; 8 | } 9 | a { 10 | color: #261a3b; 11 | } 12 | a:visited { 13 | color: #261a3b; 14 | } 15 | p { 16 | margin: 0 0 15px 0; 17 | } 18 | h1, h2, h3, h4, h5, h6 { 19 | margin: 0px 0 15px 0; 20 | } 21 | h1 { 22 | margin-top: 40px; 23 | } 24 | #container { 25 | position: relative; 26 | } 27 | #background { 28 | position: fixed; 29 | top: 0; left: 525px; right: 0; bottom: 0; 30 | background: #f5f5ff; 31 | border-left: 1px solid #e5e5ee; 32 | z-index: -1; 33 | } 34 | #jump_to, #jump_page { 35 | background: white; 36 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 37 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 38 | font: 10px Arial; 39 | text-transform: uppercase; 40 | cursor: pointer; 41 | text-align: right; 42 | } 43 | #jump_to, #jump_wrapper { 44 | position: fixed; 45 | right: 0; top: 0; 46 | padding: 5px 10px; 47 | } 48 | #jump_wrapper { 49 | padding: 0; 50 | display: none; 51 | } 52 | #jump_to:hover #jump_wrapper { 53 | display: block; 54 | } 55 | #jump_page { 56 | padding: 5px 0 3px; 57 | margin: 0 0 25px 25px; 58 | } 59 | #jump_page .source { 60 | display: block; 61 | padding: 5px 10px; 62 | text-decoration: none; 63 | border-top: 1px solid #eee; 64 | } 65 | #jump_page .source:hover { 66 | background: #f5f5ff; 67 | } 68 | #jump_page .source:first-child { 69 | } 70 | table td { 71 | border: 0; 72 | outline: 0; 73 | } 74 | td.docs, th.docs { 75 | max-width: 450px; 76 | min-width: 450px; 77 | min-height: 5px; 78 | padding: 10px 25px 1px 50px; 79 | overflow-x: hidden; 80 | vertical-align: top; 81 | text-align: left; 82 | } 83 | .docs pre { 84 | margin: 15px 0 15px; 85 | padding-left: 15px; 86 | } 87 | .docs p tt, .docs p code { 88 | background: #f8f8ff; 89 | border: 1px solid #dedede; 90 | font-size: 12px; 91 | padding: 0 0.2em; 92 | } 93 | .pilwrap { 94 | position: relative; 95 | } 96 | .pilcrow { 97 | font: 12px Arial; 98 | text-decoration: none; 99 | color: #454545; 100 | position: absolute; 101 | top: 3px; left: -20px; 102 | padding: 1px 2px; 103 | opacity: 0; 104 | -webkit-transition: opacity 0.2s linear; 105 | } 106 | td.docs:hover .pilcrow { 107 | opacity: 1; 108 | } 109 | td.code, th.code { 110 | padding: 14px 15px 16px 25px; 111 | width: 100%; 112 | vertical-align: top; 113 | background: #f5f5ff; 114 | border-left: 1px solid #e5e5ee; 115 | } 116 | pre, tt, code { 117 | font-size: 12px; line-height: 18px; 118 | font-family: Monaco, Consolas, "Lucida Console", monospace; 119 | margin: 0; padding: 0; 120 | } 121 | 122 | 123 | /*---------------------- Syntax Highlighting -----------------------------*/ 124 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 125 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 126 | body .hll { background-color: #ffffcc } 127 | body .c { color: #408080; font-style: italic } /* Comment */ 128 | body .err { border: 1px solid #FF0000 } /* Error */ 129 | body .k { color: #954121 } /* Keyword */ 130 | body .o { color: #666666 } /* Operator */ 131 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 132 | body .cp { color: #BC7A00 } /* Comment.Preproc */ 133 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */ 134 | body .cs { color: #408080; font-style: italic } /* Comment.Special */ 135 | body .gd { color: #A00000 } /* Generic.Deleted */ 136 | body .ge { font-style: italic } /* Generic.Emph */ 137 | body .gr { color: #FF0000 } /* Generic.Error */ 138 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 139 | body .gi { color: #00A000 } /* Generic.Inserted */ 140 | body .go { color: #808080 } /* Generic.Output */ 141 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 142 | body .gs { font-weight: bold } /* Generic.Strong */ 143 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 144 | body .gt { color: #0040D0 } /* Generic.Traceback */ 145 | body .kc { color: #954121 } /* Keyword.Constant */ 146 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ 147 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ 148 | body .kp { color: #954121 } /* Keyword.Pseudo */ 149 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ 150 | body .kt { color: #B00040 } /* Keyword.Type */ 151 | body .m { color: #666666 } /* Literal.Number */ 152 | body .s { color: #219161 } /* Literal.String */ 153 | body .na { color: #7D9029 } /* Name.Attribute */ 154 | body .nb { color: #954121 } /* Name.Builtin */ 155 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 156 | body .no { color: #880000 } /* Name.Constant */ 157 | body .nd { color: #AA22FF } /* Name.Decorator */ 158 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */ 159 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 160 | body .nf { color: #0000FF } /* Name.Function */ 161 | body .nl { color: #A0A000 } /* Name.Label */ 162 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 163 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */ 164 | body .nv { color: #19469D } /* Name.Variable */ 165 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 166 | body .w { color: #bbbbbb } /* Text.Whitespace */ 167 | body .mf { color: #666666 } /* Literal.Number.Float */ 168 | body .mh { color: #666666 } /* Literal.Number.Hex */ 169 | body .mi { color: #666666 } /* Literal.Number.Integer */ 170 | body .mo { color: #666666 } /* Literal.Number.Oct */ 171 | body .sb { color: #219161 } /* Literal.String.Backtick */ 172 | body .sc { color: #219161 } /* Literal.String.Char */ 173 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ 174 | body .s2 { color: #219161 } /* Literal.String.Double */ 175 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 176 | body .sh { color: #219161 } /* Literal.String.Heredoc */ 177 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 178 | body .sx { color: #954121 } /* Literal.String.Other */ 179 | body .sr { color: #BB6688 } /* Literal.String.Regex */ 180 | body .s1 { color: #219161 } /* Literal.String.Single */ 181 | body .ss { color: #19469D } /* Literal.String.Symbol */ 182 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */ 183 | body .vc { color: #19469D } /* Name.Variable.Class */ 184 | body .vg { color: #19469D } /* Name.Variable.Global */ 185 | body .vi { color: #19469D } /* Name.Variable.Instance */ 186 | body .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /test.coffee: -------------------------------------------------------------------------------- 1 | tests = 2 | 'Literal text': 3 | template: "text 'Just text'" 4 | expected: 'Just text' 5 | 6 | 'Default DOCTYPE': 7 | template: "doctype()" 8 | expected: '' 9 | 10 | 'DOCTYPE': 11 | template: "doctype 'xml'" 12 | expected: '' 13 | 14 | 'Custom tag': 15 | template: "tag 'custom'" 16 | expected: '' 17 | 18 | 'Custom tag with attributes': 19 | template: "tag 'custom', foo: 'bar', ping: 'pong'" 20 | expected: '' 21 | 22 | 'Custom tag with attributes and inner content': 23 | template: "tag 'custom', foo: 'bar', ping: 'pong', -> 'zag'" 24 | expected: 'zag' 25 | 26 | 'Self-closing tags': 27 | template: "img src: 'icon.png', alt: 'Icon'" 28 | expected: 'Icon' 29 | 30 | 'Common tag': 31 | template: "p 'hi'" 32 | expected: '

hi

' 33 | 34 | 'Attributes': 35 | template: "a href: '/', title: 'Home'" 36 | expected: '' 37 | 38 | 'HereDocs': 39 | template: ''' 40 | script """ 41 | $(document).ready(function(){ 42 | alert('test'); 43 | }); 44 | """ 45 | ''' 46 | expected: "" 47 | 48 | 'CoffeeScript helper (function)': 49 | template: "coffeescript -> alert 'hi'" 50 | expected: "" 51 | 52 | 'CoffeeScript helper (string)': 53 | template: "coffeescript \"alert 'hi'\"" 54 | expected: "" 55 | 56 | 'CoffeeScript helper (object)': 57 | template: "coffeescript src: 'script.coffee'" 58 | expected: "" 59 | 60 | 'Context vars': 61 | template: "h1 @foo" 62 | expected: '

bar

' 63 | params: {foo: 'bar'} 64 | 65 | 'Local vars, hardcoded': 66 | template: 'h1 "harcoded: " + obj.foo' 67 | run: -> 68 | obj = {foo: 'bar'} 69 | @compiled = ck.compile(@template, hardcode: {obj}) 70 | @expected = '

harcoded: bar

' 71 | @result = @compiled() 72 | @success = @result is @expected 73 | if @success 74 | obj.foo = 'baz' 75 | @result = @compiled() 76 | @success = @result is @expected 77 | 78 | 'Local vars, hard-coded (functions)': 79 | template: "h1 \"The sum is: \#{sum 1, 2}\"" 80 | expected: '

The sum is: 3

' 81 | params: {hardcode: {sum: (a, b) -> a + b}} 82 | 83 | 'Local vars, hard-coded ("helpers")': 84 | template: "textbox id: 'foo'" 85 | expected: '' 86 | params: 87 | hardcode: 88 | textbox: (attrs) -> 89 | attrs.name = attrs.id 90 | attrs.type = 'text' 91 | tag 'input', attrs 92 | 93 | 'Local vars': 94 | template: 'h1 "dynamic: " + obj.foo' 95 | run: -> 96 | obj = {foo: 'bar'} 97 | @expected = '

dynamic: bar

' 98 | @result = render(@template, locals: {obj: obj}) 99 | @success = @result is @expected 100 | if @success 101 | obj.foo = 'baz' 102 | @expected = '

dynamic: baz

' 103 | @result = render(@template, locals: {obj: obj}) 104 | @success = @result is @expected 105 | 106 | 'Comments': 107 | template: "comment 'Comment'" 108 | expected: '' 109 | 110 | 'Escaping': 111 | template: "h1 h(\"\")" 112 | expected: "

<script>alert('"pwned" by c&a &copy;')</script>

" 113 | 114 | 'Autoescaping': 115 | template: "h1 \"\"" 116 | expected: "

<script>alert('"pwned" by c&a &copy;')</script>

" 117 | params: {autoescape: yes} 118 | 119 | 'Safe filter': 120 | template: "h1 -> safe @html" 121 | expected: "

plop

" 122 | params: {autoescape: yes, html: "plop"} 123 | 124 | 'ID/class shortcut (combo)': 125 | template: "div '#myid.myclass1.myclass2', 'foo'" 126 | expected: '
foo
' 127 | 128 | 'ID/class shortcut (ID only)': 129 | template: "div '#myid', 'foo'" 130 | expected: '
foo
' 131 | 132 | 'ID/class shortcut (one class only)': 133 | template: "div '.myclass', 'foo'" 134 | expected: '
foo
' 135 | 136 | 'ID/class shortcut (multiple classes)': 137 | template: "div '.myclass.myclass2.myclass3', 'foo'" 138 | expected: '
foo
' 139 | 140 | 'ID/class shortcut (no string contents)': 141 | template: "img '#myid.myclass', src: '/pic.png'" 142 | expected: '' 143 | 144 | 'Attribute values': 145 | template: "br vrai: yes, faux: no, undef: @foo, nil: null, str: 'str', num: 42, arr: [1, 2, 3], obj: {foo: 'bar'}, func: ->" 146 | expected: '
' 147 | 148 | 'IE conditionals': 149 | template: """ 150 | html -> 151 | head -> 152 | title 'test' 153 | ie 'gte IE8', -> 154 | link href: 'ie.css', rel: 'stylesheet' 155 | """ 156 | expected: ''' 157 | 158 | 159 | test 160 | 163 | 164 | 165 | 166 | ''' 167 | params: {format: yes} 168 | 169 | 'yield': 170 | template: "p \"This text could use \#{yield -> strong -> a href: '/', 'a link'}.\"" 171 | expected: '

This text could use a link.

' 172 | 173 | ck = require './src/coffeekup' 174 | render = ck.render 175 | 176 | @run = -> 177 | {print} = require 'sys' 178 | colors = {red: "\033[31m", redder: "\033[91m", green: "\033[32m", normal: "\033[0m"} 179 | printc = (color, str) -> print colors[color] + str + colors.normal 180 | 181 | [total, passed, failed, errors] = [0, [], [], []] 182 | 183 | for name, test of tests 184 | total++ 185 | try 186 | test.original_params = JSON.stringify test.params 187 | 188 | if test.run 189 | test.run() 190 | else 191 | test.result = ck.render(test.template, test.params) 192 | test.success = test.result is test.expected 193 | 194 | if test.success 195 | passed.push name 196 | print "[Passed] #{name}\n" 197 | else 198 | failed.push name 199 | printc 'red', "[Failed] #{name}\n" 200 | catch ex 201 | test.result = ex 202 | errors.push name 203 | printc 'redder', "[Error] #{name}\n" 204 | 205 | print "\n#{total} tests, #{passed.length} passed, #{failed.length} failed, #{errors.length} errors\n\n" 206 | 207 | if failed.length > 0 208 | printc 'red', "FAILED:\n\n" 209 | 210 | for name in failed 211 | t = tests[name] 212 | print "- #{name}:\n" 213 | print t.template + "\n" 214 | print t.original_params + "\n" if t.params 215 | printc 'green', t.expected + "\n" 216 | printc 'red', t.result + "\n\n" 217 | 218 | if errors.length > 0 219 | printc 'redder', "ERRORS:\n\n" 220 | 221 | for name in errors 222 | t = tests[name] 223 | print "- #{name}:\n" 224 | print t.template + "\n" 225 | printc 'green', t.expected + "\n" 226 | printc 'redder', t.result.stack + "\n\n" -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- 1 | # CoffeeKup 0.3.1 Reference 2 | 3 | ## The CoffeeKup object 4 | 5 | Both the returned value from `require 'coffeekup'` and the global `CoffeeKup` created by ` 103 | 104 | Contents (function): 105 | 106 | div -> 'Foo' 107 |
Foo
108 | 109 | # equivalent to js: div(function(){'Foo'; return 'Bar';}); 110 | div -> 111 | 'Foo' 112 | 'Bar' 113 |
114 | Bar 115 |
116 | 117 | # equivalent to js: div(function(){'Foo'; div('Ping'); return 'Bar';}); 118 | div -> 119 | 'Foo' 120 | div 'Ping' 121 | 'Bar' 122 |
123 |
Ping
124 | Bar 125 |
126 | 127 | # equivalent to js: div(function(){text('Foo'); div('Ping'); return 'Bar';}); 128 | div -> 129 | text 'Foo' 130 | div 'Ping' 131 | 'Bar' 132 |
133 | Foo 134 |
Ping
135 | Bar 136 |
137 | 138 | ID/class shortcut 139 | 140 | div '#id.class.anotherclass', 'string contents' 141 |
string contents
142 | 143 | div '#id.class.anotherclass', -> h1 'Hullo' 144 |

Hullo

145 | 146 | div '#id.class.anotherclass', style: 'position: fixed', 'string contents' 147 |
string contents
148 | 149 | ### Other locals 150 | 151 | #### doctype 152 | 153 | Writes the doctype. Usage: `doctype()` (picks the default), `doctype 'xml'` (specifying). You can see and modify the list of doctypes at `CoffeeKup.doctypes`. 154 | 155 | #### comment 156 | 157 | Writes an HTML comment. 158 | 159 | #### ie 160 | 161 | Writes an IE conditional comment. Ex.: 162 | 163 | ie 'gte IE8', -> 164 | link href: 'ie.css', rel: 'stylesheet' 165 | 166 | 169 | 170 | #### text 171 | 172 | Writes arbitrary text to the buffer. 173 | 174 | #### tag 175 | 176 | Used for arbitrary tags. Works like the builtin tags, but the first string parameter is the name of the tag. 177 | 178 | #### coffeescript 179 | 180 | CoffeeScript-friendly shortcut to `script`: 181 | 182 | coffeescript -> alert 'hi' 183 | 187 | 188 | coffeescript "alert 'hi'" 189 | 190 | 191 | coffeescript src: 'script.coffee' 192 | 193 | 194 | #### yield 195 | 196 | Returns the output of a template chunk as a string instead of writing it to the buffer. Useful for string interpolations. Ex.: 197 | 198 | p "This text could use #{yield -> a href: '/', 'a link'}." 199 |

This text could use a link.

200 | 201 | Without it, the `a` function runs first, writes to the buffer and returns `null`, resulting in a useless output: 202 | 203 | p "This text could use #{a href: '/', 'a link'}." 204 | a link

This text could use null.

205 | 206 | #### @ 207 | 208 | CoffeeScript shortcut to `this`. This is where all the input data can be accessed. 209 | 210 | ## Extending CoffeeKup 211 | 212 | template = -> 213 | h1 @title 214 | form method: 'post', action: 'login', -> 215 | textbox id: 'username' 216 | textbox id: 'password' 217 | button @title 218 | 219 | helpers = 220 | textbox: (attrs) -> 221 | attrs.type = 'text' 222 | attrs.name = attrs.id 223 | input attrs 224 | 225 | console.log CoffeeKup.render template, title: 'Log In', hardcode: helpers 226 | 227 | ## The coffeekup command 228 | 229 | When installing CoffeeKup with `npm install coffeekup -g`, you get a `coffeekup` command that allows you to generate HTML from CoffeeKup templates: 230 | 231 | $ coffeekup -h 232 | 233 | Usage: 234 | coffeekup [options] path/to/template.coffee 235 | 236 | --js compile template to js function 237 | -n, --namespace global object holding the templates (default: "templates") 238 | -w, --watch watch templates for changes, and recompile 239 | -o, --output set the directory for compiled html 240 | -p, --print print the compiled html to stdout 241 | -f, --format apply line breaks and indentation to html output 242 | -u, --utils add helper locals (currently only "render") 243 | -v, --version display CoffeeKup version 244 | -h, --help display this help message -------------------------------------------------------------------------------- /examples/browser/decaf/template.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | this.templates || (this.templates = {}); 3 | this.templates["template"] = function anonymous(data) { 4 | var a,i,li,p,s,th,u,ul;a = function(){return __ck.tag('a', arguments);};i = function(){return __ck.tag('i', arguments);};li = function(){return __ck.tag('li', arguments);};p = function(){return __ck.tag('p', arguments);};s = function(){return __ck.tag('s', arguments);};th = function(){return __ck.tag('th', arguments);};u = function(){return __ck.tag('u', arguments);};ul = function(){return __ck.tag('ul', arguments);};var __slice = Array.prototype.slice;var __hasProp = Object.prototype.hasOwnProperty;var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };var __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };var __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (this[i] === item) return i; } return -1; }; 5 | var coffeescript, comment, doctype, h, ie, tag, text, __ck, _ref, _ref2; 6 | if (data == null) { 7 | data = {}; 8 | } 9 | if ((_ref = data.format) != null) { 10 | _ref; 11 | } else { 12 | data.format = false; 13 | }; 14 | if ((_ref2 = data.autoescape) != null) { 15 | _ref2; 16 | } else { 17 | data.autoescape = false; 18 | }; 19 | __ck = { 20 | buffer: [], 21 | esc: function(txt) { 22 | if (data.autoescape) { 23 | return h(txt); 24 | } else { 25 | return String(txt); 26 | } 27 | }, 28 | tabs: 0, 29 | repeat: function(string, count) { 30 | return Array(count + 1).join(string); 31 | }, 32 | indent: function() { 33 | if (data.format) { 34 | return text(this.repeat(' ', this.tabs)); 35 | } 36 | }, 37 | tag: function(name, args) { 38 | var combo, i, _i, _len; 39 | combo = [name]; 40 | for (_i = 0, _len = args.length; _i < _len; _i++) { 41 | i = args[_i]; 42 | combo.push(i); 43 | } 44 | return tag.apply(data, combo); 45 | }, 46 | render_idclass: function(str) { 47 | var c, classes, i, id, _i, _j, _len, _len2, _ref3; 48 | classes = []; 49 | _ref3 = str.split('.'); 50 | for (_i = 0, _len = _ref3.length; _i < _len; _i++) { 51 | i = _ref3[_i]; 52 | if (i.indexOf('#') === 0) { 53 | id = i.replace('#', ''); 54 | } else { 55 | if (i !== '') { 56 | classes.push(i); 57 | } 58 | } 59 | } 60 | if (id) { 61 | text(" id=\"" + id + "\""); 62 | } 63 | if (classes.length > 0) { 64 | text(" class=\""); 65 | for (_j = 0, _len2 = classes.length; _j < _len2; _j++) { 66 | c = classes[_j]; 67 | if (c !== classes[0]) { 68 | text(' '); 69 | } 70 | text(c); 71 | } 72 | return text('"'); 73 | } 74 | }, 75 | render_attrs: function(obj) { 76 | var k, v, _results; 77 | _results = []; 78 | for (k in obj) { 79 | v = obj[k]; 80 | if (typeof v === 'boolean' && v) { 81 | v = k; 82 | } 83 | _results.push(v ? text(" " + k + "=\"" + (this.esc(v)) + "\"") : void 0); 84 | } 85 | return _results; 86 | }, 87 | render_contents: function(contents) { 88 | var result; 89 | switch (typeof contents) { 90 | case 'string': 91 | case 'number': 92 | case 'boolean': 93 | return text(this.esc(contents)); 94 | case 'function': 95 | if (data.format) { 96 | text('\n'); 97 | } 98 | this.tabs++; 99 | result = contents.call(data); 100 | if (typeof result === 'string') { 101 | this.indent(); 102 | text(this.esc(result)); 103 | if (data.format) { 104 | text('\n'); 105 | } 106 | } 107 | this.tabs--; 108 | return this.indent(); 109 | } 110 | }, 111 | render_tag: function(name, idclass, attrs, contents) { 112 | this.indent(); 113 | text("<" + name); 114 | if (idclass) { 115 | this.render_idclass(idclass); 116 | } 117 | if (attrs) { 118 | this.render_attrs(attrs); 119 | } 120 | if (__indexOf.call(this.self_closing, name) >= 0) { 121 | text(' />'); 122 | if (data.format) { 123 | text('\n'); 124 | } 125 | } else { 126 | text('>'); 127 | this.render_contents(contents); 128 | text(""); 129 | if (data.format) { 130 | text('\n'); 131 | } 132 | } 133 | return null; 134 | } 135 | }; 136 | tag = function() { 137 | var a, args, attrs, contents, idclass, name, _i, _len; 138 | name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 139 | for (_i = 0, _len = args.length; _i < _len; _i++) { 140 | a = args[_i]; 141 | switch (typeof a) { 142 | case 'function': 143 | contents = a; 144 | break; 145 | case 'object': 146 | attrs = a; 147 | break; 148 | case 'number': 149 | case 'boolean': 150 | contents = a; 151 | break; 152 | case 'string': 153 | if (args.length === 1) { 154 | contents = a; 155 | } else { 156 | if (a === args[0]) { 157 | idclass = a; 158 | } else { 159 | contents = a; 160 | } 161 | } 162 | } 163 | } 164 | return __ck.render_tag(name, idclass, attrs, contents); 165 | }; 166 | h = function(txt) { 167 | return String(txt).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); 168 | }; 169 | doctype = function(type) { 170 | if (type == null) { 171 | type = 'default'; 172 | } 173 | text(__ck.doctypes[type]); 174 | if (data.format) { 175 | return text('\n'); 176 | } 177 | }; 178 | text = function(txt) { 179 | __ck.buffer.push(String(txt)); 180 | return null; 181 | }; 182 | comment = function(cmt) { 183 | text(""); 184 | if (data.format) { 185 | return text('\n'); 186 | } 187 | }; 188 | coffeescript = function(param) { 189 | switch (typeof param) { 190 | case 'function': 191 | return script("" + __ck.coffeescript_helpers + "(" + param + ").call(this);"); 192 | case 'string': 193 | return script({ 194 | type: 'text/coffeescript' 195 | }, function() { 196 | return param; 197 | }); 198 | case 'object': 199 | param.type = 'text/coffeescript'; 200 | return script(param); 201 | } 202 | }; 203 | ie = function(condition, contents) { 204 | __ck.indent(); 205 | text(""); 208 | if (data.format) { 209 | return text('\n'); 210 | } 211 | }; 212 | __ck.doctypes = {"5":"","default":"","xml":"","transitional":"","strict":"","frameset":"","1.1":"","basic":"","mobile":"","ce":""};__ck.coffeescript_helpers = "var __slice = Array.prototype.slice;var __hasProp = Object.prototype.hasOwnProperty;var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };var __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };var __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (this[i] === item) return i; } return -1; };";__ck.self_closing = ["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","basefont","frame"];(function(){ul(function() { 213 | var guy, _i, _len, _ref, _results; 214 | _ref = this.stooges; 215 | _results = []; 216 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 217 | guy = _ref[_i]; 218 | _results.push(li(guy)); 219 | } 220 | return _results; 221 | });}).call(data);return __ck.buffer.join(''); 222 | }; 223 | }).call(this); -------------------------------------------------------------------------------- /examples/browser/regular/template.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | this.templates || (this.templates = {}); 3 | this.templates["template"] = function anonymous(data) { 4 | var a,i,li,p,s,th,u,ul;a = function(){return __ck.tag('a', arguments);};i = function(){return __ck.tag('i', arguments);};li = function(){return __ck.tag('li', arguments);};p = function(){return __ck.tag('p', arguments);};s = function(){return __ck.tag('s', arguments);};th = function(){return __ck.tag('th', arguments);};u = function(){return __ck.tag('u', arguments);};ul = function(){return __ck.tag('ul', arguments);};var __slice = Array.prototype.slice;var __hasProp = Object.prototype.hasOwnProperty;var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };var __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };var __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (this[i] === item) return i; } return -1; }; 5 | var coffeescript, comment, doctype, h, ie, tag, text, __ck, _ref, _ref2; 6 | if (data == null) { 7 | data = {}; 8 | } 9 | if ((_ref = data.format) != null) { 10 | _ref; 11 | } else { 12 | data.format = false; 13 | }; 14 | if ((_ref2 = data.autoescape) != null) { 15 | _ref2; 16 | } else { 17 | data.autoescape = false; 18 | }; 19 | __ck = { 20 | buffer: [], 21 | esc: function(txt) { 22 | if (data.autoescape) { 23 | return h(txt); 24 | } else { 25 | return String(txt); 26 | } 27 | }, 28 | tabs: 0, 29 | repeat: function(string, count) { 30 | return Array(count + 1).join(string); 31 | }, 32 | indent: function() { 33 | if (data.format) { 34 | return text(this.repeat(' ', this.tabs)); 35 | } 36 | }, 37 | tag: function(name, args) { 38 | var combo, i, _i, _len; 39 | combo = [name]; 40 | for (_i = 0, _len = args.length; _i < _len; _i++) { 41 | i = args[_i]; 42 | combo.push(i); 43 | } 44 | return tag.apply(data, combo); 45 | }, 46 | render_idclass: function(str) { 47 | var c, classes, i, id, _i, _j, _len, _len2, _ref3; 48 | classes = []; 49 | _ref3 = str.split('.'); 50 | for (_i = 0, _len = _ref3.length; _i < _len; _i++) { 51 | i = _ref3[_i]; 52 | if (i.indexOf('#') === 0) { 53 | id = i.replace('#', ''); 54 | } else { 55 | if (i !== '') { 56 | classes.push(i); 57 | } 58 | } 59 | } 60 | if (id) { 61 | text(" id=\"" + id + "\""); 62 | } 63 | if (classes.length > 0) { 64 | text(" class=\""); 65 | for (_j = 0, _len2 = classes.length; _j < _len2; _j++) { 66 | c = classes[_j]; 67 | if (c !== classes[0]) { 68 | text(' '); 69 | } 70 | text(c); 71 | } 72 | return text('"'); 73 | } 74 | }, 75 | render_attrs: function(obj) { 76 | var k, v, _results; 77 | _results = []; 78 | for (k in obj) { 79 | v = obj[k]; 80 | if (typeof v === 'boolean' && v) { 81 | v = k; 82 | } 83 | _results.push(v ? text(" " + k + "=\"" + (this.esc(v)) + "\"") : void 0); 84 | } 85 | return _results; 86 | }, 87 | render_contents: function(contents) { 88 | var result; 89 | switch (typeof contents) { 90 | case 'string': 91 | case 'number': 92 | case 'boolean': 93 | return text(this.esc(contents)); 94 | case 'function': 95 | if (data.format) { 96 | text('\n'); 97 | } 98 | this.tabs++; 99 | result = contents.call(data); 100 | if (typeof result === 'string') { 101 | this.indent(); 102 | text(this.esc(result)); 103 | if (data.format) { 104 | text('\n'); 105 | } 106 | } 107 | this.tabs--; 108 | return this.indent(); 109 | } 110 | }, 111 | render_tag: function(name, idclass, attrs, contents) { 112 | this.indent(); 113 | text("<" + name); 114 | if (idclass) { 115 | this.render_idclass(idclass); 116 | } 117 | if (attrs) { 118 | this.render_attrs(attrs); 119 | } 120 | if (__indexOf.call(this.self_closing, name) >= 0) { 121 | text(' />'); 122 | if (data.format) { 123 | text('\n'); 124 | } 125 | } else { 126 | text('>'); 127 | this.render_contents(contents); 128 | text(""); 129 | if (data.format) { 130 | text('\n'); 131 | } 132 | } 133 | return null; 134 | } 135 | }; 136 | tag = function() { 137 | var a, args, attrs, contents, idclass, name, _i, _len; 138 | name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 139 | for (_i = 0, _len = args.length; _i < _len; _i++) { 140 | a = args[_i]; 141 | switch (typeof a) { 142 | case 'function': 143 | contents = a; 144 | break; 145 | case 'object': 146 | attrs = a; 147 | break; 148 | case 'number': 149 | case 'boolean': 150 | contents = a; 151 | break; 152 | case 'string': 153 | if (args.length === 1) { 154 | contents = a; 155 | } else { 156 | if (a === args[0]) { 157 | idclass = a; 158 | } else { 159 | contents = a; 160 | } 161 | } 162 | } 163 | } 164 | return __ck.render_tag(name, idclass, attrs, contents); 165 | }; 166 | h = function(txt) { 167 | return String(txt).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); 168 | }; 169 | doctype = function(type) { 170 | if (type == null) { 171 | type = 'default'; 172 | } 173 | text(__ck.doctypes[type]); 174 | if (data.format) { 175 | return text('\n'); 176 | } 177 | }; 178 | text = function(txt) { 179 | __ck.buffer.push(String(txt)); 180 | return null; 181 | }; 182 | comment = function(cmt) { 183 | text(""); 184 | if (data.format) { 185 | return text('\n'); 186 | } 187 | }; 188 | coffeescript = function(param) { 189 | switch (typeof param) { 190 | case 'function': 191 | return script("" + __ck.coffeescript_helpers + "(" + param + ").call(this);"); 192 | case 'string': 193 | return script({ 194 | type: 'text/coffeescript' 195 | }, function() { 196 | return param; 197 | }); 198 | case 'object': 199 | param.type = 'text/coffeescript'; 200 | return script(param); 201 | } 202 | }; 203 | ie = function(condition, contents) { 204 | __ck.indent(); 205 | text(""); 208 | if (data.format) { 209 | return text('\n'); 210 | } 211 | }; 212 | __ck.doctypes = {"5":"","default":"","xml":"","transitional":"","strict":"","frameset":"","1.1":"","basic":"","mobile":"","ce":""};__ck.coffeescript_helpers = "var __slice = Array.prototype.slice;var __hasProp = Object.prototype.hasOwnProperty;var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };var __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };var __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (this[i] === item) return i; } return -1; };";__ck.self_closing = ["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","basefont","frame"];(function(){ul(function() { 213 | var guy, _i, _len, _ref, _results; 214 | _ref = this.stooges; 215 | _results = []; 216 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 217 | guy = _ref[_i]; 218 | _results.push(li(guy)); 219 | } 220 | return _results; 221 | });}).call(data);return __ck.buffer.join(''); 222 | }; 223 | }).call(this); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoffeeKup <☕/> 2 | ## Markup as CoffeeScript 3 | 4 | CoffeeKup is a templating engine for [node.js](http://nodejs.org) and browsers that lets you to write your HTML templates in 100% pure [CoffeeScript](http://coffeescript.org). 5 | 6 | It was created in celebration of [whyday](http://whyday.org/), as an application of the concept used in [Markaby](https://github.com/markaby/markaby) ("Markup as Ruby", by Tim Fletcher and why the lucky stiff) to CoffeeScript. 7 | 8 | Here's what a template written for CoffeeKup looks like: 9 | 10 | doctype 5 11 | html -> 12 | head -> 13 | meta charset: 'utf-8' 14 | title "#{@title or 'Untitled'} | A completely plausible website" 15 | meta(name: 'description', content: @description) if @description? 16 | 17 | link rel: 'stylesheet', href: '/css/app.css' 18 | 19 | style ''' 20 | body {font-family: sans-serif} 21 | header, nav, section, footer {display: block} 22 | ''' 23 | 24 | script src: '/js/jquery.js' 25 | 26 | coffeescript -> 27 | $(document).ready -> 28 | alert 'Alerts suck!' 29 | body -> 30 | header -> 31 | h1 @title or 'Untitled' 32 | 33 | nav -> 34 | ul -> 35 | (li -> a href: '/', -> 'Home') unless @path is '/' 36 | li -> a href: '/chunky', -> 'Bacon!' 37 | switch @user.role 38 | when 'owner', 'admin' 39 | li -> a href: '/admin', -> 'Secret Stuff' 40 | when 'vip' 41 | li -> a href: '/vip', -> 'Exclusive Stuff' 42 | else 43 | li -> a href: '/commoners', -> 'Just Stuff' 44 | 45 | div '#myid.myclass.anotherclass', style: 'position: fixed', -> 46 | p 'Divitis kills! Inline styling too.' 47 | 48 | section -> 49 | # A helper function you built and included. 50 | breadcrumb separator: '>', clickable: yes 51 | 52 | h2 "Let's count to 10:" 53 | p i for i in [1..10] 54 | 55 | # Another hypothetical helper. 56 | form_to @post, -> 57 | textbox '#title', label: 'Title:' 58 | textbox '#author', label: 'Author:' 59 | submit 'Save' 60 | 61 | footer -> 62 | # CoffeeScript comments. Not visible in the output document. 63 | comment 'HTML comments.' 64 | p 'Bye!' 65 | 66 | Interactive demo at [coffeekup.org](http://coffeekup.org). 67 | 68 | ## _why? 69 | 70 | - **One language to rule them all**. JavaScript is everywhere, thus so is CoffeeScript. Servers, browsers, even databases. If extending this to rendering logic and UI structure (server and client side) is desirable to you, CoffeeKup is your friend. 71 | 72 | - **More specifically, one _outstanding_ language**. CoffeeScript is one hell of a clean, expressive, flexible and powerful language. It's hard to find such combination, especially if you need it to run in the browser too. 73 | 74 | - **Not yet another specialized language to learn**. Transferable knowledge FTW. 75 | 76 | - **Embed your templates in CoffeeScript nicely**. Templates are just functions, so they don't lose syntax highlighting and syntax checking when embedded in CoffeeScript apps. 77 | 78 | - **Embed CoffeeScript in your templates nicely**. In the same manner, you can write the contents of ` 188 | 191 | 192 | This is one of many browser deployment possibilities, pre-compiling your template on the server to a standalone function. To see all serving suggestions, check out [regular](http://github.com/mauricemach/coffeekup/blob/master/examples/browser/regular/index.html), [decaf](http://github.com/mauricemach/coffeekup/blob/master/examples/browser/decaf/index.html) and [crème](http://github.com/mauricemach/coffeekup/blob/master/examples/browser/creme/index.html). 193 | 194 | Command-line: 195 | 196 | $ coffeekup -h 197 | 198 | Usage: 199 | coffeekup [options] path/to/template.coffee 200 | 201 | --js compile template to js function 202 | -n, --namespace global object holding the templates (default: "templates") 203 | -w, --watch watch templates for changes, and recompile 204 | -o, --output set the directory for compiled html 205 | -p, --print print the compiled html to stdout 206 | -f, --format apply line breaks and indentation to html output 207 | -u, --utils add helper locals (currently only "render") 208 | -v, --version display CoffeeKup version 209 | -h, --help display this help message 210 | 211 | See [/examples](http://github.com/mauricemach/coffeekup/tree/master/examples) for complete versions (you have to run `cake build` first). 212 | 213 | Please note that even though all examples are given in CoffeeScript, you can also use their plain JavaScript counterparts just fine. 214 | 215 | ## Resources 216 | 217 | - [API reference](https://github.com/mauricemach/coffeekup/blob/master/docs/reference.md) 218 | 219 | - [Mailing list](https://groups.google.com/group/coffeekup) 220 | 221 | - [Issues](https://github.com/mauricemach/coffeekup/issues) 222 | 223 | - **IRC**: #coffeekup on irc.freenode.net 224 | 225 | - [A Beginners's Introduction to CoffeeKup](https://github.com/mark-hahn/coffeekup-intro) 226 | 227 | ## Tools 228 | 229 | - [html2coffeekup](https://github.com/brandonbloom/html2coffeekup) - Converts HTML to CoffeeKup templates. 230 | 231 | - [htmlkup](https://github.com/colinta/htmlkup) - Another HTML converter, stdin/stdout based. 232 | 233 | - [ice](https://github.com/ludicast/ice) - CoffeeKup and Eco in Rails ([screencast](http://vimeo.com/25907220)). 234 | 235 | - [coffee-world](https://github.com/khoomeister/coffee-world) - Tool to watch and compile HTML with CoffeeKup, CSS with coffee-css and JS with CoffeeScript. 236 | 237 | - [cupcake](https://github.com/twilson63/cupcake) - Express app generator with CoffeeKup support. 238 | 239 | ## Related projects 240 | 241 | - [ck](https://github.com/aeosynth/ck) - "a smaller, faster coffeekup": Alternative, barebones implementation. 242 | 243 | - [ckup](https://github.com/satyr/ckup) - "Markup as Coco": Similar engine but for [Coco](https://github.com/satyr/coco) ("Unfancy CoffeeScript"). 244 | 245 | - [Eco](https://github.com/sstephenson/eco) - "Embedded CoffeeScript templates": "EJS/ERB" for CoffeeScript. 246 | 247 | - [timbits](https://github.com/Postmedia/timbits) - "Widget framework based on Express and CoffeeScript". 248 | 249 | - [coffee-css](https://github.com/khoomeister/coffee-css) - "More CSS for CoffeeScript". 250 | 251 | - [ccss](https://github.com/aeosynth/ccss) - "CoffeeScript CSS". 252 | 253 | ## Compatibility 254 | 255 | Latest version tested with node 0.4.9 and CoffeeScript 1.1.1. 256 | 257 | ## Special thanks 258 | 259 | - [Jeremy Ashkenas](https://github.com/jashkenas), for the amazing CoffeeScript language. 260 | - [why the lucky stiff](Why_the_lucky_stiff), for the inspiration. 261 | -------------------------------------------------------------------------------- /src/coffeekup.coffee: -------------------------------------------------------------------------------- 1 | # **CoffeeKup** lets you to write HTML templates in 100% pure 2 | # [CoffeeScript](http://coffeescript.org). 3 | # 4 | # You can run it on [node.js](http://nodejs.org) or the browser, or compile your 5 | # templates down to self-contained javascript functions, that will take in data 6 | # and options and return generated HTML on any JS runtime. 7 | # 8 | # The concept is directly stolen from the amazing 9 | # [Markaby](http://markaby.rubyforge.org/) by Tim Fletcher and why the lucky 10 | # stiff. 11 | 12 | if window? 13 | coffeekup = window.CoffeeKup = {} 14 | coffee = if CoffeeScript? then CoffeeScript else null 15 | else 16 | coffeekup = exports 17 | coffee = require 'coffee-script' 18 | 19 | coffeekup.version = '0.3.1' 20 | 21 | # Values available to the `doctype` function inside a template. 22 | # Ex.: `doctype 'strict'` 23 | coffeekup.doctypes = 24 | 'default': '' 25 | '5': '' 26 | 'xml': '' 27 | 'transitional': '' 28 | 'strict': '' 29 | 'frameset': '' 30 | '1.1': '', 31 | 'basic': '' 32 | 'mobile': '' 33 | 'ce': '' 34 | 35 | # CoffeeScript-generated JavaScript may contain anyone of these; but when we 36 | # take a function to string form to manipulate it, and then recreate it through 37 | # the `Function()` constructor, it loses access to its parent scope and 38 | # consequently to any helpers it might need. So we need to reintroduce these 39 | # inside any "rewritten" function. 40 | coffeescript_helpers = """ 41 | var __slice = Array.prototype.slice; 42 | var __hasProp = Object.prototype.hasOwnProperty; 43 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 44 | var __extends = function(child, parent) { 45 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 46 | function ctor() { this.constructor = child; } 47 | ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; 48 | return child; }; 49 | var __indexOf = Array.prototype.indexOf || function(item) { 50 | for (var i = 0, l = this.length; i < l; i++) { 51 | if (this[i] === item) return i; 52 | } return -1; }; 53 | """.replace /\n/g, '' 54 | 55 | # Private HTML element reference. 56 | # Please mind the gap (1 space at the beginning of each subsequent line). 57 | elements = 58 | # Valid HTML 5 elements requiring a closing tag. 59 | # Note: the `var` element is out for obvious reasons, please use `tag 'var'`. 60 | regular: 'a abbr address article aside audio b bdi bdo blockquote body button 61 | canvas caption cite code colgroup datalist dd del details dfn div dl dt em 62 | fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup 63 | html i iframe ins kbd label legend li map mark menu meter nav noscript object 64 | ol optgroup option output p pre progress q rp rt ruby s samp script section 65 | select small span strong style sub summary sup table tbody td textarea tfoot 66 | th thead time title tr u ul video' 67 | 68 | # Valid self-closing HTML 5 elements. 69 | void: 'area base br col command embed hr img input keygen link meta param 70 | source track wbr' 71 | 72 | obsolete: 'applet acronym bgsound dir frameset noframes isindex listing 73 | nextid noembed plaintext rb strike xmp big blink center font marquee multicol 74 | nobr spacer tt' 75 | 76 | obsolete_void: 'basefont frame' 77 | 78 | # Create a unique list of element names merging the desired groups. 79 | merge_elements = (args...) -> 80 | result = [] 81 | for a in args 82 | for element in elements[a].split ' ' 83 | result.push element unless element in result 84 | result 85 | 86 | # Public/customizable list of possible elements. 87 | # For each name in this list that is also present in the input template code, 88 | # a function with the same name will be added to the compiled template. 89 | coffeekup.tags = merge_elements 'regular', 'obsolete', 'void', 'obsolete_void' 90 | 91 | # Public/customizable list of elements that should be rendered self-closed. 92 | coffeekup.self_closing = merge_elements 'void', 'obsolete_void' 93 | 94 | # This is the basic material from which compiled templates will be formed. 95 | # It will be manipulated in its string form at the `coffeekup.compile` function 96 | # to generate the final template function. 97 | skeleton = (data = {}) -> 98 | # Whether to generate formatted HTML with indentation and line breaks, or 99 | # just the natural "faux-minified" output. 100 | data.format ?= off 101 | 102 | # Whether to autoescape all content or let you handle it on a case by case 103 | # basis with the `h` function. 104 | data.autoescape ?= off 105 | 106 | # Internal CoffeeKup stuff. 107 | __ck = 108 | buffer: [] 109 | 110 | esc: (txt) -> 111 | if data.autoescape then h(txt) else String(txt) 112 | 113 | tabs: 0 114 | 115 | repeat: (string, count) -> Array(count + 1).join string 116 | 117 | indent: -> text @repeat(' ', @tabs) if data.format 118 | 119 | # Adapter to keep the builtin tag functions DRY. 120 | tag: (name, args) -> 121 | combo = [name] 122 | combo.push i for i in args 123 | tag.apply data, combo 124 | 125 | render_idclass: (str) -> 126 | classes = [] 127 | 128 | for i in str.split '.' 129 | if '#' in i 130 | id = i.replace '#', '' 131 | else 132 | classes.push i unless i is '' 133 | 134 | text " id=\"#{id}\"" if id 135 | 136 | if classes.length > 0 137 | text " class=\"" 138 | for c in classes 139 | text ' ' unless c is classes[0] 140 | text c 141 | text '"' 142 | 143 | render_attrs: (obj, prefix = '') -> 144 | for k, v of obj 145 | # `true` is rendered as `selected="selected"`. 146 | v = k if typeof v is 'boolean' and v 147 | 148 | # Functions are rendered in an executable form. 149 | v = "(#{v}).call(this);" if typeof v is 'function' 150 | 151 | # Prefixed attribute. 152 | if typeof v is 'object' and v not instanceof Array 153 | # `data: {icon: 'foo'}` is rendered as `data-icon="foo"`. 154 | @render_attrs(v, prefix + k + '-') 155 | # `undefined`, `false` and `null` result in the attribute not being rendered. 156 | else if v 157 | # strings, numbers, arrays and functions are rendered "as is". 158 | text " #{prefix + k}=\"#{@esc(v)}\"" 159 | 160 | render_contents: (contents, safe) -> 161 | safe ?= false 162 | switch typeof contents 163 | when 'string', 'number', 'boolean' 164 | text if safe then contents else @esc(contents) 165 | when 'function' 166 | text '\n' if data.format 167 | @tabs++ 168 | result = contents.call data 169 | if typeof result is 'string' 170 | @indent() 171 | text if safe then result else @esc(result) 172 | text '\n' if data.format 173 | @tabs-- 174 | @indent() 175 | 176 | render_tag: (name, idclass, attrs, contents) -> 177 | @indent() 178 | 179 | text "<#{name}" 180 | @render_idclass(idclass) if idclass 181 | @render_attrs(attrs) if attrs 182 | 183 | if name in @self_closing 184 | text ' />' 185 | text '\n' if data.format 186 | else 187 | text '>' 188 | 189 | @render_contents(contents) 190 | 191 | text "" 192 | text '\n' if data.format 193 | 194 | null 195 | 196 | tag = (name, args...) -> 197 | for a in args 198 | switch typeof a 199 | when 'function' 200 | contents = a 201 | when 'object' 202 | attrs = a 203 | when 'number', 'boolean' 204 | contents = a 205 | when 'string' 206 | if args.length is 1 207 | contents = a 208 | else 209 | if a is args[0] 210 | idclass = a 211 | else 212 | contents = a 213 | 214 | __ck.render_tag(name, idclass, attrs, contents) 215 | 216 | yield = (f) -> 217 | temp_buffer = [] 218 | old_buffer = __ck.buffer 219 | __ck.buffer = temp_buffer 220 | f() 221 | __ck.buffer = old_buffer 222 | temp_buffer.join '' 223 | 224 | h = (txt) -> 225 | String(txt).replace(/&/g, '&') 226 | .replace(//g, '>') 228 | .replace(/"/g, '"') 229 | 230 | doctype = (type = 'default') -> 231 | text __ck.doctypes[type] 232 | text '\n' if data.format 233 | 234 | safe = (val) -> 235 | __ck.render_contents(val, true) 236 | 237 | text = (txt) -> 238 | __ck.buffer.push String(txt) 239 | null 240 | 241 | comment = (cmt) -> 242 | text "" 243 | text '\n' if data.format 244 | 245 | coffeescript = (param) -> 246 | switch typeof param 247 | # `coffeescript -> alert 'hi'` becomes: 248 | # `` 249 | when 'function' 250 | script "#{__ck.coffeescript_helpers}(#{param}).call(this);" 251 | # `coffeescript "alert 'hi'"` becomes: 252 | # `` 253 | when 'string' 254 | script type: 'text/coffeescript', -> param 255 | # `coffeescript src: 'script.coffee'` becomes: 256 | # `` 257 | when 'object' 258 | param.type = 'text/coffeescript' 259 | script param 260 | 261 | # Conditional IE comments. 262 | ie = (condition, contents) -> 263 | __ck.indent() 264 | 265 | text "" 268 | text '\n' if data.format 269 | 270 | null 271 | 272 | # Stringify the skeleton and unwrap it from its enclosing `function(){}`, then 273 | # add the CoffeeScript helpers. 274 | skeleton = String(skeleton) 275 | .replace(/function\s*\(.*\)\s*\{/, '') 276 | .replace(/return null;\s*\}$/, '') 277 | 278 | skeleton = coffeescript_helpers + skeleton 279 | 280 | # Compiles a template into a standalone JavaScript function. 281 | coffeekup.compile = (template, options = {}) -> 282 | # The template can be provided as either a function or a CoffeeScript string 283 | # (in the latter case, the CoffeeScript compiler must be available). 284 | if typeof template is 'function' then template = String(template) 285 | else if typeof template is 'string' and coffee? 286 | template = coffee.compile template, bare: yes 287 | template = "function(){#{template}}" 288 | 289 | # If an object `hardcode` is provided, insert the stringified value 290 | # of each variable directly in the function body. This is a less flexible but 291 | # faster alternative to the standard method of using `with` (see below). 292 | hardcoded_locals = '' 293 | 294 | if options.hardcode 295 | for k, v of options.hardcode 296 | if typeof v is 'function' 297 | # Make sure these functions have access to `data` as `@/this`. 298 | hardcoded_locals += "var #{k} = function(){return (#{v}).apply(data, arguments);};" 299 | else hardcoded_locals += "var #{k} = #{JSON.stringify v};" 300 | 301 | # Add a function for each tag this template references. We don't want to have 302 | # all hundred-odd tags wasting space in the compiled function. 303 | tag_functions = '' 304 | tags_used = [] 305 | 306 | for t in coffeekup.tags 307 | if template.indexOf(t) > -1 or hardcoded_locals.indexOf(t) > -1 308 | tags_used.push t 309 | 310 | tag_functions += "var #{tags_used.join ','};" 311 | for t in tags_used 312 | tag_functions += "#{t} = function(){return __ck.tag('#{t}', arguments);};" 313 | 314 | # Main function assembly. 315 | code = tag_functions + hardcoded_locals + skeleton 316 | 317 | code += "__ck.doctypes = #{JSON.stringify coffeekup.doctypes};" 318 | code += "__ck.coffeescript_helpers = #{JSON.stringify coffeescript_helpers};" 319 | code += "__ck.self_closing = #{JSON.stringify coffeekup.self_closing};" 320 | 321 | # If `locals` is set, wrap the template inside a `with` block. This is the 322 | # most flexible but slower approach to specifying local variables. 323 | code += 'with(data.locals){' if options.locals 324 | code += "(#{template}).call(data);" 325 | code += '}' if options.locals 326 | code += "return __ck.buffer.join('');" 327 | 328 | new Function('data', code) 329 | 330 | cache = {} 331 | 332 | # Template in, HTML out. Accepts functions or strings as does `coffeekup.compile`. 333 | # 334 | # Accepts an option `cache`, by default `false`. If set to `false` templates will 335 | # be recompiled each time. 336 | # 337 | # `options` is just a convenience parameter to pass options separately from the 338 | # data, but the two will be merged and passed down to the compiler (which uses 339 | # `locals` and `hardcode`), and the template (which understands `locals`, `format` 340 | # and `autoescape`). 341 | coffeekup.render = (template, data = {}, options = {}) -> 342 | data[k] = v for k, v of options 343 | data.cache ?= off 344 | 345 | if data.cache and cache[template]? then tpl = cache[template] 346 | else if data.cache then tpl = cache[template] = coffeekup.compile(template, data) 347 | else tpl = coffeekup.compile(template, data) 348 | tpl(data) 349 | 350 | unless window? 351 | coffeekup.adapters = 352 | # Legacy adapters for when CoffeeKup expected data in the `context` attribute. 353 | simple: coffeekup.render 354 | meryl: coffeekup.render 355 | 356 | express: 357 | TemplateError: class extends Error 358 | constructor: (@message) -> 359 | Error.call this, @message 360 | Error.captureStackTrace this, arguments.callee 361 | name: 'TemplateError' 362 | 363 | compile: (template, data) -> 364 | # Allows `partial 'foo'` instead of `text @partial 'foo'`. 365 | data.hardcode ?= {} 366 | data.hardcode.partial = -> 367 | text @partial.apply @, arguments 368 | 369 | TemplateError = @TemplateError 370 | try tpl = coffeekup.compile(template, data) 371 | catch e then throw new TemplateError "Error compiling #{data.filename}: #{e.message}" 372 | 373 | return -> 374 | try tpl arguments... 375 | catch e then throw new TemplateError "Error rendering #{data.filename}: #{e.message}" -------------------------------------------------------------------------------- /docs/coffeekup.html: -------------------------------------------------------------------------------- 1 | coffeekup.coffee

coffeekup.coffee

CoffeeKup lets you to write HTML templates in 100% pure 2 | CoffeeScript.

3 | 4 |

You can run it on node.js or the browser, or compile your 5 | templates down to self-contained javascript functions, that will take in data 6 | and options and return generated HTML on any JS runtime.

7 | 8 |

The concept is directly stolen from the amazing 9 | Markaby by Tim Fletcher and why the lucky 10 | stiff.

if window?
 11 |   coffeekup = window.CoffeeKup = {}
 12 |   coffee = if CoffeeScript? then CoffeeScript else null
 13 | else
 14 |   coffeekup = exports
 15 |   coffee = require 'coffee-script'
 16 | 
 17 | coffeekup.version = '0.3.1'

Values available to the doctype function inside a template. 18 | Ex.: doctype 'strict'

coffeekup.doctypes =
 19 |   'default': '<!DOCTYPE html>'
 20 |   '5': '<!DOCTYPE html>'
 21 |   'xml': '<?xml version="1.0" encoding="utf-8" ?>'
 22 |   'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
 23 |   'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
 24 |   'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
 25 |   '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
 26 |   'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
 27 |   'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
 28 |   'ce': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ce-html-1.0-transitional.dtd">'

CoffeeScript-generated JavaScript may contain anyone of these; but when we 29 | take a function to string form to manipulate it, and then recreate it through 30 | the Function() constructor, it loses access to its parent scope and 31 | consequently to any helpers it might need. So we need to reintroduce these 32 | inside any "rewritten" function.

coffeescript_helpers = """
 33 |   var __slice = Array.prototype.slice;
 34 |   var __hasProp = Object.prototype.hasOwnProperty;
 35 |   var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 36 |   var __extends = function(child, parent) {
 37 |     for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
 38 |     function ctor() { this.constructor = child; }
 39 |     ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype;
 40 |     return child; };
 41 |   var __indexOf = Array.prototype.indexOf || function(item) {
 42 |     for (var i = 0, l = this.length; i < l; i++) {
 43 |       if (this[i] === item) return i;
 44 |     } return -1; };
 45 | """.replace /\n/g, ''

Private HTML element reference. 46 | Please mind the gap (1 space at the beginning of each subsequent line).

elements =

Valid HTML 5 elements requiring a closing tag. 47 | Note: the var element is out for obvious reasons, please use tag 'var'.

  regular: 'a abbr address article aside audio b bdi bdo blockquote body button
 48 |  canvas caption cite code colgroup datalist dd del details dfn div dl dt em
 49 |  fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup
 50 |  html i iframe ins kbd label legend li map mark menu meter nav noscript object
 51 |  ol optgroup option output p pre progress q rp rt ruby s samp script section
 52 |  select small span strong style sub summary sup table tbody td textarea tfoot
 53 |  th thead time title tr u ul video'

Valid self-closing HTML 5 elements.

  void: 'area base br col command embed hr img input keygen link meta param
 54 |  source track wbr'
 55 | 
 56 |   obsolete: 'applet acronym bgsound dir frameset noframes isindex listing
 57 |  nextid noembed plaintext rb strike xmp big blink center font marquee multicol
 58 |  nobr spacer tt'
 59 | 
 60 |   obsolete_void: 'basefont frame'

Create a unique list of element names merging the desired groups.

merge_elements = (args...) ->
 61 |   result = []
 62 |   for a in args
 63 |     for element in elements[a].split ' '
 64 |       result.push element unless element in result
 65 |   result

Public/customizable list of possible elements. 66 | For each name in this list that is also present in the input template code, 67 | a function with the same name will be added to the compiled template.

coffeekup.tags = merge_elements 'regular', 'obsolete', 'void', 'obsolete_void'

Public/customizable list of elements that should be rendered self-closed.

coffeekup.self_closing = merge_elements 'void', 'obsolete_void'

This is the basic material from which compiled templates will be formed. 68 | It will be manipulated in its string form at the coffeekup.compile function 69 | to generate the final template function.

skeleton = (data = {}) ->

Whether to generate formatted HTML with indentation and line breaks, or 70 | just the natural "faux-minified" output.

  data.format ?= off

Whether to autoescape all content or let you handle it on a case by case 71 | basis with the h function.

  data.autoescape ?= off

Internal CoffeeKup stuff.

  __ck =
 72 |     buffer: []
 73 |       
 74 |     esc: (txt) ->
 75 |       if data.autoescape then h(txt) else String(txt)
 76 | 
 77 |     tabs: 0
 78 | 
 79 |     repeat: (string, count) -> Array(count + 1).join string
 80 | 
 81 |     indent: -> text @repeat('  ', @tabs) if data.format

Adapter to keep the builtin tag functions DRY.

    tag: (name, args) ->
 82 |       combo = [name]
 83 |       combo.push i for i in args
 84 |       tag.apply data, combo
 85 | 
 86 |     render_idclass: (str) ->
 87 |       classes = []
 88 |         
 89 |       for i in str.split '.'
 90 |         if '#' in i
 91 |           id = i.replace '#', ''
 92 |         else
 93 |           classes.push i unless i is ''
 94 |             
 95 |       text " id=\"#{id}\"" if id
 96 |       
 97 |       if classes.length > 0
 98 |         text " class=\""
 99 |         for c in classes
100 |           text ' ' unless c is classes[0]
101 |           text c
102 |         text '"'
103 | 
104 |     render_attrs: (obj, prefix = '') ->
105 |       for k, v of obj

true is rendered as selected="selected".

        v = k if typeof v is 'boolean' and v
106 |         

Functions are rendered in an executable form.

        v = "(#{v}).call(this);" if typeof v is 'function'

Prefixed attribute.

        if typeof v is 'object' and v not instanceof Array

data: {icon: 'foo'} is rendered as data-icon="foo".

          @render_attrs(v, prefix + k + '-')

undefined, false and null result in the attribute not being rendered.

        else if v

strings, numbers, arrays and functions are rendered "as is".

          text " #{prefix + k}=\"#{@esc(v)}\""
107 | 
108 |     render_contents: (contents) ->
109 |       switch typeof contents
110 |         when 'string', 'number', 'boolean'
111 |           text @esc(contents)
112 |         when 'function'
113 |           text '\n' if data.format
114 |           @tabs++
115 |           result = contents.call data
116 |           if typeof result is 'string'
117 |             @indent()
118 |             text @esc(result)
119 |             text '\n' if data.format
120 |           @tabs--
121 |           @indent()
122 | 
123 |     render_tag: (name, idclass, attrs, contents) ->
124 |       @indent()
125 |     
126 |       text "<#{name}"
127 |       @render_idclass(idclass) if idclass
128 |       @render_attrs(attrs) if attrs
129 |   
130 |       if name in @self_closing
131 |         text ' />'
132 |         text '\n' if data.format
133 |       else
134 |         text '>'
135 |   
136 |         @render_contents(contents)
137 | 
138 |         text "</#{name}>"
139 |         text '\n' if data.format
140 |   
141 |       null
142 | 
143 |   tag = (name, args...) ->
144 |     for a in args
145 |       switch typeof a
146 |         when 'function'
147 |           contents = a
148 |         when 'object'
149 |           attrs = a
150 |         when 'number', 'boolean'
151 |           contents = a
152 |         when 'string'
153 |           if args.length is 1
154 |             contents = a
155 |           else
156 |             if a is args[0]
157 |               idclass = a
158 |             else
159 |               contents = a
160 | 
161 |     __ck.render_tag(name, idclass, attrs, contents)
162 | 
163 |   yield = (f) ->
164 |     temp_buffer = []
165 |     old_buffer = __ck.buffer
166 |     __ck.buffer = temp_buffer
167 |     f()
168 |     __ck.buffer = old_buffer
169 |     temp_buffer.join ''
170 | 
171 |   h = (txt) ->
172 |     String(txt).replace(/&/g, '&amp;')
173 |       .replace(/</g, '&lt;')
174 |       .replace(/>/g, '&gt;')
175 |       .replace(/"/g, '&quot;')
176 |     
177 |   doctype = (type = 'default') ->
178 |     text __ck.doctypes[type]
179 |     text '\n' if data.format
180 |     
181 |   text = (txt) ->
182 |     __ck.buffer.push String(txt)
183 |     null
184 | 
185 |   comment = (cmt) ->
186 |     text "<!--#{cmt}-->"
187 |     text '\n' if data.format
188 |   
189 |   coffeescript = (param) ->
190 |     switch typeof param

coffeescript -> alert 'hi' becomes: 191 | <script>;(function () {return alert('hi');})();</script>

      when 'function'
192 |         script "#{__ck.coffeescript_helpers}(#{param}).call(this);"

coffeescript "alert 'hi'" becomes: 193 | <script type="text/coffeescript">alert 'hi'</script>

      when 'string'
194 |         script type: 'text/coffeescript', -> param

coffeescript src: 'script.coffee' becomes: 195 | <script type="text/coffeescript" src="script.coffee"></script>

      when 'object'
196 |         param.type = 'text/coffeescript'
197 |         script param
198 |   

Conditional IE comments.

  ie = (condition, contents) ->
199 |     __ck.indent()
200 |     
201 |     text "<!--[if #{condition}]>"
202 |     __ck.render_contents(contents)
203 |     text "<![endif]-->"
204 |     text '\n' if data.format
205 | 
206 |   null

Stringify the skeleton and unwrap it from its enclosing function(){}, then 207 | add the CoffeeScript helpers.

skeleton = String(skeleton)
208 |   .replace(/function\s*\(.*\)\s*\{/, '')
209 |   .replace(/return null;\s*\}$/, '')
210 | 
211 | skeleton = coffeescript_helpers + skeleton

Compiles a template into a standalone JavaScript function.

coffeekup.compile = (template, options = {}) ->

The template can be provided as either a function or a CoffeeScript string 212 | (in the latter case, the CoffeeScript compiler must be available).

  if typeof template is 'function' then template = String(template)
213 |   else if typeof template is 'string' and coffee?
214 |     template = coffee.compile template, bare: yes
215 |     template = "function(){#{template}}"

If an object hardcode is provided, insert the stringified value 216 | of each variable directly in the function body. This is a less flexible but 217 | faster alternative to the standard method of using with (see below).

  hardcoded_locals = ''
218 |   
219 |   if options.hardcode
220 |     for k, v of options.hardcode
221 |       if typeof v is 'function'

Make sure these functions have access to data as @/this.

        hardcoded_locals += "var #{k} = function(){return (#{v}).apply(data, arguments);};"
222 |       else hardcoded_locals += "var #{k} = #{JSON.stringify v};"

Add a function for each tag this template references. We don't want to have 223 | all hundred-odd tags wasting space in the compiled function.

  tag_functions = ''
224 |   tags_used = []
225 |   
226 |   for t in coffeekup.tags
227 |     if template.indexOf(t) > -1 or hardcoded_locals.indexOf(t) > -1
228 |       tags_used.push t
229 |       
230 |   tag_functions += "var #{tags_used.join ','};"
231 |   for t in tags_used
232 |     tag_functions += "#{t} = function(){return __ck.tag('#{t}', arguments);};"

Main function assembly.

  code = tag_functions + hardcoded_locals + skeleton
233 | 
234 |   code += "__ck.doctypes = #{JSON.stringify coffeekup.doctypes};"
235 |   code += "__ck.coffeescript_helpers = #{JSON.stringify coffeescript_helpers};"
236 |   code += "__ck.self_closing = #{JSON.stringify coffeekup.self_closing};"

If locals is set, wrap the template inside a with block. This is the 237 | most flexible but slower approach to specifying local variables.

  code += 'with(data.locals){' if options.locals
238 |   code += "(#{template}).call(data);"
239 |   code += '}' if options.locals
240 |   code += "return __ck.buffer.join('');"
241 |   
242 |   new Function('data', code)
243 | 
244 | cache = {}

Template in, HTML out. Accepts functions or strings as does coffeekup.compile.

245 | 246 |

Accepts an option cache, by default false. If set to false templates will 247 | be recompiled each time.

248 | 249 |

options is just a convenience parameter to pass options separately from the 250 | data, but the two will be merged and passed down to the compiler (which uses 251 | locals and hardcode), and the template (which understands locals, format 252 | and autoescape).

coffeekup.render = (template, data = {}, options = {}) ->
253 |   data[k] = v for k, v of options
254 |   data.cache ?= off
255 | 
256 |   if data.cache and cache[template]? then tpl = cache[template]
257 |   else if data.cache then tpl = cache[template] = coffeekup.compile(template, data)
258 |   else tpl = coffeekup.compile(template, data)
259 |   tpl(data)
260 | 
261 | unless window?
262 |   coffeekup.adapters =

Legacy adapters for when CoffeeKup expected data in the context attribute.

    simple: coffeekup.render
263 |     meryl: coffeekup.render
264 |     
265 |     express:
266 |       TemplateError: class extends Error
267 |         constructor: (@message) ->
268 |           Error.call this, @message
269 |           Error.captureStackTrace this, arguments.callee
270 |         name: 'TemplateError'
271 |         
272 |       compile: (template, data) -> 

Allows partial 'foo' instead of text @partial 'foo'.

        data.hardcode ?= {}
273 |         data.hardcode.partial = ->
274 |           text @partial.apply @, arguments
275 |         
276 |         TemplateError = @TemplateError
277 |         try tpl = coffeekup.compile(template, data)
278 |         catch e then throw new TemplateError "Error compiling #{data.filename}: #{e.message}"
279 |         
280 |         return ->
281 |           try tpl arguments...
282 |           catch e then throw new TemplateError "Error rendering #{data.filename}: #{e.message}"
283 | 
284 | 
--------------------------------------------------------------------------------