Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial import.

  • Loading branch information...
commit 75ee63ad23c68dd2866dfa1dfa70f09cb3e02f8f 0 parents
@mauricemach authored
70 Cakefile
@@ -0,0 +1,70 @@
+exec = require('child_process').exec
+coffeekup = require 'coffeekup'
+render = coffeekup.render
+
+task 'build', ->
+ exec 'coffee -c lib/coffeekup.coffee', (err) ->
+ puts err if err
+ exec 'cp lib/coffeekup.js examples/browser', (err) ->
+ puts err if err
+
+task 'test', ->
+ [tests, passed, failed, errors] = [[], [], [], []]
+
+ test = (name, code) ->
+ tests.push name
+ print "Testing \"#{name}\"... "
+ try
+ if code()
+ passed.push name
+ puts "[OK]"
+ else
+ failed.push name
+ puts "[Failed]"
+ catch ex
+ errors.push name
+ puts "[Error] (#{ex.message})"
+
+ test 'Literal text', ->
+ 'Just text' is render ->
+ text 'Just text'
+
+ test 'Default DOCTYPE', ->
+ '<!DOCTYPE html>' is render ->
+ doctype()
+
+ test 'DOCTYPE', ->
+ '<?xml version="1.0" encoding="utf-8" ?>' is render ->
+ doctype 'xml'
+
+ test 'Self-closing tags', ->
+ '<br />' is (render -> br()) and
+ '<img src="icon.png" alt="Icon" />' is render -> img src: 'icon.png', alt: 'Icon'
+
+ test 'Normal tags', ->
+ '<h1>hi</h1>' is render ->
+ h1 'hi'
+
+ test 'Attributes', ->
+ '<a href="/" title="Home"></a>' is render ->
+ a href: '/', title: 'Home'
+
+ test 'HereDocs', ->
+ "<script>$(document).ready(function(){\n alert('test');\n});</script>" is render ->
+ script """
+ $(document).ready(function(){
+ alert('test');
+ });
+ """
+
+ test 'CoffeeScript', ->
+ "<script>$(document).ready(function() {\n return alert('hi!');\n });</script>" is render ->
+ coffeescript ->
+ $(document).ready ->
+ alert 'hi!'
+
+ test 'Comments', ->
+ '<!--Comment-->' is render ->
+ comment 'Comment'
+
+ puts "\nTests: #{tests.length} | Passed: #{passed.length} | Failed: #{failed.length} | Errors: #{errors.length}"
80 README
@@ -0,0 +1,80 @@
+# CoffeeKup
+Caffeinated Templates
+
+In (shamelessly late) celebration of [whyday](http://whyday.org/), here goes a little experiment in revisiting Markaby's concept, this time with the fine flavour of fresh [CoffeeScript](http://coffeescript.org):
+
+ doctype 5
+ html ->
+ head ->
+ meta charset: 'utf-8'
+ title "#{@title} | My awesome website"
+ meta(name: 'description', content: @description) if @description?
+ link rel: 'stylesheet', href: '/stylesheets/app.css'
+ style '''
+ body {font-family: sans-serif}
+ header, nav, section, footer {display: block}
+ '''
+ script src: "/javascripts/jquery.js"
+ coffeescript ->
+ $(document).ready ->
+ alert 'Alerts are so annoying...'
+ body ->
+ header ->
+ h1 @title
+ nav ->
+ ul ->
+ (li -> a href: '/', -> 'Home') unless @path is '/'
+ li -> a href: '/chunky', -> 'Chunky'
+ li -> a href: '/bacon', -> 'Bacon!'
+ section ->
+ h2 "Let's count to 10:"
+ p i for i in [1..10]
+ footer ->
+ p 'Bye!'
+
+## _Why?
+
+* Profit from a hell of a terse and expressive language in your templates.
+* Keep the dignity of templates when embedding them in your app.
+* Feels like an extensible language, as there's no syntactic distiction between your "helpers" and the original "vocabulary" of elements.
+* Use it from coffeescript or javascript apps, in node.js or in the browser.
+* It's just coffeescript! It doesn't need separate syntax highlighting, syntax checking, etc.
+
+## Installing
+
+Just grab [node.js](http://nodejs.org/#download) and [npm](http://github.com/isaacs/npm) and you're set:
+
+ [sudo] npm install coffeekup
+
+## Using
+
+ coffeekup = require 'coffeekup'
+ coffeekup.render "h1 'You can feed me raw strings!'"
+ coffeekup.render -> h1 "Or live code. I'm not too picky."
+
+With [express](http://expressjs.com):
+
+ app.register '.coffee', require('coffeekup')
+ app.set 'view engine', 'coffee'
+ app.get '/', (req, res) ->
+ # Will render views/index.coffee:
+ res.render 'index', context: {foo: 'bar'}
+
+In the browser (see /examples dir):
+
+ <script src="/coffee-script.js"></script>
+ <script src="/coffeekup.js"></script>
+ <script type="text/coffeescript">
+ template = -> h1 "Hello #{@world}"
+ alert(CoffeeKup.render template, context: {world: 'mars'})
+ </script>
+
+Command-line:
+
+ coffeekup FILE [> OUTPUT]
+
+Please note that even though all examples were written in coffeescript, their javascript counterparts will also work just fine.
+
+## Caveats
+
+* Like Markaby, not the fastest horse in the stable. Run benchmark.coffee for details. In the context of node's screaming performance though, maybe it won't matter as much as it did for Markaby in the MRI. Your feedback is appreciated.
3  TODO
@@ -0,0 +1,3 @@
+- IE conditionals
+- Decent error reporting
+- Improve test coverage
77 benchmark.coffee
@@ -0,0 +1,77 @@
+#!/usr/bin/env coffee
+
+jade = require 'jade'
+coffeekup = require 'coffeekup'
+
+jade_template = '''
+!!! 5
+html(lang="en")
+ head
+ title= pageTitle
+ :javascript
+ | if (foo) {
+ | bar()
+ | }
+ body
+ h1 Jade - node template engine
+ #container
+ - if (youAreUsingJade)
+ p You are amazing
+ - else
+ p Get on it!
+'''
+
+coffeekup_template = ->
+ doctype 5
+ html lang: 'en', ->
+ head ->
+ title @title
+ script '''
+ if (foo) {
+ bar()
+ }
+ '''
+ body ->
+ h1 'Jade - node template engine'
+ div id: 'container', ->
+ if @you_are_using_coffeekup
+ p 'You are amazing'
+ else
+ p 'Get on it!'
+
+#coffeekup_template = """
+# doctype 5
+# html lang: 'en', ->
+# head ->
+# title @title
+# script '''
+# if (foo) {
+# bar()
+# }
+# '''
+# body ->
+# h1 'Jade - node template engine'
+# div id: 'container', ->
+# if @you_are_using_coffeekup
+# p 'You are amazing'
+# else
+# p 'Get on it!'
+#"""
+
+benchmark = (title, code) ->
+ start = new Date
+ for i in [1..5000]
+ code()
+ puts "#{title}: #{new Date - start} ms"
+
+benchmark 'Jade', ->
+ jade.render jade_template, {locals: {pageTitle: 'pageTitle', youAreUsingJade: yes}}
+
+benchmark 'CoffeeKup', ->
+ coffeekup.render coffeekup_template, {context: {title: 'title', you_are_using_coffeekup: yes}}
+
+benchmark 'Jade (cached)', ->
+ jade.render jade_template, {locals: {pageTitle: 'pageTitle', youAreUsingJade: yes}, cache: yes, filename: 'aaa'}
+
+benchmark 'CoffeeKup (cached)', ->
+ coffeekup.render coffeekup_template, {context: {title: 'title', you_are_using_coffeekup: yes}, cache: yes}
27 bin/coffeekup.coffee
@@ -0,0 +1,27 @@
+#!/usr/bin/env coffee
+
+coffeekup = require 'coffeekup'
+fs = require 'fs'
+
+usage = '''
+ Usage:
+ coffeekup INPUT_FILE
+
+ Options:
+ -v, --version
+ -h, --help
+'''
+
+args = process.argv
+
+if args.length is 0
+ puts usage
+else
+ input = args[0]
+ if input in ['-v', '--version']
+ puts coffeekup.version
+ else if input in ['-h', '--help']
+ puts usage
+ else
+ code = fs.readFileSync input, 'utf8'
+ puts coffeekup.render(code)
8 examples/browser/coffee-script.js
8 additions, 0 deletions not shown
162 examples/browser/coffeekup.js
@@ -0,0 +1,162 @@
+(function() {
+ var CoffeeKup, browser, coffee, root;
+ var __hasProp = Object.prototype.hasOwnProperty, __slice = Array.prototype.slice;
+ browser = (typeof window !== "undefined" && window !== null);
+ root = browser ? window : exports;
+ coffee = browser ? CoffeeScript : require('coffee-script');
+ CoffeeKup = function() {
+ var _a;
+ _a = this;
+ this.comment = function(){ return CoffeeKup.prototype.comment.apply(_a, arguments); };
+ this.doctype = function(){ return CoffeeKup.prototype.doctype.apply(_a, arguments); };
+ this.tag = function(){ return CoffeeKup.prototype.tag.apply(_a, arguments); };
+ this.text = function(){ return CoffeeKup.prototype.text.apply(_a, arguments); };
+ return this;
+ };
+ CoffeeKup.version = '0.1.0';
+ CoffeeKup.doctypes = {
+ '5': '<!DOCTYPE html>',
+ 'xml': '<?xml version="1.0" encoding="utf-8" ?>',
+ 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
+ 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
+ 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
+ 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
+ '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
+ 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
+ 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
+ };
+ CoffeeKup.tags = 'a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|keygen|kbd|label|legend|li|link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|small|source|span|strike|strong|style|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|u|ul|var|video|xmp'.split('|');
+ CoffeeKup.self_closing = 'area|base|basefont|br|hr|img|input|link|meta'.split('|');
+ CoffeeKup.render = function(template, options) {
+ var _a, _b, _c, _d, _e, context, fn, k, locals, str, v;
+ str = String(template);
+ if (((typeof options === "undefined" || options === null) ? undefined : options.cache) === true) {
+ this.inst = (typeof this.inst !== "undefined" && this.inst !== null) ? this.inst : new CoffeeKup();
+ if (typeof template === 'function') {
+ this.js = (typeof this.js !== "undefined" && this.js !== null) ? this.js : (String(template) + '();');
+ } else {
+ this.js = (typeof this.js !== "undefined" && this.js !== null) ? this.js : coffee.compile(String(template), {
+ 'noWrap': 'noWrap'
+ });
+ }
+ } else {
+ this.inst = new CoffeeKup();
+ if (typeof template === 'function') {
+ this.js = '(' + String(template) + ')();';
+ } else {
+ this.js = coffee.compile(String(template), {
+ 'noWrap': 'noWrap'
+ });
+ }
+ }
+ context = ((typeof options === "undefined" || options === null) ? undefined : options.context) || {};
+ locals = ((typeof options === "undefined" || options === null) ? undefined : options.locals) || {};
+ _a = context;
+ for (k in _a) {
+ if (!__hasProp.call(_a, k)) continue;
+ v = _a[k];
+ this.inst[k] = v;
+ }
+ if (typeof (_b = locals.body) !== "undefined" && _b !== null) {
+ this.inst.body = locals.body;
+ delete locals.body;
+ }
+ locals.doctype = this.inst.doctype;
+ locals.comment = this.inst.comment;
+ locals.text = this.inst.text;
+ locals.tag = this.inst.tag;
+ locals.coffeescript = this.inst.coffeescript;
+ _d = this.tags;
+ for (_c = 0, _e = _d.length; _c < _e; _c++) {
+ (function() {
+ var t = _d[_c];
+ return (locals[t] = function() {
+ var opts;
+ opts = __slice.call(arguments, 0);
+ return this.tag(t, opts);
+ });
+ })();
+ }
+ this.inst.buffer = [];
+ fn = Function('locals', "with(locals) {" + (this.js) + "}");
+ fn.call(this.inst, locals);
+ if (this.inst.buffer[this.inst.buffer.length - 1] === "\n") {
+ this.inst.buffer.pop();
+ }
+ return this.inst.buffer.join('');
+ };
+ CoffeeKup.prototype.text = function(txt) {
+ this.buffer.push(txt);
+ return null;
+ };
+ CoffeeKup.prototype.tag = function(name, opts) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, o, result;
+ this.text("<" + (name));
+ _b = opts;
+ for (_a = 0, _c = _b.length; _a < _c; _a++) {
+ o = _b[_a];
+ if (typeof o === 'object') {
+ this.text(this.render_attrs(o));
+ }
+ }
+ if ((function(){ for (var _e=0, _f=(_d = CoffeeKup.self_closing).length; _e<_f; _e++) { if (_d[_e] === name) return true; } return false; }).call(this)) {
+ this.text(' />');
+ } else {
+ this.text('>');
+ _h = opts;
+ for (_g = 0, _i = _h.length; _g < _i; _g++) {
+ o = _h[_g];
+ if ((_j = typeof o) === 'function') {
+ result = o.call(this);
+ if (typeof result !== "undefined" && result !== null) {
+ this.text(result.toString());
+ }
+ } else if (_j === 'string') {
+ this.text(o);
+ }
+ }
+ this.text("</" + (name) + ">");
+ }
+ if (!(this.compact)) {
+ this.text("\n");
+ }
+ return null;
+ };
+ CoffeeKup.prototype.render_attrs = function(obj) {
+ var _a, k, str, v;
+ str = '';
+ _a = obj;
+ for (k in _a) {
+ if (!__hasProp.call(_a, k)) continue;
+ v = _a[k];
+ str += (" " + (k) + "=\"" + (v) + "\"");
+ }
+ return str;
+ };
+ CoffeeKup.prototype.doctype = function(type) {
+ type = (typeof type !== "undefined" && type !== null) ? type : 5;
+ this.text(CoffeeKup.doctypes[type]);
+ if (!(this.compact)) {
+ return this.text("\n");
+ }
+ };
+ CoffeeKup.prototype.comment = function(text) {
+ return this.text("<!--" + (text) + "-->");
+ };
+ CoffeeKup.prototype.coffeescript = function(func) {
+ return this.script(function() {
+ var code;
+ code = String(func);
+ code = code.replace(/^function \(\) \{\n( )*return /, '');
+ code = code.replace(/\n( )*\}$/, '');
+ return this.text(code);
+ });
+ };
+ root.CoffeeKup = CoffeeKup;
+ root.version = CoffeeKup.version;
+ root.render = function(template, options) {
+ if (!(browser)) {
+ return root.CoffeeKup.render(template, options);
+ }
+ };
+})();
59 examples/browser/index.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>CoffeeKup in the browser</title>
+
+ <script src="coffee-script.js"></script>
+ <script src="coffeekup.js"></script>
+ <!-- jQuery is NOT required, just used here for convenience -->
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+
+ <script>
+ var render = function(template){
+ $('body').append(CoffeeKup.render(template));
+ };
+ </script>
+
+ <script>
+ $(document).ready(function(){
+ render(
+ "h1 'Works with good ol\\' JavaScript too, if you\\'re masochist enough'\n\n" +
+ "table ->\n" +
+ " tr ->\n" +
+ " th 'Language used'\n" +
+ " th 'Template provided as'\n" +
+ " tr ->\n" +
+ " td -> a href: 'http://coffeescript.org', -> 'CoffeeScript'\n" +
+ " td 'Raw string'");
+ });
+ </script>
+
+ <script type="text/coffeescript">
+ $(document).ready ->
+ render """
+ h1 'Template as raw string'
+
+ table ->
+ tr ->
+ th 'Language'
+ th 'Template provided as'
+ tr ->
+ td -> a href: 'http://coffeescript.org', -> 'CoffeeScript'
+ td 'Raw string'
+ """
+
+ render ->
+ h1 'Template as literal code'
+
+ table ->
+ tr ->
+ th 'Language'
+ th 'Template provided as'
+ tr ->
+ td -> a href: 'http://coffeescript.org', -> 'CoffeeScript'
+ td 'Function'
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
16 examples/express/app.coffee
@@ -0,0 +1,16 @@
+app = require('express').createServer()
+
+app.register '.coffee', require('coffeekup')
+app.set 'view engine', 'coffee'
+
+app.get '/', (req, res) ->
+ res.render 'index'
+
+app.get '/login', (req, res) ->
+ res.render 'login', locals: {a_local_var: 'local'}, context: {a_context_var: 'context'}
+
+app.get '/embedded', (req, res) ->
+ res.send require('coffeekup').render ->
+ h1 'This template is embedded right within the express app.'
+
+app.listen 8000
10 examples/express/views/index.coffee
@@ -0,0 +1,10 @@
+@title = 'Chunky Bacon!'
+@canonical = 'http://chunky.bacon'
+
+h1 @title
+
+p 'This is the home page.'
+
+p "Let's count to 10: "
+
+p "#{i}..." for i in [1..10]
43 examples/express/views/layout.coffee
@@ -0,0 +1,43 @@
+doctype 5
+html ->
+ head ->
+ meta charset: 'utf-8'
+
+ title "#{@title} | My Site" if @title?
+ meta(name: 'description', content: @description) if @description?
+ link(rel: 'canonical', href: @canonical) if @canonical?
+
+ link rel: 'icon', href: '/favicon.png'
+ link rel: 'stylesheet', href: '/app.css'
+
+ script src: 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js'
+ script src: '/app.js'
+
+ coffeescript ->
+ $(document).ready ->
+ alert 'hi!'
+
+ style '''
+ header, nav, section, article, aside, footer {display: block}
+ nav li {display: inline}
+ nav.sub {float: right}
+ #content {margin-left: 120px}
+ '''
+ body ->
+ header ->
+ a href: '/', title: 'Home', -> 'Home'
+
+ nav ->
+ ul ->
+ for item in ['About', 'Pricing', 'Contact']
+ li -> a href: "/#{item.toLowerCase()}", title: item, -> item
+
+ li -> a href: '/about', title: 'About', -> 'About'
+ li -> a href: '/pricing', title: 'Pricing', -> 'Pricing'
+ li -> a href: '/contact', title: 'Contact Us', -> 'Contact Us'
+
+ div id: 'content', ->
+ @body
+
+ footer ->
+ p -> a href: '/privacy', -> 'Privacy Policy'
15 examples/express/views/login.coffee
@@ -0,0 +1,15 @@
+@title = 'Log In'
+
+h1 @title
+
+p "A local var: #{a_local_var}"
+p "A context var: #{@a_context_var}"
+
+form action: '/', method: 'post', ->
+ div class: 'field', ->
+ label for: 'username', -> 'Username: '
+ input id: 'username', name: 'username'
+
+ div class: 'field', ->
+ label for: 'password', -> 'Password: '
+ input id: 'password', name: 'password'
110 lib/coffeekup.coffee
@@ -0,0 +1,110 @@
+browser = window?
+root = if browser then window else exports
+coffee = if browser then CoffeeScript else require 'coffee-script'
+
+class CoffeeKup
+ @version: '0.1.0'
+
+ @doctypes: {
+ '5': '<!DOCTYPE html>'
+ 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
+ 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
+ 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
+ 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
+ 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
+ '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
+ 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
+ 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
+ }
+
+ @tags: 'a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|keygen|kbd|label|legend|li|link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|small|source|span|strike|strong|style|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|u|ul|var|video|xmp'.split '|'
+
+ @self_closing: 'area|base|basefont|br|hr|img|input|link|meta'.split '|'
+
+ @render: (template, options) ->
+ str = String(template)
+
+ if options?.cache is on
+ @inst ?= new CoffeeKup
+ if typeof template is 'function'
+ @js ?= String(template) + '();'
+ else
+ @js ?= coffee.compile String(template), {'noWrap'}
+ else
+ @inst = new CoffeeKup
+ if typeof template is 'function'
+ @js = '(' + String(template) + ')();'
+ else
+ @js = coffee.compile String(template), {'noWrap'}
+
+ context = options?.context or {}
+ locals = options?.locals or {}
+
+ for k, v of context
+ @inst[k] = v
+
+ if locals.body?
+ @inst.body = locals.body
+ delete locals.body
+
+ locals.doctype = @inst.doctype
+ locals.comment = @inst.comment
+ locals.text = @inst.text
+ locals.tag = @inst.tag
+ locals.coffeescript = @inst.coffeescript
+ for t in @tags
+ locals[t] = (opts...) -> @tag t, opts
+
+ @inst.buffer = []
+ fn = Function('locals', "with(locals) {#{@js}}")
+ fn.call @inst, locals
+ @inst.buffer.pop() if @inst.buffer[@inst.buffer.length-1] is "\n"
+ @inst.buffer.join ''
+
+ text: (txt) => @buffer.push txt; null
+
+ tag: (name, opts) =>
+ @text "<#{name}"
+ for o in opts
+ @text @render_attrs(o) if typeof o is 'object'
+
+ if name in CoffeeKup.self_closing
+ @text ' />'
+ else
+ @text '>'
+ for o in opts
+ switch typeof o
+ when 'function'
+ result = o.call(@)
+ @text result.toString() if result?
+ when 'string' then @text o
+ @text "</#{name}>"
+
+ @text "\n" unless @compact
+
+ null
+
+ render_attrs: (obj) ->
+ str = ''
+ for k, v of obj
+ str += " #{k}=\"#{v}\""
+ str
+
+ doctype: (type) =>
+ type ?= 5
+ @text CoffeeKup.doctypes[type]
+ @text "\n" unless @compact
+
+ comment: (text) =>
+ @text "<!--#{text}-->"
+
+ coffeescript: (func) ->
+ @script ->
+ code = String(func)
+ code = code.replace /^function \(\) \{\n( )*return /, ''
+ code = code.replace /\n( )*\}$/, ''
+ @text code
+
+root.CoffeeKup = CoffeeKup
+root.version = CoffeeKup.version
+root.render = (template, options) -> root.CoffeeKup.render(template, options) unless browser
162 lib/coffeekup.js
@@ -0,0 +1,162 @@
+(function() {
+ var CoffeeKup, browser, coffee, root;
+ var __hasProp = Object.prototype.hasOwnProperty, __slice = Array.prototype.slice;
+ browser = (typeof window !== "undefined" && window !== null);
+ root = browser ? window : exports;
+ coffee = browser ? CoffeeScript : require('coffee-script');
+ CoffeeKup = function() {
+ var _a;
+ _a = this;
+ this.comment = function(){ return CoffeeKup.prototype.comment.apply(_a, arguments); };
+ this.doctype = function(){ return CoffeeKup.prototype.doctype.apply(_a, arguments); };
+ this.tag = function(){ return CoffeeKup.prototype.tag.apply(_a, arguments); };
+ this.text = function(){ return CoffeeKup.prototype.text.apply(_a, arguments); };
+ return this;
+ };
+ CoffeeKup.version = '0.1.0';
+ CoffeeKup.doctypes = {
+ '5': '<!DOCTYPE html>',
+ 'xml': '<?xml version="1.0" encoding="utf-8" ?>',
+ 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
+ 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
+ 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
+ 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
+ '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
+ 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
+ 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
+ };
+ CoffeeKup.tags = 'a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|keygen|kbd|label|legend|li|link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|small|source|span|strike|strong|style|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|u|ul|var|video|xmp'.split('|');
+ CoffeeKup.self_closing = 'area|base|basefont|br|hr|img|input|link|meta'.split('|');
+ CoffeeKup.render = function(template, options) {
+ var _a, _b, _c, _d, _e, context, fn, k, locals, str, v;
+ str = String(template);
+ if (((typeof options === "undefined" || options === null) ? undefined : options.cache) === true) {
+ this.inst = (typeof this.inst !== "undefined" && this.inst !== null) ? this.inst : new CoffeeKup();
+ if (typeof template === 'function') {
+ this.js = (typeof this.js !== "undefined" && this.js !== null) ? this.js : (String(template) + '();');
+ } else {
+ this.js = (typeof this.js !== "undefined" && this.js !== null) ? this.js : coffee.compile(String(template), {
+ 'noWrap': 'noWrap'
+ });
+ }
+ } else {
+ this.inst = new CoffeeKup();
+ if (typeof template === 'function') {
+ this.js = '(' + String(template) + ')();';
+ } else {
+ this.js = coffee.compile(String(template), {
+ 'noWrap': 'noWrap'
+ });
+ }
+ }
+ context = ((typeof options === "undefined" || options === null) ? undefined : options.context) || {};
+ locals = ((typeof options === "undefined" || options === null) ? undefined : options.locals) || {};
+ _a = context;
+ for (k in _a) {
+ if (!__hasProp.call(_a, k)) continue;
+ v = _a[k];
+ this.inst[k] = v;
+ }
+ if (typeof (_b = locals.body) !== "undefined" && _b !== null) {
+ this.inst.body = locals.body;
+ delete locals.body;
+ }
+ locals.doctype = this.inst.doctype;
+ locals.comment = this.inst.comment;
+ locals.text = this.inst.text;
+ locals.tag = this.inst.tag;
+ locals.coffeescript = this.inst.coffeescript;
+ _d = this.tags;
+ for (_c = 0, _e = _d.length; _c < _e; _c++) {
+ (function() {
+ var t = _d[_c];
+ return (locals[t] = function() {
+ var opts;
+ opts = __slice.call(arguments, 0);
+ return this.tag(t, opts);
+ });
+ })();
+ }
+ this.inst.buffer = [];
+ fn = Function('locals', "with(locals) {" + (this.js) + "}");
+ fn.call(this.inst, locals);
+ if (this.inst.buffer[this.inst.buffer.length - 1] === "\n") {
+ this.inst.buffer.pop();
+ }
+ return this.inst.buffer.join('');
+ };
+ CoffeeKup.prototype.text = function(txt) {
+ this.buffer.push(txt);
+ return null;
+ };
+ CoffeeKup.prototype.tag = function(name, opts) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, o, result;
+ this.text("<" + (name));
+ _b = opts;
+ for (_a = 0, _c = _b.length; _a < _c; _a++) {
+ o = _b[_a];
+ if (typeof o === 'object') {
+ this.text(this.render_attrs(o));
+ }
+ }
+ if ((function(){ for (var _e=0, _f=(_d = CoffeeKup.self_closing).length; _e<_f; _e++) { if (_d[_e] === name) return true; } return false; }).call(this)) {
+ this.text(' />');
+ } else {
+ this.text('>');
+ _h = opts;
+ for (_g = 0, _i = _h.length; _g < _i; _g++) {
+ o = _h[_g];
+ if ((_j = typeof o) === 'function') {
+ result = o.call(this);
+ if (typeof result !== "undefined" && result !== null) {
+ this.text(result.toString());
+ }
+ } else if (_j === 'string') {
+ this.text(o);
+ }
+ }
+ this.text("</" + (name) + ">");
+ }
+ if (!(this.compact)) {
+ this.text("\n");
+ }
+ return null;
+ };
+ CoffeeKup.prototype.render_attrs = function(obj) {
+ var _a, k, str, v;
+ str = '';
+ _a = obj;
+ for (k in _a) {
+ if (!__hasProp.call(_a, k)) continue;
+ v = _a[k];
+ str += (" " + (k) + "=\"" + (v) + "\"");
+ }
+ return str;
+ };
+ CoffeeKup.prototype.doctype = function(type) {
+ type = (typeof type !== "undefined" && type !== null) ? type : 5;
+ this.text(CoffeeKup.doctypes[type]);
+ if (!(this.compact)) {
+ return this.text("\n");
+ }
+ };
+ CoffeeKup.prototype.comment = function(text) {
+ return this.text("<!--" + (text) + "-->");
+ };
+ CoffeeKup.prototype.coffeescript = function(func) {
+ return this.script(function() {
+ var code;
+ code = String(func);
+ code = code.replace(/^function \(\) \{\n( )*return /, '');
+ code = code.replace(/\n( )*\}$/, '');
+ return this.text(code);
+ });
+ };
+ root.CoffeeKup = CoffeeKup;
+ root.version = CoffeeKup.version;
+ root.render = function(template, options) {
+ if (!(browser)) {
+ return root.CoffeeKup.render(template, options);
+ }
+ };
+})();
1  lib/index.js
@@ -0,0 +1 @@
+module.exports = require('./coffeekup')
14 package.json
@@ -0,0 +1,14 @@
+{
+ "name": "coffeekup",
+ "description": "Markup expressed as CoffeeScript.",
+ "version": "0.1.0",
+ "author": "Maurice Machado <maurice@bitbending.com>",
+ "contributors": [
+ {"name": "Maurice Machado", "email": "maurice@bitbending.com"}
+ ],
+ "dependencies": {"coffee-script": ">=0.9.2"},
+ "keywords": ["template", "view", "coffeescript"],
+ "bin": {"coffeekup": "./bin/coffeekup.coffee"},
+ "directories": {"lib": "./lib"},
+ "engines": {"node": ">=0.2.0"}
+}
Please sign in to comment.
Something went wrong with that request. Please try again.