├── lib ├── README.md ├── coffee-script │ ├── index.js │ ├── helpers.js │ ├── browser.js │ ├── cake.js │ ├── scope.js │ ├── optparse.js │ ├── coffee-script.js │ ├── repl.js │ ├── rewriter.js │ └── command.js ├── consts.js ├── pool.js ├── utils.js ├── command_line.js ├── errorHandler.js └── engine.js ├── .yarnrc ├── .gitignore ├── test ├── cases │ ├── indent_attack │ │ ├── vars.js │ │ ├── output.toffee │ │ └── input.toffee │ ├── plaintext │ │ ├── input.toffee │ │ └── output.toffee │ ├── include_techniques │ │ ├── vars.js │ │ ├── message.toffee │ │ ├── output.toffee │ │ └── input.toffee │ ├── hello_world │ │ ├── output.toffee │ │ ├── input.toffee │ │ ├── vars.js │ │ └── temp.toffee │ ├── lambda_fns │ │ ├── output.toffee │ │ └── input.toffee │ ├── multiline_interpolation │ │ ├── foo.toffee │ │ ├── output.toffee │ │ └── input.toffee │ ├── render_no_args │ │ ├── input.toffee │ │ └── output.toffee │ ├── comments │ │ ├── vars.js │ │ ├── output.toffee │ │ └── input.toffee │ ├── post_process │ │ ├── signature.toffee │ │ ├── output.toffee │ │ ├── vars.coffee │ │ ├── buncha_junk.toffee │ │ └── input.toffee │ ├── include_order │ │ ├── vars.js │ │ ├── child.toffee │ │ ├── output.toffee │ │ └── input.toffee │ ├── include_recursion │ │ ├── vars.js │ │ ├── output.toffee │ │ └── input.toffee │ ├── passback │ │ ├── vars.js │ │ ├── const3.toffee │ │ ├── const4_sub.toffee │ │ ├── const5_sub.toffee │ │ ├── const4.toffee │ │ ├── const2.toffee │ │ ├── const1.toffee │ │ ├── const5.toffee │ │ ├── output.toffee │ │ └── input.toffee │ ├── custom_escape │ │ ├── vars.coffee │ │ ├── output.toffee │ │ └── input.toffee │ ├── junk │ │ ├── output.toffee │ │ └── input.toffee │ ├── snippets │ │ ├── foo │ │ │ ├── bar │ │ │ │ └── body.toffee │ │ │ └── message.toffee │ │ ├── vars.js │ │ ├── output.toffee │ │ └── input.toffee │ ├── bad_unicode │ │ ├── output.toffee │ │ └── input.toffee │ ├── special_cases │ │ ├── output.toffee │ │ └── input.toffee │ ├── eco_compare │ │ ├── output.toffee │ │ └── input.toffee │ ├── json_formatting │ │ ├── input.toffee │ │ └── output.toffee │ ├── escape │ │ ├── input.toffee │ │ └── output.toffee │ └── big_file │ │ ├── output.toffee │ │ └── input.toffee ├── express4 │ ├── views │ │ ├── subdir2 │ │ │ ├── foo │ │ │ │ └── goodbye.toffee │ │ │ └── goodbye_world.toffee │ │ └── subdir1 │ │ │ └── hello_world.toffee │ ├── public │ │ ├── stylesheets │ │ │ └── style.css │ │ └── javascripts │ │ │ └── toffee.js │ └── app.coffee ├── express4_error_handling │ ├── views │ │ ├── test_bad_str_interpolate.toffee │ │ ├── test_bad_toffee_syntax.toffee │ │ ├── test_bad_runtime.toffee │ │ ├── index.toffee │ │ └── test_bad_coffee_syntax.toffee │ ├── public │ │ ├── stylesheets │ │ │ └── style.css │ │ └── javascripts │ │ │ └── toffee.js │ ├── package.json │ ├── routes │ │ └── index.js │ └── app.coffee ├── generate_express_test.coffee └── run_cases.iced ├── bin └── toffee ├── src ├── consts.coffee ├── pool.coffee ├── utils.coffee ├── toffee.jison ├── command_line.coffee ├── errorHandler.coffee └── engine.coffee ├── LICENSE ├── package.json ├── Cakefile ├── index.js ├── index.coffee ├── toffee.js └── README.md /lib/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | save-prefix "" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.DS_Store -------------------------------------------------------------------------------- /test/cases/indent_attack/vars.js: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /test/cases/plaintext/input.toffee: -------------------------------------------------------------------------------- 1 | Hi there. -------------------------------------------------------------------------------- /test/cases/plaintext/output.toffee: -------------------------------------------------------------------------------- 1 | Hi there. -------------------------------------------------------------------------------- /test/cases/include_techniques/vars.js: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /test/cases/hello_world/output.toffee: -------------------------------------------------------------------------------- 1 | Hello, world. -------------------------------------------------------------------------------- /test/cases/lambda_fns/output.toffee: -------------------------------------------------------------------------------- 1 | PassPassPassPass -------------------------------------------------------------------------------- /test/cases/hello_world/input.toffee: -------------------------------------------------------------------------------- 1 | #{greeting}, world. -------------------------------------------------------------------------------- /test/cases/multiline_interpolation/foo.toffee: -------------------------------------------------------------------------------- 1 | #{a} #{b} -------------------------------------------------------------------------------- /test/cases/render_no_args/input.toffee: -------------------------------------------------------------------------------- 1 | No arguments passed. -------------------------------------------------------------------------------- /test/cases/comments/vars.js: -------------------------------------------------------------------------------- 1 | { 2 | "greeting": "Hello" 3 | } -------------------------------------------------------------------------------- /test/cases/hello_world/vars.js: -------------------------------------------------------------------------------- 1 | { 2 | "greeting": "Hello" 3 | } -------------------------------------------------------------------------------- /test/cases/post_process/signature.toffee: -------------------------------------------------------------------------------- 1 | Goodbye, cruel world. -------------------------------------------------------------------------------- /test/cases/render_no_args/output.toffee: -------------------------------------------------------------------------------- 1 | No arguments passed. -------------------------------------------------------------------------------- /test/cases/include_order/vars.js: -------------------------------------------------------------------------------- 1 | { 2 | "greeting": "Hello" 3 | } -------------------------------------------------------------------------------- /test/cases/include_recursion/vars.js: -------------------------------------------------------------------------------- 1 | { 2 | "countdown" : 10 3 | } -------------------------------------------------------------------------------- /test/express4/views/subdir2/foo/goodbye.toffee: -------------------------------------------------------------------------------- 1 | Goodbye! -------------------------------------------------------------------------------- /bin/toffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/command_line').run(); -------------------------------------------------------------------------------- /test/cases/include_order/child.toffee: -------------------------------------------------------------------------------- 1 | a 2 | {# 3 | say_hi() 4 | #} 5 | b -------------------------------------------------------------------------------- /test/cases/passback/vars.js: -------------------------------------------------------------------------------- 1 | { 2 | "vx": "vx0", 3 | "x" : "hi" 4 | } -------------------------------------------------------------------------------- /test/cases/include_order/output.toffee: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | hia 4 | 5 | b 6 | 3 7 | 4 -------------------------------------------------------------------------------- /test/cases/comments/output.toffee: -------------------------------------------------------------------------------- 1 | 2 | Pass 1 3 | 4 | Pass 2 5 | 6 | Pass 3 7 | -------------------------------------------------------------------------------- /test/cases/hello_world/temp.toffee: -------------------------------------------------------------------------------- 1 | a 2 | b 3 | c 4 | #{passed_fn 100} 5 | d 6 | e 7 | f -------------------------------------------------------------------------------- /test/cases/include_recursion/output.toffee: -------------------------------------------------------------------------------- 1 | 10...9...8...7...6...5...4...3...2...1...blastoff! -------------------------------------------------------------------------------- /test/cases/multiline_interpolation/output.toffee: -------------------------------------------------------------------------------- 1 | Hello, world 2 |
3 | Goodbye, world -------------------------------------------------------------------------------- /test/cases/custom_escape/vars.coffee: -------------------------------------------------------------------------------- 1 | { 2 | escape: (s) -> "#{s}".replace /[^a-z0-9]/gi, '' 3 | } -------------------------------------------------------------------------------- /test/cases/junk/output.toffee: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/cases/snippets/foo/bar/body.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | msg = msg or "Unknown message" 3 | print msg 4 | #} -------------------------------------------------------------------------------- /test/cases/snippets/vars.js: -------------------------------------------------------------------------------- 1 | { 2 | "from": "Preloaded sender", 3 | "msg" : "Preloaded message." 4 | } -------------------------------------------------------------------------------- /test/cases/post_process/output.toffee: -------------------------------------------------------------------------------- 1 | This-is-a-hidden-message 2 | 3 | .dlrow leurc ,eybdooG 4 | .dlrow ,olleH -------------------------------------------------------------------------------- /test/cases/post_process/vars.coffee: -------------------------------------------------------------------------------- 1 | { 2 | greeting: 'Hello' 3 | postProcess: (s) -> (c for c in s by -1).join '' 4 | } -------------------------------------------------------------------------------- /test/cases/include_techniques/message.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | from = from or "Unknown" 3 | #}From: #{from} 4 | Msg: Hello, world 5 | -------------------------------------------------------------------------------- /test/cases/post_process/buncha_junk.toffee: -------------------------------------------------------------------------------- 1 | T3246h354is345-i3245s345-534a534-h534i543d534d534e534n543-m534e543s543s543ag5e534.543 -------------------------------------------------------------------------------- /test/cases/bad_unicode/output.toffee: -------------------------------------------------------------------------------- 1 | \u2028: 2 | 3 | \u2029: 4 | 5 | 6 |

HI 2028:

7 |

HI 2028:

8 | -------------------------------------------------------------------------------- /test/cases/passback/const3.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | passback["vx"] = "vx3" 3 | passback["vy"] = "vy3" 4 | #} 5 | This should not output (3). -------------------------------------------------------------------------------- /test/express4_error_handling/views/test_bad_str_interpolate.toffee: -------------------------------------------------------------------------------- 1 | 2 | This is a bad variable. 3 | 4 | foo.bar = #{foo.bar} 5 | 6 | Hah. -------------------------------------------------------------------------------- /test/cases/include_order/input.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | say_hi = -> 3 | {:hi:} 4 | #}1 5 | 2 6 | #{partial "child.toffee", say_hi: say_hi} 7 | 3 8 | 4 -------------------------------------------------------------------------------- /test/cases/passback/const4_sub.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | passback.vx = "vx4_sub" 3 | passback.vy = "vy4_sub" 4 | #} 5 | This should not output (4_sub). -------------------------------------------------------------------------------- /test/cases/passback/const5_sub.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | passback.vx = "vx5_sub" 3 | passback.vy = "vy5_sub" 4 | #} 5 | This should not output (5_sub). -------------------------------------------------------------------------------- /test/cases/junk/input.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | supplies = ["broom", "mop", "vacuum"] 3 | #} 6 | -------------------------------------------------------------------------------- /test/cases/passback/const4.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | passback.vx = "vx4" 3 | passback.vy = "vy4" 4 | partial "./const4_sub.toffee" 5 | #} 6 | This should not output (4). -------------------------------------------------------------------------------- /test/cases/custom_escape/output.toffee: -------------------------------------------------------------------------------- 1 |

2 | custom x = Helloworldscriptvarx100script 3 | custom y = td 4 | custom z = clickclack 5 | custom w = 12objectObject 6 |

-------------------------------------------------------------------------------- /test/express4_error_handling/views/test_bad_toffee_syntax.toffee: -------------------------------------------------------------------------------- 1 | 2 | {# 3 | x = "Foo" 4 | #} 5 | 6 | {# 7 | {: :} 8 | y = "Bar" 9 | {: 10 | #} 11 | -------------------------------------------------------------------------------- /test/cases/bad_unicode/input.toffee: -------------------------------------------------------------------------------- 1 | \u2028: 2 | \u2029: 3 | {# 4 | u2028 = "
" 5 | u2029 = "
" 6 | #} 7 |

HI 2028: #{"
"}

8 |

HI 2028: #{"
"}

9 | -------------------------------------------------------------------------------- /test/cases/passback/const2.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | passback.vz = "vz2" 3 | vx = "Should not set." 4 | vy = "Should not set." 5 | #} 6 | This should not output (2). -------------------------------------------------------------------------------- /test/cases/passback/const1.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | passback.vx = "vx1" 3 | passback.vy = "vy1" 4 | passback.x = "oh shit" 5 | passback.y = "oh noze" 6 | #} 7 | This should output (1). -------------------------------------------------------------------------------- /test/express4/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /test/cases/include_recursion/input.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | if (countdown is 0) {:blastoff!:} 3 | else 4 | print "#{countdown}...#{partial 'input.toffee', countdown: (countdown-1)}" 5 | #} -------------------------------------------------------------------------------- /test/cases/snippets/output.toffee: -------------------------------------------------------------------------------- 1 | From: Preloaded sender 2 | Preloaded message. 3 | From: Unknown sender 4 | Unknown message. 5 | From: Sam 6 | Preloaded message. 7 | From: Max 8 | Unknown message. -------------------------------------------------------------------------------- /test/cases/special_cases/output.toffee: -------------------------------------------------------------------------------- 1 | 2 | "PASSED" 3 | 4 |

5 | click & clack 6 |

7 | 8 | A backslash is a \ 9 | -------------------------------------------------------------------------------- /test/express4_error_handling/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /test/cases/multiline_interpolation/input.toffee: -------------------------------------------------------------------------------- 1 | #{ 2 | "Hello, " + 3 | "world" 4 | } 5 |
6 | #{ 7 | partial "foo.toffee", 8 | a: "Goodbye#{','}" 9 | b: "world" 10 | } -------------------------------------------------------------------------------- /test/cases/snippets/input.toffee: -------------------------------------------------------------------------------- 1 | #{ partial "./foo/message.toffee"} 2 | #{ snippet "./foo/message.toffee"} 3 | #{ partial "./foo/message.toffee", from: "Sam"} 4 | #{ snippet "./foo/message.toffee", from: "Max"} -------------------------------------------------------------------------------- /test/cases/passback/const5.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | passback.vx = "vx5" 3 | passback.vy = "vy5" 4 | print load "./const5_sub.toffee" 5 | passback.vx = vx 6 | passback.vy = vy 7 | #} 8 | This should not output (5). -------------------------------------------------------------------------------- /test/cases/snippets/foo/message.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | from = from or "Unknown sender" 3 | msg = msg or "Unknown message." 4 | print """ 5 | From: #{from} 6 | #{snippet './bar/body.toffee', msg: msg} 7 | """ 8 | #} -------------------------------------------------------------------------------- /src/consts.coffee: -------------------------------------------------------------------------------- 1 | exports.states = 2 | TOFFEE: 1 3 | COFFEE: 2 4 | 5 | exports.TAB_SPACES = 2 6 | 7 | exports.tweakables = 8 | MISSING_FILE_RECHECK: 1000 # ms before checking if a file has been introduced/changed if fs.watch() fails 9 | -------------------------------------------------------------------------------- /test/cases/passback/output.toffee: -------------------------------------------------------------------------------- 1 | vx,vy,vz = vx0,, 2 | 3 | This should output (1). 4 | vx,vy,vz = vx1,vy1, 5 | 6 | vx,vy,vz = vx1,vy1,vz2 7 | 8 | vx,vy,vz = vx3,vy3,vz2 9 | 10 | vx,vy,vz = vx4,vy4,vz2 11 | 12 | vx,vy,vz = vx5_sub,vy5_sub,vz2 -------------------------------------------------------------------------------- /test/cases/eco_compare/output.toffee: -------------------------------------------------------------------------------- 1 | 2 | okcupid 3 |

A site for singles

4 | 5 | tallygram 6 |

A site for anyone

7 | 8 | 9 | You have 3 female friends. -------------------------------------------------------------------------------- /lib/coffee-script/index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var key, val, _ref; 4 | 5 | _ref = require('./coffee-script'); 6 | for (key in _ref) { 7 | val = _ref[key]; 8 | exports[key] = val; 9 | } 10 | 11 | }).call(this); 12 | -------------------------------------------------------------------------------- /test/express4_error_handling/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "application-name", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app" 7 | }, 8 | "dependencies": { 9 | "express": "3.0.0beta7", 10 | "jade": "*" 11 | } 12 | } -------------------------------------------------------------------------------- /test/express4_error_handling/views/test_bad_runtime.toffee: -------------------------------------------------------------------------------- 1 | 2 | About to convert a circular JSON structure 3 | 4 | {# 5 | x = [1,2,3] 6 | x.push x 7 | {: 8 |

x as JSON

9 | {# 10 | print JSON.stringify x 11 | #} 12 | :} 13 | #} 14 | -------------------------------------------------------------------------------- /lib/consts.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.7 2 | (function() { 3 | exports.states = { 4 | TOFFEE: 1, 5 | COFFEE: 2 6 | }; 7 | 8 | exports.TAB_SPACES = 2; 9 | 10 | exports.tweakables = { 11 | MISSING_FILE_RECHECK: 1000 12 | }; 13 | 14 | }).call(this); 15 | -------------------------------------------------------------------------------- /test/cases/custom_escape/input.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | x = '"Hello world"' 3 | y = '' 4 | z = 'click&clack' 5 | w = [1, 2, {"place": "The Dreadfort"}] 6 | #}

7 | custom x = #{x} 8 | custom y = #{y} 9 | custom z = #{z} 10 | custom w = #{w} 11 |

-------------------------------------------------------------------------------- /test/express4/views/subdir1/hello_world.toffee: -------------------------------------------------------------------------------- 1 | Hello, world! 2 | 3 | {# 4 | i = 1 5 | for j in [0...rows] {: 6 | 7 | {# 8 | for k in [0...cols] {::} 9 | #} 10 | 11 | :} 12 | #} 13 |
#{i++}
-------------------------------------------------------------------------------- /test/cases/include_techniques/output.toffee: -------------------------------------------------------------------------------- 1 | From: Chris <ccoyne77@gmail> 2 | Msg: Hello, world 3 | 4 | From: Max & Sam 5 | Msg: Hello, world 6 | 7 | From: Christian 8 | Msg: Hello, world 9 | From: Jennie 10 | Msg: Hello, world 11 | From: Unknown 12 | Msg: Hello, world 13 | -------------------------------------------------------------------------------- /test/cases/indent_attack/output.toffee: -------------------------------------------------------------------------------- 1 |
2 | Pass1Pass2 3 |
4 | 5 | Pass3Pass4 6 |
7 | 8 | Pass5Pass6 9 | 10 | Pass7Pass8 11 | 12 | ...passed with flying colors. 13 |

14 | Pass12Pass13Pass14Pass15Pass16(a perfect square)Pass17Pass18Pass19 15 | Pass20 16 |

-------------------------------------------------------------------------------- /test/cases/post_process/input.toffee: -------------------------------------------------------------------------------- 1 | #{greeting}, world. 2 | #{partial './signature.toffee'} 3 | {# 4 | reverse = (s) -> (c for c in s by -1).join "" 5 | clean = (s) -> (c for c in s when c.match /[a-z\-]/gi).join "" 6 | #} 7 | #{partial './buncha_junk.toffee', {postProcess: (s) -> reverse(clean(s))}} -------------------------------------------------------------------------------- /test/cases/json_formatting/input.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | x = 3 | foo: [1,2,3] 4 | bar: 5 | car: [4,5,"<\/html",{zar: [6,7,null]}] 6 | y = [1,2,"<\/script>\""] 7 | #} 8 | #{x} 9 | #{json x, {indent: ' '}} 10 | #{json x, {indent: 2}} 11 | #{json x, {indent: '\t'}} 12 | #{__toffee.json y, {indent:3}} 13 | -------------------------------------------------------------------------------- /test/cases/include_techniques/input.toffee: -------------------------------------------------------------------------------- 1 | #{partial "message.toffee", from: "Chris "} 2 | #{partial "message.toffee", from: "Max & Sam"} 3 | {# 4 | print partial "message.toffee", from: "Christian" 5 | {:#{ partial "message.toffee", from: "Jennie"}:} 6 | print partial "message.toffee", sender: "The enemy" 7 | #} -------------------------------------------------------------------------------- /test/cases/lambda_fns/input.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | print_it = (msg) -> {:#{msg}:} 3 | 4 | print_it_twice = (msg) -> 5 | {:#{msg}:} 6 | m = msg 7 | {:#{m}:} 8 | 9 | echo_it = (msg) -> 10 | v = msg 11 | v 12 | 13 | print_it "Pass" 14 | print_it_twice "Pass" 15 | print echo_it "Pass" 16 | #} -------------------------------------------------------------------------------- /src/pool.coffee: -------------------------------------------------------------------------------- 1 | class Pool 2 | constructor: (cons, size) -> 3 | @_consfn = cons 4 | @_size = size 5 | @_pool = [] 6 | 7 | get: () -> 8 | if @_pool.length > 0 9 | @_pool.pop() 10 | else 11 | @_consfn() 12 | 13 | release: (x) -> 14 | if @_pool.length < @_size 15 | @_pool.push x 16 | 17 | exports.Pool = Pool 18 | -------------------------------------------------------------------------------- /test/cases/passback/input.toffee: -------------------------------------------------------------------------------- 1 | vx,vy,vz = #{vx},#{vy},#{vz} 2 | #{partial "./const1.toffee"} 3 | vx,vy,vz = #{vx},#{vy},#{vz} 4 | #{load "./const2.toffee"} 5 | vx,vy,vz = #{vx},#{vy},#{vz} 6 | #{load "./const3.toffee"} 7 | vx,vy,vz = #{vx},#{vy},#{vz} 8 | #{load "./const4.toffee"} 9 | vx,vy,vz = #{vx},#{vy},#{vz} 10 | #{load "./const5.toffee"} 11 | vx,vy,vz = #{vx},#{vy},#{vz} -------------------------------------------------------------------------------- /test/express4/views/subdir2/goodbye_world.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # let's test some recursion. 4 | # 5 | # ---------------------------- 6 | 7 | if not countdown? then countdown = 32 8 | if countdown is 0 9 | print partial "foo/goodbye.toffee" 10 | else {: 11 | #{countdown}
#{partial './goodbye_world.toffee', {countdown: countdown - 1}} 12 | :} 13 | 14 | #} -------------------------------------------------------------------------------- /test/express4_error_handling/routes/index.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET home page. 4 | */ 5 | 6 | exports.index = function(req, res){ 7 | var vars = { } 8 | res.render('index.toffee', vars); 9 | }; 10 | 11 | /* 12 | * individual test cases 13 | */ 14 | 15 | exports.path = function(req, res){ 16 | var vars = { }; 17 | vars.path = req.params.path 18 | res.render("./" + vars.path + ".toffee", vars); 19 | }; 20 | -------------------------------------------------------------------------------- /test/cases/special_cases/input.toffee: -------------------------------------------------------------------------------- 1 | {## 2 | 3 | Make sure leading a trailing quotes don't mess with string printing. 4 | 5 | ##} 6 | {# 7 | {:"PASSED":} 8 | #} 9 | {## 10 | 11 | Make sure `print` inside toffee mode still works and is kept raw 12 | 13 | ##} 14 |

15 | #{print "#{'click & clack'}"} 16 |

17 | {## 18 | 19 | Make backslashes in text are handled ok. 20 | 21 | ##} 22 | A backslash is a \ 23 | -------------------------------------------------------------------------------- /test/cases/comments/input.toffee: -------------------------------------------------------------------------------- 1 | {## Fail ##} 2 | Pass 1 3 | {## 4 | 5 | Fail 6 | #{x} 7 | 8 | ##} 9 | Pass 2 10 | {# 11 | 12 | # print "Fail" 13 | 14 | ### 15 | print "FAIL FAIL FAIL" 16 | #{ foo } 17 | ### 18 | 19 | #} 20 | Pass 3{## Fail ##}{## Fail ##} 21 | {## 22 | Fail 23 | #{"Fail"} 24 | {# 25 | for x in [1...100] {: 26 | #{"Fail"} 27 | {# 28 | print "Fail" 29 | #} 30 | :} 31 | #} 32 | ##} -------------------------------------------------------------------------------- /lib/pool.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.7 2 | (function() { 3 | var Pool; 4 | 5 | Pool = (function() { 6 | function Pool(cons, size) { 7 | this._consfn = cons; 8 | this._size = size; 9 | this._pool = []; 10 | } 11 | 12 | Pool.prototype.get = function() { 13 | if (this._pool.length > 0) { 14 | return this._pool.pop(); 15 | } else { 16 | return this._consfn(); 17 | } 18 | }; 19 | 20 | Pool.prototype.release = function(x) { 21 | if (this._pool.length < this._size) { 22 | return this._pool.push(x); 23 | } 24 | }; 25 | 26 | return Pool; 27 | 28 | })(); 29 | 30 | exports.Pool = Pool; 31 | 32 | }).call(this); 33 | -------------------------------------------------------------------------------- /test/cases/eco_compare/input.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | @projects = [ 3 | {url: "http://localhost:3000", name: "okcupid", description: "A site for singles"} 4 | {url: "http://localhost:3001", name: "tallygram", description: "A site for anyone"} 5 | ] 6 | 7 | if @projects.length 8 | for project in @projects {: 9 | #{project.name} 10 |

#{project.description}

11 | :} 12 | else {: No projects :} 13 | 14 | friends = [ 15 | { gender: "f", name: "Jennie" } 16 | { gender: "f", name: "Rachel" } 17 | { gender: "m", name: "Petar" } 18 | { gender: "f", name: "Marissa" } 19 | ] 20 | #} 21 | 22 | You have #{(f for f in friends when f.gender is "f").length} female friends. -------------------------------------------------------------------------------- /test/express4_error_handling/views/index.toffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | Express 3 Test 4 | 5 | 10 | 11 | {# 12 | ports = [3034, 3035] 13 | percent = ~~(100 / (ports.length)) - 2 14 | tests = [ 15 | ["test_bad_str_interpolate", 300] 16 | ["test_bad_toffee_syntax", 300] 17 | ["test_bad_coffee_syntax", 300] 18 | ["test_bad_runtime", 300] 19 | ] 20 | #} 21 | 22 | 23 | 24 | {# 25 | for [t,height] in tests 26 | {::} 27 | for p in ports 28 | {: 29 | 32 | :} 33 | {::} 34 | #} 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/cases/json_formatting/output.toffee: -------------------------------------------------------------------------------- 1 | 2 | {"foo":[1,2,3],"bar":{"car":[4,5,"\u003C/html",{"zar":[6,7,null]}]}} 3 | { 4 | "foo": [ 5 | 1, 6 | 2, 7 | 3 8 | ], 9 | "bar": { 10 | "car": [ 11 | 4, 12 | 5, 13 | "\u003C/html", 14 | { 15 | "zar": [ 16 | 6, 17 | 7, 18 | null 19 | ] 20 | } 21 | ] 22 | } 23 | } 24 | { 25 | "foo": [ 26 | 1, 27 | 2, 28 | 3 29 | ], 30 | "bar": { 31 | "car": [ 32 | 4, 33 | 5, 34 | "\u003C/html", 35 | { 36 | "zar": [ 37 | 6, 38 | 7, 39 | null 40 | ] 41 | } 42 | ] 43 | } 44 | } 45 | { 46 | "foo": [ 47 | 1, 48 | 2, 49 | 3 50 | ], 51 | "bar": { 52 | "car": [ 53 | 4, 54 | 5, 55 | "\u003C/html", 56 | { 57 | "zar": [ 58 | 6, 59 | 7, 60 | null 61 | ] 62 | } 63 | ] 64 | } 65 | } 66 | [ 67 | 1, 68 | 2, 69 | "\u003C/script\u003E\"" 70 | ] 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) <2012> Chris Coyne 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /test/express4/app.coffee: -------------------------------------------------------------------------------- 1 | 2 | run = (cb) -> 3 | toffee = require '../../index.js' 4 | express = require 'express' 5 | http = require 'http' 6 | 7 | app = express() 8 | 9 | 10 | app_configure = -> 11 | 12 | toffee.expressEngine.verbose = not module.parent 13 | toffee.expressEngine.prettyPrintErrors = false 14 | 15 | app.set 'port', process.env.PORT or 3033 16 | app.set 'views', __dirname + '/views' 17 | app.engine 'toffee', toffee.__express 18 | app.use express.static __dirname + '/public' 19 | app.route('/').get (req, res) => 20 | circular_obj = [1,2,3] 21 | circular_obj.push circular_obj 22 | title = 'Express' 23 | a_bad_test_function = -> return JSON.stringify circular_obj 24 | vars = {title, a_bad_test_function} 25 | res.render 'index.toffee', vars 26 | 27 | http.createServer(app).listen app.get('port'), -> 28 | console.log "Express server listening on port #{app.get('port')}" 29 | if cb? then cb() 30 | 31 | app_configure() 32 | 33 | if not module.parent 34 | run() 35 | 36 | else 37 | exports.run = (cb) -> run cb 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "toffee", 3 | "description": "A NodeJs and browser-side templating language based on CoffeeScript with slicker tokens and syntax.", 4 | "version": "0.3.7", 5 | "directories": { 6 | "lib": "./lib" 7 | }, 8 | "main": "index.js", 9 | "author": "Chris Coyne ", 10 | "bin": "./bin/toffee", 11 | "dependencies": { 12 | "coffee-script": "1.12.7", 13 | "commander": "10.0.0", 14 | "highlight.js": "11.7.0", 15 | "iced-lock": "2.0.1", 16 | "mkdirp": "2.1.3" 17 | }, 18 | "devDependencies": { 19 | "assert": "2.0.0", 20 | "colors": "1.4.0", 21 | "diff": "5.1.0", 22 | "express": "4.18.2", 23 | "iced-coffee-script": "108.0.14", 24 | "jison": "0.4.18", 25 | "tablify": "0.1.5", 26 | "zombie": "6.1.4" 27 | }, 28 | "files": [ 29 | "index.js", 30 | "lib/*", 31 | "bin/*" 32 | ], 33 | "repository": { 34 | "type": "git", 35 | "url": "http://github.com/malgorithms/toffee" 36 | }, 37 | "licenses": [ 38 | { 39 | "type": "MIT", 40 | "url": "http://github.com/malgorithms/toffee/raw/master/LICENSE" 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /test/express4_error_handling/views/test_bad_coffee_syntax.toffee: -------------------------------------------------------------------------------- 1 | 2 | {# 3 | x = "Foo" 4 | #} 5 | 6 | {# 7 | y = "Bar" 8 | {: 9 | Hello there 10 | {# 11 | var x = 100 12 | #} 13 | :} 14 | #} 15 | Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah Bleah bleah bleah Bleah -------------------------------------------------------------------------------- /src/utils.coffee: -------------------------------------------------------------------------------- 1 | lexer = require './coffee-script/lexer' 2 | lex = new lexer.Lexer() 3 | 4 | exports.interpolateString = (str) -> 5 | ### 6 | Similar to the interpolateString function in CoffeeScript, 7 | except that it doesn't actually work on anything inside an outer #{}; 8 | we're just looking to recognize them. 9 | ### 10 | tokens = [] 11 | res = [] 12 | pi = 0 13 | i = -1 14 | while letter = str.charAt i += 1 15 | if letter is '\\' 16 | i += 1 17 | continue 18 | unless letter is '#' and str.charAt(i+1) is '{' and 19 | (expr = lex.balancedString str[i + 1..], '}') 20 | continue 21 | tokens.push ['NEOSTRING', str[pi...i]] if pi < i 22 | inner = expr[1...-1] 23 | if inner.length 24 | tokens.push ['TOKENS', inner] 25 | i += expr.length 26 | pi = i + 1 27 | tokens.push ['NEOSTRING', str[pi..]] if i > pi < str.length 28 | return res.push 'STRING', '""' unless tokens.length 29 | tokens.unshift ['', ''] unless tokens[0][0] is 'NEOSTRING' 30 | res.push '(', '(' if interpolated = tokens.length > 1 31 | for [tag, value], i in tokens 32 | res.push '+', '+' if i 33 | if tag is 'TOKENS' 34 | res.push [tag, value] 35 | else 36 | res.push ['STRING', value] 37 | #res.push 'STRING', @makeString value, '"', heredoc 38 | res.push ')', ')' if interpolated 39 | tokens 40 | -------------------------------------------------------------------------------- /test/cases/escape/input.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | x = '"Hello world"' 3 | y = '
' 4 | z = 'click&clack' 5 | w = [1, 2, {"place": "The Dreadfort", "evil \"code\"": "italic"}] 6 | v = ["\u2028", "\u2029"] 7 | dir = ["hi\u{202e}there"] # ltr type things 8 | #}

9 | default x = #{x} 10 | default y = #{y} 11 | default z = #{z} 12 | default w = #{w} 13 | default r = #{r}eol 14 | default w.foo = #{w.foo}eol 15 | default v = #{v} 16 | default dir = #{dir} 17 | default dir0 = #{dir[0]} 18 |

19 |

20 | raw x = #{raw x} 21 | raw y = #{raw y} 22 | raw z = #{raw z} 23 | raw w = #{raw w} 24 |

25 |
26 |   w_as_json_stringify=#{JSON.stringify w}
27 |   w_as_json_stringify_raw=#{raw JSON.stringify w}
28 | 
29 | 36 |

37 | {# 38 | print " raw printed x = #{x}\n" 39 | print " raw printed y = #{y}\n" 40 | print " raw printed z = #{z}\n" 41 | print " raw printed w = #{w}" 42 | #} 43 |

44 |

45 | {# 46 | print " json printed x = #{ raw raw raw raw json x }\n" 47 | print " json printed y = #{ raw raw raw raw json y }\n" 48 | print " json printed z = #{ raw raw raw raw json z }\n" 49 | print " json printed w = #{ raw raw raw raw json w }\n" 50 | print " json printed v = #{ raw raw raw raw json v }" 51 | #} 52 |

53 |

54 | {# 55 | print " html printed longhand x = #{ __toffee.html x }\n" 56 | print " html printed longhand y = #{ __toffee.html y }\n" 57 | print " html printed longhand z = #{ __toffee.html z }\n" 58 | print " html printed longhand w = #{ __toffee.html w }" 59 | #} 60 |

61 | -------------------------------------------------------------------------------- /test/express4_error_handling/app.coffee: -------------------------------------------------------------------------------- 1 | toffee = require '../../index.js' 2 | 3 | run = (port, express_engine, cb) -> 4 | express = require 'express' 5 | routes = require './routes' 6 | http = require 'http' 7 | 8 | app = express() 9 | 10 | 11 | app.configure -> 12 | 13 | app.set 'port', port 14 | app.set 'views', __dirname + '/views' 15 | app.engine 'toffee', express_engine 16 | app.use express.favicon() 17 | app.use express.logger 'dev' 18 | app.use express.bodyParser() 19 | app.use express.methodOverride() 20 | app.use app.router 21 | app.use express.static __dirname + '/public' 22 | 23 | app.configure 'development', -> 24 | app.use express.errorHandler() 25 | 26 | app.get '/', routes.index 27 | app.get '/:path([^ ]+)', routes.path 28 | 29 | http.createServer(app).listen app.get('port'), -> 30 | console.log "Express server listening on port #{app.get('port')}" 31 | if cb? then cb() 32 | 33 | # ----------------------------------------------------------------------- 34 | 35 | run_all = (cb) -> 36 | 37 | # run a standard version on port 3034 38 | # ----------------------------------- 39 | run 3034, toffee.__express, -> 40 | 41 | # run a version that doesn't catch errors on port 3035 42 | # ---------------------------------------------------- 43 | e2 = new toffee.engine { 44 | prettyPrintErrors: false 45 | } 46 | run 3035, toffee.toExpress(e2), -> 47 | 48 | cb() 49 | 50 | # ----------------------------------------------------------------------- 51 | 52 | 53 | if not module.parent 54 | run_all -> 55 | console.log "All running" 56 | 57 | else 58 | exports.run = (cb) -> run_all cb -------------------------------------------------------------------------------- /test/cases/escape/output.toffee: -------------------------------------------------------------------------------- 1 |

2 | default x = "Hello world" 3 | default y = <hr /> 4 | default z = click&clack 5 | default w = [1,2,{"place":"The Dreadfort","evil \u003Cb\u003E\"code\"\u003C/b\u003E":"\u003Ci\u003Eitalic\u003C/i\u003E"}] 6 | default r = eol 7 | default w.foo = eol 8 | default v = ["\u2028","\u2029"] 9 | default dir = ["hi\u202ethere"] 10 | default dir0 = hithere 11 |

12 |

13 | raw x = "Hello world" 14 | raw y =


15 | raw z = click&clack 16 | raw w = 1,2,[object Object] 17 |

18 |
19 |   w_as_json_stringify=[1,2,{"place":"The Dreadfort","evil <b>\"code\"</b>":"<i>italic</i>"}]
20 |   w_as_json_stringify_raw=[1,2,{"place":"The Dreadfort","evil \"code\"":"italic"}]
21 | 
22 | 29 |

30 | raw printed x = "Hello world" 31 | raw printed y =


32 | raw printed z = click&clack 33 | raw printed w = 1,2,[object Object] 34 |

35 |

36 | json printed x = "\"Hello world\"" 37 | json printed y = "\u003Chr /\u003E" 38 | json printed z = "click\u0026clack" 39 | json printed w = [1,2,{"place":"The Dreadfort","evil \u003Cb\u003E\"code\"\u003C/b\u003E":"\u003Ci\u003Eitalic\u003C/i\u003E"}] 40 | json printed v = ["\u2028","\u2029"] 41 |

42 |

43 | html printed longhand x = "Hello world" 44 | html printed longhand y = <hr /> 45 | html printed longhand z = click&clack 46 | html printed longhand w = 1,2,[object Object] 47 |

48 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | require 'iced-coffee-script/register' 2 | {spawn, exec} = require 'child_process' 3 | fs = require 'fs' 4 | jison = require 'jison' 5 | path = require 'path' 6 | express_test = require './test/generate_express_test' 7 | 8 | task 'build', 'build the whole jam', (cb) -> 9 | console.log "Building" 10 | files = fs.readdirSync 'src' 11 | files = ('src/' + file for file in files when file.match(/\.coffee$/)) 12 | clearLibJs -> 13 | buildParser -> 14 | runCoffee ['-c', '-o', 'lib/'].concat(files), -> 15 | runCoffee ['-c', 'index.coffee'], -> 16 | buildCommonBrowserHeaders -> 17 | express_test.generate -> 18 | console.log "Done building." 19 | cb() if typeof cb is 'function' 20 | 21 | task 'test', 'test server and browser support', (cb) -> 22 | run_cases = require './test/run_cases.iced' 23 | run_cases.test -> 24 | console.log "Done." 25 | 26 | runCoffee = (args, cb) -> 27 | proc = spawn './node_modules/.bin/coffee', args 28 | proc.stderr.on 'data', (buffer) -> console.log buffer.toString() 29 | proc.on 'exit', (status) -> 30 | process.exit(1) if status isnt 0 31 | cb() if typeof cb is 'function' 32 | 33 | clearLibJs = (cb) -> 34 | files = fs.readdirSync 'lib' 35 | files = ("lib/#{file}" for file in files when file.match(/\.js$/)) 36 | fs.unlinkSync f for f in files 37 | cb() 38 | 39 | buildParser = (cb) -> 40 | grammar = fs.readFileSync './src/toffee.jison', 'utf8' 41 | generator = new jison.Generator grammar 42 | file_name = "toffee_lang.js" 43 | source = generator.generate { 44 | moduleType: 'commonjs' 45 | moduleName: 'toffee_lang' 46 | } 47 | fs.writeFileSync "./lib/#{file_name}", source 48 | cb() 49 | 50 | buildCommonBrowserHeaders = (cb) -> 51 | {getCommonHeadersJs} = require './lib/view' 52 | headers = getCommonHeadersJs true, true 53 | fs.writeFileSync "./toffee.js", headers, "utf8" 54 | cb() 55 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.7 2 | (function() { 3 | var lex, lexer; 4 | 5 | lexer = require('./coffee-script/lexer'); 6 | 7 | lex = new lexer.Lexer(); 8 | 9 | exports.interpolateString = function(str) { 10 | 11 | /* 12 | Similar to the interpolateString function in CoffeeScript, 13 | except that it doesn't actually work on anything inside an outer #{}; 14 | we're just looking to recognize them. 15 | */ 16 | var expr, i, inner, interpolated, j, len, letter, pi, ref, res, tag, tokens, value; 17 | tokens = []; 18 | res = []; 19 | pi = 0; 20 | i = -1; 21 | while (letter = str.charAt(i += 1)) { 22 | if (letter === '\\') { 23 | i += 1; 24 | continue; 25 | } 26 | if (!(letter === '#' && str.charAt(i + 1) === '{' && (expr = lex.balancedString(str.slice(i + 1), '}')))) { 27 | continue; 28 | } 29 | if (pi < i) { 30 | tokens.push(['NEOSTRING', str.slice(pi, i)]); 31 | } 32 | inner = expr.slice(1, -1); 33 | if (inner.length) { 34 | tokens.push(['TOKENS', inner]); 35 | } 36 | i += expr.length; 37 | pi = i + 1; 38 | } 39 | if ((i > pi && pi < str.length)) { 40 | tokens.push(['NEOSTRING', str.slice(pi)]); 41 | } 42 | if (!tokens.length) { 43 | return res.push('STRING', '""'); 44 | } 45 | if (tokens[0][0] !== 'NEOSTRING') { 46 | tokens.unshift(['', '']); 47 | } 48 | if (interpolated = tokens.length > 1) { 49 | res.push('(', '('); 50 | } 51 | for (i = j = 0, len = tokens.length; j < len; i = ++j) { 52 | ref = tokens[i], tag = ref[0], value = ref[1]; 53 | if (i) { 54 | res.push('+', '+'); 55 | } 56 | if (tag === 'TOKENS') { 57 | res.push([tag, value]); 58 | } else { 59 | res.push(['STRING', value]); 60 | } 61 | } 62 | if (interpolated) { 63 | res.push(')', ')'); 64 | } 65 | return tokens; 66 | }; 67 | 68 | }).call(this); 69 | -------------------------------------------------------------------------------- /lib/coffee-script/helpers.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var extend, flatten; 4 | 5 | exports.starts = function(string, literal, start) { 6 | return literal === string.substr(start, literal.length); 7 | }; 8 | 9 | exports.ends = function(string, literal, back) { 10 | var len; 11 | len = literal.length; 12 | return literal === string.substr(string.length - len - (back || 0), len); 13 | }; 14 | 15 | exports.compact = function(array) { 16 | var item, _i, _len, _results; 17 | _results = []; 18 | for (_i = 0, _len = array.length; _i < _len; _i++) { 19 | item = array[_i]; 20 | if (item) { 21 | _results.push(item); 22 | } 23 | } 24 | return _results; 25 | }; 26 | 27 | exports.count = function(string, substr) { 28 | var num, pos; 29 | num = pos = 0; 30 | if (!substr.length) { 31 | return 1 / 0; 32 | } 33 | while (pos = 1 + string.indexOf(substr, pos)) { 34 | num++; 35 | } 36 | return num; 37 | }; 38 | 39 | exports.merge = function(options, overrides) { 40 | return extend(extend({}, options), overrides); 41 | }; 42 | 43 | extend = exports.extend = function(object, properties) { 44 | var key, val; 45 | for (key in properties) { 46 | val = properties[key]; 47 | object[key] = val; 48 | } 49 | return object; 50 | }; 51 | 52 | exports.flatten = flatten = function(array) { 53 | var element, flattened, _i, _len; 54 | flattened = []; 55 | for (_i = 0, _len = array.length; _i < _len; _i++) { 56 | element = array[_i]; 57 | if (element instanceof Array) { 58 | flattened = flattened.concat(flatten(element)); 59 | } else { 60 | flattened.push(element); 61 | } 62 | } 63 | return flattened; 64 | }; 65 | 66 | exports.del = function(obj, key) { 67 | var val; 68 | val = obj[key]; 69 | delete obj[key]; 70 | return val; 71 | }; 72 | 73 | exports.last = function(array, back) { 74 | return array[array.length - (back || 0) - 1]; 75 | }; 76 | 77 | }).call(this); 78 | -------------------------------------------------------------------------------- /test/cases/big_file/output.toffee: -------------------------------------------------------------------------------- 1 | 0... 1... 2... 3... 4... 5... 6... 7... 8... 9... 10... 11... 12... 13... 14... 15... 16... 17... 18... 19... 20... 21... 22... 23... 24... 25... 26... 27... 28... 29... 30... 31... 32... 33... 34... 35... 36... 37... 38... 39... 40... 41... 42... 43... 44... 45... 46... 47... 48... 49... 50... 51... 52... 53... 54... 55... 56... 57... 58... 59... 60... 61... 62... 63... 64... 65... 66... 67... 68... 70... 70... 71... 72... 73... 74... 75... 76... 77... 78... 79... 80... 81... 82... 83... 84... 85... 86... 87... 88... 89... 90... 91... 92... 93... 94... 95... 96... 97... 98... 99... 100... 101... 102... 103... 104... 105... 106... 107... 108... 109... 110... 111... 112... 113... 114... 115... 116... 117... 118... 119... 120... 121... 122... 123... 124... 125... 126... 127... 128... 129... 130... 131... 132... 133... 134... 135... 136... 137... 138... 139... 140... 141... 142... 143... 144... 145... 146... 147... 148... 149... 150... 151... 152... 153... 154... 155... 156... 157... 158... 159... 160... 162...162... 163... 164... 165... 166... 167... 168... 169... 170... 171... 172... 173... 174... 175... 176... 177... 178... 179... 180... 181... 182... 183... 184... 185... 186... 187... 188... 189... 190... 191... 192... 193... 194... 195... 196... 197... 198... 199... 200... 201... 202... 203... 204... 205... 206... 207... 208... 209... 210... 211... 212... 213... 214... 215... 216... 217... 218... 219... 220... 221... 222... 223... 224... 225... 226... 227... 228... 229... 230... 232... 232... 233... 234... 235... 236... 237... 238... 239... 240... 241... 242... 243... 244... 245... 246... 247... 248... 249... 250... 251... 252... 253... 254... 255... 256... 257... 258... 259... 260... 261... 262... 263... 264... 265... 266... 267... 268... 269... 270... 271... 272... 273... 274... 275... 276... 277... 278... 279... 280... 281... 282... 283... 284... 285... 286... 287... 288... 289... 290... 291... 292... 293... 294... 295... 296... 297... 298... 299... 300... 301... 302... 303... 304... 305... 306... 307... 308... 309... 310... 311... 312... 313... 314... 315... 316... 317... 318... 319... 320... 321... 322... 324... -------------------------------------------------------------------------------- /test/cases/indent_attack/input.toffee: -------------------------------------------------------------------------------- 1 |
2 | {# 3 | if 1 is 1 4 | if 2 is 2 5 | if 3 is 3 {:Pass1:} 6 | if 1 is 1 7 | if 2 is 3 8 | if 3 is 3 9 | {:Fail:} 10 | else 11 | {:Fail:} 12 | else 13 | if 2 is 2 14 | if 3 is 3 {:Pass2:} 15 | #} 16 |
17 | 18 | {# 19 | if 1 is 1 20 | if 2 is 2 21 | if 3 is 3 {:Pass3:} 22 | if 1 is 1 23 | if 2 is 3 24 | if 3 is 3 25 | {:Fail:} 26 | else 27 | {:Fail:} 28 | else 29 | if 2 is 2 30 | if 3 is 3 {:Pass4:} 31 | #} 32 |
33 | 34 | {# 35 | if 10 is 10 36 | if 20 is 20 37 | if 30 is 30 {:Pass5:} 38 | if 10 is 10 39 | if 20 is 30 40 | if 30 is 30 41 | {:Fail:} 42 | else 43 | {:Fail:} 44 | else 45 | if 20 is 20 46 | if 30 is 30 {:Pass6:} 47 | #} 48 | 49 | {# 50 | if 99 is 99 51 | print 'Pass7' 52 | else 53 | print 'Fail' 54 | {:Fail8:} 55 | {:Pass8:} 56 | #} 57 | 58 | {# 59 | 60 | {:...passed with flying colors.:} 61 | #}{## 62 | {# 63 | if true and 10 is 10 64 | {: 65 | Pass9 66 | :} 67 | print "Pass10" 68 | if 11 is 12 69 | print "Fail" 70 | if 12 is 13 {: 71 | Fail 72 | :} 73 | else 74 | {: 75 | Pass11 76 | :} 77 | else if 11 is 12 78 | {: 79 | Fail 80 | :} 81 | #} 82 | ##} 83 |

84 | {# 85 | x = 20 86 | if x > 1 87 | for i in [12...x] 88 | square = 16 89 | {:Pass#{i}{# 90 | if i is square {:(a perfect square):} 91 | #}:} 92 | #} 93 | {# 94 | x = 20 95 | if x > 1 96 | for i in [12...x] 97 | square = 16 98 | {:Pass#{i}{# 99 | if i is square {:(a perfect square):} 100 | #}:} 101 | #} 102 |

-------------------------------------------------------------------------------- /test/cases/big_file/input.toffee: -------------------------------------------------------------------------------- 1 | {# 2 | count = 0 3 | for i in [0...2] {:#{ 4 | count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 5 | }... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 6 | }... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 7 | }... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 8 | }... #{count++}... #{count++}... #{count++}... #{count++ 9 | }... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 10 | }...{# 11 | count += 1 12 | print " #{count}..." 13 | #} #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 14 | }... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 15 | }... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 16 | }... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 17 | }... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 18 | }... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 19 | }... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++}... #{count++ 20 | }...{# 21 | count += 1 22 | print " #{count}..." 23 | #}:} 24 | #} -------------------------------------------------------------------------------- /lib/coffee-script/browser.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var CoffeeScript, runScripts; 4 | 5 | CoffeeScript = require('./coffee-script'); 6 | 7 | CoffeeScript.require = require; 8 | 9 | CoffeeScript["eval"] = function(code, options) { 10 | if (options == null) { 11 | options = {}; 12 | } 13 | if (options.bare == null) { 14 | options.bare = true; 15 | } 16 | return eval(CoffeeScript.compile(code, options)); 17 | }; 18 | 19 | CoffeeScript.run = function(code, options) { 20 | if (options == null) { 21 | options = {}; 22 | } 23 | options.bare = true; 24 | return Function(CoffeeScript.compile(code, options))(); 25 | }; 26 | 27 | if (typeof window === "undefined" || window === null) { 28 | return; 29 | } 30 | 31 | CoffeeScript.load = function(url, callback) { 32 | var xhr; 33 | xhr = new (window.ActiveXObject || XMLHttpRequest)('Microsoft.XMLHTTP'); 34 | xhr.open('GET', url, true); 35 | if ('overrideMimeType' in xhr) { 36 | xhr.overrideMimeType('text/plain'); 37 | } 38 | xhr.onreadystatechange = function() { 39 | var _ref; 40 | if (xhr.readyState === 4) { 41 | if ((_ref = xhr.status) === 0 || _ref === 200) { 42 | CoffeeScript.run(xhr.responseText); 43 | } else { 44 | throw new Error("Could not load " + url); 45 | } 46 | if (callback) { 47 | return callback(); 48 | } 49 | } 50 | }; 51 | return xhr.send(null); 52 | }; 53 | 54 | runScripts = function() { 55 | var coffees, execute, index, length, s, scripts; 56 | scripts = document.getElementsByTagName('script'); 57 | coffees = (function() { 58 | var _i, _len, _results; 59 | _results = []; 60 | for (_i = 0, _len = scripts.length; _i < _len; _i++) { 61 | s = scripts[_i]; 62 | if (s.type === 'text/coffeescript') { 63 | _results.push(s); 64 | } 65 | } 66 | return _results; 67 | })(); 68 | index = 0; 69 | length = coffees.length; 70 | (execute = function() { 71 | var script; 72 | script = coffees[index++]; 73 | if ((script != null ? script.type : void 0) === 'text/coffeescript') { 74 | if (script.src) { 75 | return CoffeeScript.load(script.src, execute); 76 | } else { 77 | CoffeeScript.run(script.innerHTML); 78 | return execute(); 79 | } 80 | } 81 | })(); 82 | return null; 83 | }; 84 | 85 | if (window.addEventListener) { 86 | addEventListener('DOMContentLoaded', runScripts, false); 87 | } else { 88 | attachEvent('onload', runScripts); 89 | } 90 | 91 | }).call(this); 92 | -------------------------------------------------------------------------------- /src/toffee.jison: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // grammar file for Toffee Templating 4 | // 5 | // 6 | 7 | %lex 8 | %% 9 | 10 | "{##" return 'START_TOFFEE_COMMENT'; 11 | "##}" return 'END_TOFFEE_COMMENT'; 12 | ":}" return 'END_TOFFEE'; 13 | "{:" return 'START_TOFFEE'; 14 | "{#" return 'START_COFFEE'; 15 | "#}" return 'END_COFFEE'; 16 | [^{}#\\:\-]+|[\\{}#:\-] return 'CODE'; 17 | <> return 'EOF'; 18 | 19 | /lex 20 | 21 | %start starter 22 | 23 | %% 24 | 25 | starter 26 | : 27 | toffee_zone EOF { $$ = ["TOFFEE_ZONE", $1]; return $$;} 28 | ; 29 | 30 | 31 | toffee_zone 32 | : 33 | toffee_code { $$ = [$1]; } 34 | | 35 | toffee_code flip_to_coffee toffee_zone { $$ = $3; $3.splice(0,0,$1,$2); } 36 | | 37 | flip_to_coffee toffee_zone { $$ = $2; $2.splice(0,0,$1); } 38 | | 39 | toffee_code flip_to_toffee_comment toffee_zone { $$ = $3; $3.splice(0,0,$1); } 40 | | 41 | flip_to_toffee_comment toffee_zone { $$ = $2; } 42 | | 43 | { $$ = []; } 44 | ; 45 | 46 | flip_to_toffee_comment 47 | : 48 | START_TOFFEE_COMMENT toffee_commented_region END_TOFFEE_COMMENT {} 49 | ; 50 | 51 | toffee_commented_region 52 | : 53 | toffee_commented_region START_COFFEE 54 | | 55 | toffee_commented_region END_COFFEE 56 | | 57 | toffee_commented_region START_TOFFEE 58 | | 59 | toffee_commented_region END_TOFFEE 60 | | 61 | toffee_commented_region CODE 62 | | 63 | ; 64 | 65 | flip_to_coffee 66 | : 67 | START_COFFEE coffee_zone END_COFFEE { $$ = ["COFFEE_ZONE", $2]; } 68 | ; 69 | 70 | coffee_zone 71 | : 72 | coffee_code { $$ = [$1]; } 73 | | 74 | coffee_code flip_to_toffee coffee_zone { $$ = $3; $3.splice(0,0,$1,$2); } 75 | | 76 | flip_to_toffee coffee_zone { $$ = $2; $2.splice(0,0,$1); } 77 | | 78 | { $$ = []; } 79 | ; 80 | 81 | flip_to_toffee 82 | : 83 | START_TOFFEE toffee_zone END_TOFFEE { $$ = ["TOFFEE_ZONE", $2]; } 84 | ; 85 | 86 | 87 | toffee_code 88 | : 89 | code { $$ = ["TOFFEE", $1[0], $1[1] ]; } 90 | ; 91 | 92 | coffee_code 93 | : 94 | code { $$ = ["COFFEE", $1[0], $1[1] ]; } 95 | ; 96 | 97 | 98 | code 99 | : 100 | CODE { var ln = yylineno + 1 - $1.split("\n").length + 1; 101 | $$ = [$1, ln]; 102 | } 103 | | 104 | code CODE { var c = $1[0] + $2; 105 | var ln = yylineno + 1 - c.split("\n").length + 1; 106 | $$ = [c, ln]; 107 | } 108 | ; 109 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.7 2 | (function() { 3 | var __express, cacheless_engine, e, engine, getCommonHeaders, getCommonHeadersJs, ref, to_express, view; 4 | 5 | engine = require('./lib/engine').engine; 6 | 7 | ref = require('./lib/view'), view = ref.view, getCommonHeaders = ref.getCommonHeaders, getCommonHeadersJs = ref.getCommonHeadersJs; 8 | 9 | exports.engine = engine; 10 | 11 | exports.view = view; 12 | 13 | exports.getCommonHeaders = getCommonHeaders; 14 | 15 | exports.getCommonHeadersJs = getCommonHeadersJs; 16 | 17 | exports.expressEngine = e = new engine({ 18 | verbose: false, 19 | prettyPrintErrors: true 20 | }); 21 | 22 | exports.render = e.run; 23 | 24 | cacheless_engine = new engine({ 25 | verbose: false, 26 | prettyPrintErrors: true, 27 | cache: false 28 | }); 29 | 30 | exports.compileStr = function(template_str, options) { 31 | var v; 32 | v = new view(template_str, options); 33 | return function(x) { 34 | return v.run(x); 35 | }; 36 | }; 37 | 38 | to_express = exports.toExpress = function(eng) { 39 | return function(filename, options, cb) { 40 | return eng.run(filename, options, function(err, res) { 41 | if (err) { 42 | if (typeof err === "string") { 43 | err = new Error(err); 44 | } 45 | return cb(err); 46 | } else { 47 | return cb(null, res); 48 | } 49 | }); 50 | }; 51 | }; 52 | 53 | __express = exports.__express = to_express(e); 54 | 55 | exports.__consolidate_engine_render = function(filename, options, cb) { 56 | var eng; 57 | eng = options.cache ? e : cacheless_engine; 58 | return eng.run(filename, options, function(err, res) { 59 | return cb(err, res); 60 | }); 61 | }; 62 | 63 | exports.str_render = exports.strRender = function(template_str, options, cb) { 64 | var err, ref1, res, v; 65 | v = new view(template_str, options); 66 | ref1 = v.run(options), err = ref1[0], res = ref1[1]; 67 | return cb(err, res); 68 | }; 69 | 70 | exports.compile = require('./lib/view').expressCompile; 71 | 72 | exports.configurable_compile = function(source, opts) { 73 | var err, header, output, v; 74 | opts = opts || {}; 75 | opts.headers = opts.headers != null ? opts.headers : true; 76 | opts.filename = opts.filename || null; 77 | opts.to_coffee = opts.to_coffee || false; 78 | err = null; 79 | v = new view(source, { 80 | filename: opts.filename, 81 | bundlePath: opts.filename, 82 | browserMode: true 83 | }); 84 | if (opts.to_coffee) { 85 | output = v.toCoffee(); 86 | } else { 87 | output = v.toJavaScript(); 88 | } 89 | if (v.error) { 90 | throw v.error.e; 91 | } 92 | if (opts.headers) { 93 | header = getCommonHeadersJs(true, true, true); 94 | if (opts.coffee) { 95 | output = "`" + header + "`\n\n" + output; 96 | } else { 97 | output = header + "\n;\n" + output; 98 | } 99 | } 100 | return output; 101 | }; 102 | 103 | }).call(this); 104 | -------------------------------------------------------------------------------- /index.coffee: -------------------------------------------------------------------------------- 1 | # expose the render function 2 | {engine} = require('./lib/engine') 3 | {view, getCommonHeaders, getCommonHeadersJs} = require('./lib/view') 4 | 5 | exports.engine = engine 6 | exports.view = view 7 | exports.getCommonHeaders = getCommonHeaders 8 | exports.getCommonHeadersJs = getCommonHeadersJs 9 | 10 | exports.expressEngine = e = new engine { verbose: false, prettyPrintErrors: true } 11 | exports.render = e.run 12 | cacheless_engine = new engine { verbose: false, prettyPrintErrors: true, cache: false} 13 | 14 | # given a template string, returns a function that can be called 15 | # on an object to render it. 16 | # -------------------------------------------- 17 | 18 | exports.compileStr = (template_str, options) -> 19 | v = new view template_str, options 20 | return (x) -> v.run x 21 | 22 | # express 3.x support from a custom engine; 23 | # this function takes a toffee engine 24 | # and returns a function that matches the __express 25 | # standard. 26 | # -------------------------------------------- 27 | 28 | to_express = exports.toExpress = (eng) -> 29 | return (filename, options, cb) -> 30 | eng.run filename, options, (err, res) -> 31 | if err 32 | if typeof(err) is "string" 33 | err = new Error err 34 | cb err 35 | else 36 | cb null, res 37 | 38 | # express 3.x support using the default engine 39 | # -------------------------------------------- 40 | 41 | __express = exports.__express = to_express e 42 | 43 | # consolidate.js support, which doesn't want caching on by default 44 | # -------------------------------------------- 45 | 46 | exports.__consolidate_engine_render = (filename, options, cb) -> 47 | eng = if options.cache then e else cacheless_engine 48 | eng.run filename, options, (err, res) -> 49 | cb err, res 50 | 51 | # consolidate.js wants this, but it might generally be useful 52 | # -------------------------------------------- 53 | 54 | exports.str_render = exports.strRender = (template_str, options, cb) -> 55 | v = new view template_str, options 56 | [err, res] = v.run options 57 | cb err, res 58 | 59 | # express 2.x support 60 | # -------------------------------------------- 61 | 62 | exports.compile = require('./lib/view').expressCompile 63 | 64 | # better support for string compiling 65 | # -------------------------------------------- 66 | 67 | exports.configurable_compile = (source, opts) -> 68 | opts = opts or {} 69 | opts.headers = if opts.headers? then opts.headers else true 70 | opts.filename = opts.filename or null 71 | opts.to_coffee = opts.to_coffee or false 72 | err = null 73 | 74 | # this compiles an individual template that you've read while recursing: 75 | v = new view source, { 76 | filename: opts.filename 77 | bundlePath: opts.filename 78 | browserMode: true 79 | } 80 | if opts.to_coffee 81 | output = v.toCoffee() 82 | else 83 | output = v.toJavaScript() 84 | if v.error then throw v.error.e 85 | if opts.headers 86 | header = getCommonHeadersJs true, true, true 87 | if opts.coffee then output = "`#{header}`\n\n#{output}" 88 | else output = "#{header}\n;\n#{output}" 89 | return output 90 | -------------------------------------------------------------------------------- /lib/coffee-script/cake.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var CoffeeScript, cakefileDirectory, fatalError, fs, helpers, missingTask, oparse, options, optparse, path, printTasks, switches, tasks; 4 | 5 | fs = require('fs'); 6 | 7 | path = require('path'); 8 | 9 | helpers = require('./helpers'); 10 | 11 | optparse = require('./optparse'); 12 | 13 | CoffeeScript = require('./coffee-script'); 14 | 15 | tasks = {}; 16 | 17 | options = {}; 18 | 19 | switches = []; 20 | 21 | oparse = null; 22 | 23 | helpers.extend(global, { 24 | task: function(name, description, action) { 25 | var _ref; 26 | if (!action) { 27 | _ref = [description, action], action = _ref[0], description = _ref[1]; 28 | } 29 | return tasks[name] = { 30 | name: name, 31 | description: description, 32 | action: action 33 | }; 34 | }, 35 | option: function(letter, flag, description) { 36 | return switches.push([letter, flag, description]); 37 | }, 38 | invoke: function(name) { 39 | if (!tasks[name]) { 40 | missingTask(name); 41 | } 42 | return tasks[name].action(options); 43 | } 44 | }); 45 | 46 | exports.run = function() { 47 | var arg, args, _i, _len, _ref, _results; 48 | global.__originalDirname = fs.realpathSync('.'); 49 | process.chdir(cakefileDirectory(__originalDirname)); 50 | args = process.argv.slice(2); 51 | CoffeeScript.run(fs.readFileSync('Cakefile').toString(), { 52 | filename: 'Cakefile' 53 | }); 54 | oparse = new optparse.OptionParser(switches); 55 | if (!args.length) { 56 | return printTasks(); 57 | } 58 | try { 59 | options = oparse.parse(args); 60 | } catch (e) { 61 | return fatalError("" + e); 62 | } 63 | _ref = options["arguments"]; 64 | _results = []; 65 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 66 | arg = _ref[_i]; 67 | _results.push(invoke(arg)); 68 | } 69 | return _results; 70 | }; 71 | 72 | printTasks = function() { 73 | var cakefilePath, desc, name, relative, spaces, task; 74 | relative = path.relative || path.resolve; 75 | cakefilePath = path.join(relative(__originalDirname, process.cwd()), 'Cakefile'); 76 | console.log("" + cakefilePath + " defines the following tasks:\n"); 77 | for (name in tasks) { 78 | task = tasks[name]; 79 | spaces = 20 - name.length; 80 | spaces = spaces > 0 ? Array(spaces + 1).join(' ') : ''; 81 | desc = task.description ? "# " + task.description : ''; 82 | console.log("cake " + name + spaces + " " + desc); 83 | } 84 | if (switches.length) { 85 | return console.log(oparse.help()); 86 | } 87 | }; 88 | 89 | fatalError = function(message) { 90 | console.error(message + '\n'); 91 | console.log('To see a list of all tasks/options, run "cake"'); 92 | return process.exit(1); 93 | }; 94 | 95 | missingTask = function(task) { 96 | return fatalError("No such task: " + task); 97 | }; 98 | 99 | cakefileDirectory = function(dir) { 100 | var parent; 101 | if (path.existsSync(path.join(dir, 'Cakefile'))) { 102 | return dir; 103 | } 104 | parent = path.normalize(path.join(dir, '..')); 105 | if (parent !== dir) { 106 | return cakefileDirectory(parent); 107 | } 108 | throw new Error("Cakefile not found in " + (process.cwd())); 109 | }; 110 | 111 | }).call(this); 112 | -------------------------------------------------------------------------------- /test/generate_express_test.coffee: -------------------------------------------------------------------------------- 1 | {spawn, exec} = require 'child_process' 2 | fs = require 'fs' 3 | path = require 'path' 4 | coffee = require 'coffee-script' 5 | 6 | 7 | generateExpressTest = (cb) -> 8 | 9 | proc = spawn path.join(__dirname,"../node_modules/.bin/coffee"), ['./src/command_line.coffee', '-n', './test/cases', '-o', './test/express4/public/javascripts/test_cases.js'] 10 | proc.stderr.on 'data', (buffer) -> console.log buffer.toString() 11 | proc.stdout.on 'data', (buffer) -> console.log buffer.toString() 12 | proc.on 'exit', (status) -> 13 | if status isnt 0 14 | console.log "Error running command line. #{status}" 15 | process.exit 1 16 | cb() if typeof cb is 'function' 17 | 18 | {getCommonHeadersJs} = require '../lib/view' 19 | headers = getCommonHeadersJs true, true 20 | fs.writeFileSync "./test/express4/public/javascripts/toffee.js", headers, "utf8" 21 | 22 | # generate an index page that tests them all 23 | 24 | test_page = """ 25 | 26 | 27 | Testing Toffee in the Browser 28 | 29 | 30 | 31 | 43 | 49 | 50 | 51 |
#{t} 30 | 31 |
52 | 53 | """ 54 | 55 | case_dirs = fs.readdirSync "./test/cases/" 56 | 57 | for dir,i in case_dirs 58 | expected_output = fs.readFileSync "./test/cases/#{dir}/output.toffee", "utf8" 59 | if fs.existsSync "./test/cases/#{dir}/vars.coffee" 60 | coffee_vars = fs.readFileSync "./test/cases/#{dir}/vars.coffee", "utf8" 61 | js_vars = coffee.compile(coffee_vars, {bare: true}).replace(/;[ \n]*$/,'') 62 | else if fs.existsSync "./test/cases/#{dir}/vars.js" 63 | coffee_vars = fs.readFileSync "./test/cases/#{dir}/vars.js", "utf8" 64 | js_vars = coffee_vars; 65 | else 66 | if dir == "render_no_args" 67 | coffee_vars = "" 68 | js_vars = "" 69 | else 70 | coffee_vars = "{}" 71 | js_vars = "{}" 72 | rid = i 73 | test_page += """ 74 | \n\n\n 75 | 76 | 77 | 78 | 79 | 80 | 81 | 85 | \n\n\n 86 | """ 87 | 88 | test_page += """ 89 |
FILEEXPECTED OUTPUTSERVER RENDERBROWSER RENDER
#{dir}#{expected_output}\#{partial '../../cases/#{dir}/input.toffee', #{coffee_vars}}
90 | 91 | 92 | """ 93 | fs.writeFileSync "./test/express4/views/index.toffee", test_page, "utf8" 94 | 95 | exports.generate = generateExpressTest 96 | 97 | -------------------------------------------------------------------------------- /test/run_cases.iced: -------------------------------------------------------------------------------- 1 | {engine} = require '../lib/engine' 2 | fs = require 'fs' 3 | path = require 'path' 4 | Browser = require 'zombie' 5 | coffee = require 'coffee-script' 6 | tablify = require 'tablify' 7 | colors = require 'colors' 8 | jsdiff = require 'diff' 9 | 10 | regular_engine = new engine({ 11 | verbose: false 12 | prettyPrintErrors: false 13 | }) 14 | 15 | # --------------------------------------------------------------- 16 | 17 | MULTI_RUNS = 50 18 | 19 | file_cache = {} 20 | 21 | # --------------------------------------------------------------- 22 | 23 | read_file_sync = (fname) -> 24 | if not file_cache[fname]? 25 | file_cache[fname] = fs.readFileSync fname, "utf8" 26 | return file_cache[fname] 27 | 28 | # --------------------------------------------------------------- 29 | 30 | run_case_dir = (eng, dir, cb) -> 31 | start = Date.now() 32 | expected = read_file_sync "#{dir}/output.toffee" 33 | existsSync = if path.existsSync? then path.existsSync else fs.existsSync 34 | if existsSync "#{dir}/vars.coffee" 35 | txt = read_file_sync "#{dir}/vars.coffee" 36 | vars = coffee.compile(txt, {bare: true}) 37 | vars = eval "#{vars}" 38 | else if existsSync "#{dir}/vars.js" 39 | vars = read_file_sync "#{dir}/vars.js" 40 | vars = eval "(#{vars})" 41 | else 42 | vars = {} 43 | vars["rand_#{Math.random()}"] = ("foo" for i in [0...(~~(20000*Math.random()))]).join "" 44 | await eng.run "#{dir}/input.toffee", vars, defer err, res 45 | time_ms = Date.now() - start 46 | if err 47 | cb err, time_ms 48 | else 49 | if res isnt expected 50 | diff = jsdiff.diffLines res, expected 51 | delta = "" 52 | diff.forEach (part) -> 53 | c = if part.added then 'green' else if part.removed then 'red' else 'grey' 54 | v = part.value 55 | delta += v[c] 56 | cb "Failure in case #{dir}." + 57 | "#{delta}", time_ms 58 | else 59 | cb null, time_ms 60 | 61 | run_all_case_dirs = (eng, cb) -> 62 | start = Date.now() 63 | case_dirs = fs.readdirSync "#{__dirname}/cases/" 64 | for dir in case_dirs 65 | await run_case_dir eng, "#{__dirname}/cases/#{dir}", defer err, ms 66 | if err 67 | console.log err 68 | process.exit 1 69 | cb null, (Date.now() - start), case_dirs.length 70 | 71 | run_multiple_runs = (eng, num_runs, cb) -> 72 | total_tests = 0 73 | start = Date.now() 74 | for i in [0...num_runs] 75 | await setTimeout defer(), 1 76 | await run_all_case_dirs regular_engine, defer err, time, tests_run 77 | total_tests += tests_run 78 | cb null, (Date.now() - start), total_tests 79 | 80 | run_express_test = (cb) -> 81 | require('./express4/app').run -> 82 | browser = new Browser() 83 | browser.visit 'http://127.0.0.1:3033', (e) -> 84 | if e 85 | console.log e 86 | $ = browser.window.$ 87 | successes = $('.success').length 88 | fails = $('.fail').length 89 | if (fails is 0) and (successes > 0) 90 | console.log "Express SUCCESS: #{successes} succeeded, #{fails} failed" 91 | return cb() 92 | console.log "BROWSER ERROR! Server left running at http://localhost:3033 for your convenience" 93 | 94 | # ---------------------------------------------------------------- 95 | go = -> 96 | await run_all_case_dirs regular_engine, defer err, time, tests_run 97 | console.log "Regular Engine: SUCCESS for #{tests_run} cold tests in #{time}ms (#{(time/tests_run).toFixed 2}ms/test)" 98 | await run_multiple_runs regular_engine, MULTI_RUNS, defer err, time, tests_run 99 | console.log "Regular Engine: SUCCESS for #{tests_run} hot tests in #{time}ms (#{(time/tests_run).toFixed 2}ms/test)" 100 | await run_express_test defer() 101 | process.exit 0 102 | 103 | if not module.parent? 104 | go() 105 | 106 | else exports.test = go 107 | -------------------------------------------------------------------------------- /lib/coffee-script/scope.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var Scope, extend, last, _ref; 4 | 5 | _ref = require('./helpers'), extend = _ref.extend, last = _ref.last; 6 | 7 | exports.Scope = Scope = (function() { 8 | 9 | Scope.name = 'Scope'; 10 | 11 | Scope.root = null; 12 | 13 | function Scope(parent, expressions, method) { 14 | this.parent = parent; 15 | this.expressions = expressions; 16 | this.method = method; 17 | this.variables = [ 18 | { 19 | name: 'arguments', 20 | type: 'arguments' 21 | } 22 | ]; 23 | this.positions = {}; 24 | if (!this.parent) { 25 | Scope.root = this; 26 | } 27 | } 28 | 29 | Scope.prototype.add = function(name, type, immediate) { 30 | if (this.shared && !immediate) { 31 | return this.parent.add(name, type, immediate); 32 | } 33 | if (Object.prototype.hasOwnProperty.call(this.positions, name)) { 34 | return this.variables[this.positions[name]].type = type; 35 | } else { 36 | return this.positions[name] = this.variables.push({ 37 | name: name, 38 | type: type 39 | }) - 1; 40 | } 41 | }; 42 | 43 | Scope.prototype.find = function(name, options) { 44 | if (this.check(name, options)) { 45 | return true; 46 | } 47 | this.add(name, 'var'); 48 | return false; 49 | }; 50 | 51 | Scope.prototype.parameter = function(name) { 52 | if (this.shared && this.parent.check(name, true)) { 53 | return; 54 | } 55 | return this.add(name, 'param'); 56 | }; 57 | 58 | Scope.prototype.check = function(name, immediate) { 59 | var found, _ref1; 60 | found = !!this.type(name); 61 | if (found || immediate) { 62 | return found; 63 | } 64 | return !!((_ref1 = this.parent) != null ? _ref1.check(name) : void 0); 65 | }; 66 | 67 | Scope.prototype.temporary = function(name, index) { 68 | if (name.length > 1) { 69 | return '_' + name + (index > 1 ? index - 1 : ''); 70 | } else { 71 | return '_' + (index + parseInt(name, 36)).toString(36).replace(/\d/g, 'a'); 72 | } 73 | }; 74 | 75 | Scope.prototype.type = function(name) { 76 | var v, _i, _len, _ref1; 77 | _ref1 = this.variables; 78 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 79 | v = _ref1[_i]; 80 | if (v.name === name) { 81 | return v.type; 82 | } 83 | } 84 | return null; 85 | }; 86 | 87 | Scope.prototype.freeVariable = function(name, reserve) { 88 | var index, temp; 89 | if (reserve == null) { 90 | reserve = true; 91 | } 92 | index = 0; 93 | while (this.check((temp = this.temporary(name, index)))) { 94 | index++; 95 | } 96 | if (reserve) { 97 | this.add(temp, 'var', true); 98 | } 99 | return temp; 100 | }; 101 | 102 | Scope.prototype.assign = function(name, value) { 103 | this.add(name, { 104 | value: value, 105 | assigned: true 106 | }, true); 107 | return this.hasAssignments = true; 108 | }; 109 | 110 | Scope.prototype.hasDeclarations = function() { 111 | return !!this.declaredVariables().length; 112 | }; 113 | 114 | Scope.prototype.declaredVariables = function() { 115 | var realVars, tempVars, v, _i, _len, _ref1; 116 | realVars = []; 117 | tempVars = []; 118 | _ref1 = this.variables; 119 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 120 | v = _ref1[_i]; 121 | if (v.type === 'var') { 122 | (v.name.charAt(0) === '_' ? tempVars : realVars).push(v.name); 123 | } 124 | } 125 | return realVars.sort().concat(tempVars.sort()); 126 | }; 127 | 128 | Scope.prototype.assignedVariables = function() { 129 | var v, _i, _len, _ref1, _results; 130 | _ref1 = this.variables; 131 | _results = []; 132 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 133 | v = _ref1[_i]; 134 | if (v.type.assigned) { 135 | _results.push("" + v.name + " = " + v.type.value); 136 | } 137 | } 138 | return _results; 139 | }; 140 | 141 | return Scope; 142 | 143 | })(); 144 | 145 | }).call(this); 146 | -------------------------------------------------------------------------------- /lib/coffee-script/optparse.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, buildRule, buildRules, normalizeArguments; 4 | 5 | exports.OptionParser = OptionParser = (function() { 6 | 7 | OptionParser.name = 'OptionParser'; 8 | 9 | function OptionParser(rules, banner) { 10 | this.banner = banner; 11 | this.rules = buildRules(rules); 12 | } 13 | 14 | OptionParser.prototype.parse = function(args) { 15 | var arg, i, isOption, matchedRule, options, originalArgs, pos, rule, seenNonOptionArg, skippingArgument, value, _i, _j, _len, _len1, _ref; 16 | options = { 17 | "arguments": [] 18 | }; 19 | skippingArgument = false; 20 | originalArgs = args; 21 | args = normalizeArguments(args); 22 | for (i = _i = 0, _len = args.length; _i < _len; i = ++_i) { 23 | arg = args[i]; 24 | if (skippingArgument) { 25 | skippingArgument = false; 26 | continue; 27 | } 28 | if (arg === '--') { 29 | pos = originalArgs.indexOf('--'); 30 | options["arguments"] = options["arguments"].concat(originalArgs.slice(pos + 1)); 31 | break; 32 | } 33 | isOption = !!(arg.match(LONG_FLAG) || arg.match(SHORT_FLAG)); 34 | seenNonOptionArg = options["arguments"].length > 0; 35 | if (!seenNonOptionArg) { 36 | matchedRule = false; 37 | _ref = this.rules; 38 | for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { 39 | rule = _ref[_j]; 40 | if (rule.shortFlag === arg || rule.longFlag === arg) { 41 | value = true; 42 | if (rule.hasArgument) { 43 | skippingArgument = true; 44 | value = args[i + 1]; 45 | } 46 | options[rule.name] = rule.isList ? (options[rule.name] || []).concat(value) : value; 47 | matchedRule = true; 48 | break; 49 | } 50 | } 51 | if (isOption && !matchedRule) { 52 | throw new Error("unrecognized option: " + arg); 53 | } 54 | } 55 | if (seenNonOptionArg || !isOption) { 56 | options["arguments"].push(arg); 57 | } 58 | } 59 | return options; 60 | }; 61 | 62 | OptionParser.prototype.help = function() { 63 | var letPart, lines, rule, spaces, _i, _len, _ref; 64 | lines = []; 65 | if (this.banner) { 66 | lines.unshift("" + this.banner + "\n"); 67 | } 68 | _ref = this.rules; 69 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 70 | rule = _ref[_i]; 71 | spaces = 15 - rule.longFlag.length; 72 | spaces = spaces > 0 ? Array(spaces + 1).join(' ') : ''; 73 | letPart = rule.shortFlag ? rule.shortFlag + ', ' : ' '; 74 | lines.push(' ' + letPart + rule.longFlag + spaces + rule.description); 75 | } 76 | return "\n" + (lines.join('\n')) + "\n"; 77 | }; 78 | 79 | return OptionParser; 80 | 81 | })(); 82 | 83 | LONG_FLAG = /^(--\w[\w\-]*)/; 84 | 85 | SHORT_FLAG = /^(-\w)$/; 86 | 87 | MULTI_FLAG = /^-(\w{2,})/; 88 | 89 | OPTIONAL = /\[(\w+(\*?))\]/; 90 | 91 | buildRules = function(rules) { 92 | var tuple, _i, _len, _results; 93 | _results = []; 94 | for (_i = 0, _len = rules.length; _i < _len; _i++) { 95 | tuple = rules[_i]; 96 | if (tuple.length < 3) { 97 | tuple.unshift(null); 98 | } 99 | _results.push(buildRule.apply(null, tuple)); 100 | } 101 | return _results; 102 | }; 103 | 104 | buildRule = function(shortFlag, longFlag, description, options) { 105 | var match; 106 | if (options == null) { 107 | options = {}; 108 | } 109 | match = longFlag.match(OPTIONAL); 110 | longFlag = longFlag.match(LONG_FLAG)[1]; 111 | return { 112 | name: longFlag.substr(2), 113 | shortFlag: shortFlag, 114 | longFlag: longFlag, 115 | description: description, 116 | hasArgument: !!(match && match[1]), 117 | isList: !!(match && match[2]) 118 | }; 119 | }; 120 | 121 | normalizeArguments = function(args) { 122 | var arg, l, match, result, _i, _j, _len, _len1, _ref; 123 | args = args.slice(0); 124 | result = []; 125 | for (_i = 0, _len = args.length; _i < _len; _i++) { 126 | arg = args[_i]; 127 | if (match = arg.match(MULTI_FLAG)) { 128 | _ref = match[1].split(''); 129 | for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { 130 | l = _ref[_j]; 131 | result.push('-' + l); 132 | } 133 | } else { 134 | result.push(arg); 135 | } 136 | } 137 | return result; 138 | }; 139 | 140 | }).call(this); 141 | -------------------------------------------------------------------------------- /lib/coffee-script/coffee-script.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var Lexer, RESERVED, compile, fs, lexer, parser, path, vm, _ref, 4 | __hasProp = {}.hasOwnProperty; 5 | 6 | fs = require('fs'); 7 | 8 | path = require('path'); 9 | 10 | _ref = require('./lexer'), Lexer = _ref.Lexer, RESERVED = _ref.RESERVED; 11 | 12 | parser = require('./parser').parser; 13 | 14 | vm = require('vm'); 15 | 16 | if (require.extensions) { 17 | require.extensions['.coffee'] = function(module, filename) { 18 | var content; 19 | content = compile(fs.readFileSync(filename, 'utf8'), { 20 | filename: filename 21 | }); 22 | return module._compile(content, filename); 23 | }; 24 | } else if (require.registerExtension) { 25 | require.registerExtension('.coffee', function(content) { 26 | return compile(content); 27 | }); 28 | } 29 | 30 | exports.VERSION = '1.3.1'; 31 | 32 | exports.RESERVED = RESERVED; 33 | 34 | exports.helpers = require('./helpers'); 35 | 36 | exports.compile = compile = function(code, options) { 37 | var header, js, merge; 38 | if (options == null) { 39 | options = {}; 40 | } 41 | merge = exports.helpers.merge; 42 | try { 43 | js = (parser.parse(lexer.tokenize(code))).compile(options); 44 | if (!options.header) { 45 | return js; 46 | } 47 | } catch (err) { 48 | if (options.filename) { 49 | err.message = "In " + options.filename + ", " + err.message; 50 | } 51 | throw err; 52 | } 53 | header = "Generated by CoffeeScript " + this.VERSION; 54 | return "// " + header + "\n" + js; 55 | }; 56 | 57 | exports.tokens = function(code, options) { 58 | return lexer.tokenize(code, options); 59 | }; 60 | 61 | exports.nodes = function(source, options) { 62 | if (typeof source === 'string') { 63 | return parser.parse(lexer.tokenize(source, options)); 64 | } else { 65 | return parser.parse(source); 66 | } 67 | }; 68 | 69 | exports.run = function(code, options) { 70 | var mainModule; 71 | if (options == null) { 72 | options = {}; 73 | } 74 | mainModule = require.main; 75 | mainModule.filename = process.argv[1] = options.filename ? fs.realpathSync(options.filename) : '.'; 76 | mainModule.moduleCache && (mainModule.moduleCache = {}); 77 | mainModule.paths = require('module')._nodeModulePaths(path.dirname(fs.realpathSync(options.filename))); 78 | if (path.extname(mainModule.filename) !== '.coffee' || require.extensions) { 79 | return mainModule._compile(compile(code, options), mainModule.filename); 80 | } else { 81 | return mainModule._compile(code, mainModule.filename); 82 | } 83 | }; 84 | 85 | exports["eval"] = function(code, options) { 86 | var Module, Script, js, k, o, r, sandbox, v, _i, _len, _module, _ref1, _ref2, _require; 87 | if (options == null) { 88 | options = {}; 89 | } 90 | if (!(code = code.trim())) { 91 | return; 92 | } 93 | Script = vm.Script; 94 | if (Script) { 95 | if (options.sandbox != null) { 96 | if (options.sandbox instanceof Script.createContext().constructor) { 97 | sandbox = options.sandbox; 98 | } else { 99 | sandbox = Script.createContext(); 100 | _ref1 = options.sandbox; 101 | for (k in _ref1) { 102 | if (!__hasProp.call(_ref1, k)) continue; 103 | v = _ref1[k]; 104 | sandbox[k] = v; 105 | } 106 | } 107 | sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox; 108 | } else { 109 | sandbox = global; 110 | } 111 | sandbox.__filename = options.filename || 'eval'; 112 | sandbox.__dirname = path.dirname(sandbox.__filename); 113 | if (!(sandbox !== global || sandbox.module || sandbox.require)) { 114 | Module = require('module'); 115 | sandbox.module = _module = new Module(options.modulename || 'eval'); 116 | sandbox.require = _require = function(path) { 117 | return Module._load(path, _module, true); 118 | }; 119 | _module.filename = sandbox.__filename; 120 | _ref2 = Object.getOwnPropertyNames(require); 121 | for (_i = 0, _len = _ref2.length; _i < _len; _i++) { 122 | r = _ref2[_i]; 123 | if (r !== 'paths') { 124 | _require[r] = require[r]; 125 | } 126 | } 127 | _require.paths = _module.paths = Module._nodeModulePaths(process.cwd()); 128 | _require.resolve = function(request) { 129 | return Module._resolveFilename(request, _module); 130 | }; 131 | } 132 | } 133 | o = {}; 134 | for (k in options) { 135 | if (!__hasProp.call(options, k)) continue; 136 | v = options[k]; 137 | o[k] = v; 138 | } 139 | o.bare = true; 140 | js = compile(code, o); 141 | if (sandbox === global) { 142 | return vm.runInThisContext(js); 143 | } else { 144 | return vm.runInContext(js, sandbox); 145 | } 146 | }; 147 | 148 | lexer = new Lexer; 149 | 150 | parser.lexer = { 151 | lex: function() { 152 | var tag, _ref1; 153 | _ref1 = this.tokens[this.pos++] || [''], tag = _ref1[0], this.yytext = _ref1[1], this.yylineno = _ref1[2]; 154 | return tag; 155 | }, 156 | setInput: function(tokens) { 157 | this.tokens = tokens; 158 | return this.pos = 0; 159 | }, 160 | upcomingInput: function() { 161 | return ""; 162 | } 163 | }; 164 | 165 | parser.yy = require('./nodes'); 166 | 167 | }).call(this); 168 | -------------------------------------------------------------------------------- /src/command_line.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | {view, getCommonHeadersJs} = require '../lib/view' 4 | program = require 'commander' 5 | mkdirp = require 'mkdirp' 6 | 7 | # ----------------------------------------------------------------------------- 8 | 9 | getVersionNumber = -> 10 | p = fs.readFileSync "#{__dirname}/../package.json", "utf8" 11 | o = JSON.parse p 12 | o.version 13 | 14 | # ----------------------------------------------------------------------------- 15 | 16 | program.on '--help', -> 17 | console.log " 18 | \n Examples: 19 | \n 20 | \n toffee views # recurses through views and builds views.js 21 | \n toffee foo.toffee # builds foo.js 22 | \n toffee views -o templates # builds templates.js 23 | \n toffee -p foo.toffee # outputs JS to stdout 24 | \n 25 | \n 26 | \n Then use in your : 27 | \n 28 | \n 29 | \n 33 | \n 34 | " 35 | 36 | program.version(getVersionNumber()) 37 | .option('-o, --output [path]', 'file (bundles all output into a single .js)') 38 | .option('-d, --output_dir [path]', 'compiles templates into parallel .js files') 39 | .option('-p, --print', 'print to stdout') 40 | .option('-c, --coffee', 'output to CoffeeScript (not JS)') 41 | .option('-b, --bundle_path [path]', 'bundle_path (instead of "/") for templates') 42 | .option('-n, --no_headers', 'exclude boilerplate toffee (requires toffee.js included separately)') 43 | .parse process.argv 44 | 45 | # ----------------------------------------------------------------------------- 46 | 47 | compile = (start_path, full_path) -> 48 | ### 49 | e.g., if start_path is /foo/bar 50 | and path is /foo/bar/car/thing.toffee 51 | ### 52 | source = fs.readFileSync full_path, 'utf8' 53 | bundle_path = full_path[start_path.length...] 54 | 55 | if start_path is full_path 56 | bundle_path = "/" + path.basename full_path 57 | 58 | if program.bundle_path 59 | bundle_path = path.normalize "#{program.bundle_path}/#{bundle_path}" 60 | 61 | v = new view source, 62 | fileName: full_path 63 | bundlePath: bundle_path 64 | browserMode: true 65 | if program.coffee 66 | output = v.toCoffee() 67 | else 68 | output = v.toJavaScript() 69 | if v.error 70 | process.stderr.write v.error.getPrettyPrintText() 71 | process.exit 1 72 | 73 | [output, bundle_path] 74 | 75 | # ----------------------------------------------------------------------------- 76 | 77 | recurseRun = (start_path, curr_path, out_text) -> 78 | stats = fs.statSync curr_path 79 | if stats.isDirectory() 80 | files = fs.readdirSync curr_path 81 | for file in files 82 | sub_path = path.normalize "#{curr_path}/#{file}" 83 | if file.match /\.toffee$/ 84 | out_text = recurseRun start_path, sub_path, out_text 85 | else if not (file in ['.','..']) 86 | sub_stats = fs.statSync sub_path 87 | if sub_stats.isDirectory() 88 | out_text = recurseRun start_path, sub_path, out_text 89 | else 90 | comp = compile start_path, curr_path 91 | out_text += "\n;\n" + comp[0] 92 | if program.output_dir 93 | file_out_path = path.normalize "#{program.output_dir}/#{comp[1]}" 94 | file_out_path = "#{path.dirname file_out_path}/#{path.basename file_out_path, '.toffee'}" 95 | file_out_path += if program.coffee then '.coffee' else '.js' 96 | if not program.print 97 | console.log "Outputting #{file_out_path}" 98 | mkdirp.sync path.dirname file_out_path 99 | fs.writeFileSync file_out_path, maybeAttachHeaders(comp[0]), "utf8" 100 | 101 | return out_text 102 | 103 | # ----------------------------------------------------------------------------- 104 | 105 | maybeAttachHeaders = (pre_output) -> 106 | if program.no_headers 107 | return pre_output 108 | else 109 | header_out = getCommonHeadersJs true, true, true 110 | if program.coffee 111 | return "`#{header_out}`\n\n#{pre_output}" 112 | else 113 | return "#{header_out}\n;\n#{pre_output}" 114 | 115 | # ----------------------------------------------------------------------------- 116 | 117 | run = exports.run = -> 118 | 119 | if program.args.length isnt 1 120 | console.log "Unexpected input. toffee --help for examples" 121 | console.log program.args 122 | process.exit 1 123 | else 124 | try 125 | start_path = fs.realpathSync program.args[0] 126 | catch e 127 | console.log "Input file/path not found. toffee --help for examples" 128 | process.exit 1 129 | 130 | if program.output_dir 131 | try 132 | mkdirp.sync program.output_dir 133 | catch e 134 | console.log "Couldn't make/use #{program.output_dir}; #{e}" 135 | process.exit 1 136 | 137 | start_path = path.normalize start_path 138 | template_out = recurseRun start_path, start_path, '' 139 | out_text = maybeAttachHeaders template_out 140 | 141 | if program.print 142 | console.log out_text 143 | 144 | if program.output 145 | try 146 | console.log "Writing #{program.output}" 147 | fs.writeFileSync program.output, out_text, "utf8" 148 | catch e 149 | console.log e 150 | process.exit 1 151 | 152 | 153 | # ----------------------------------------------------------------------------- 154 | 155 | if require.main is module 156 | run() 157 | -------------------------------------------------------------------------------- /test/express4_error_handling/public/javascripts/toffee.js: -------------------------------------------------------------------------------- 1 | var toffee; 2 | 3 | 4 | 5 | if (typeof toffee === "undefined" || toffee === null) { 6 | toffee = {}; 7 | } 8 | 9 | if (!toffee.templates) { 10 | toffee.templates = {}; 11 | } 12 | 13 | toffee.states = { 14 | "TOFFEE": 1, 15 | "COFFEE": 2 16 | }; 17 | 18 | toffee.__json = function(locals, o) { 19 | if (o == null) { 20 | return "null"; 21 | } else { 22 | return "" + JSON.stringify(o).replace(//g, '\\u003E').replace(/&/g, '\\u0026'); 23 | } 24 | }; 25 | 26 | toffee.__raw = function(locals, o) { 27 | return o; 28 | }; 29 | 30 | toffee.__html = function(locals, o) { 31 | return ("" + o).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); 32 | }; 33 | 34 | toffee.__escape = function(locals, o) { 35 | var ae; 36 | if (locals.__toffee.autoEscape != null) { 37 | ae = locals.__toffee.autoEscape; 38 | } else if (true) { 39 | ae = true; 40 | } else { 41 | ae = true; 42 | } 43 | if (ae) { 44 | if (o === void 0) { 45 | return ''; 46 | } 47 | if ((o != null) && (typeof o) === "object") { 48 | return locals.json(o); 49 | } 50 | return locals.html(o); 51 | } 52 | return o; 53 | }; 54 | 55 | toffee.__augmentLocals = function(locals, bundle_path) { 56 | var _l, _t; 57 | _l = locals; 58 | _t = _l.__toffee = { 59 | out: [] 60 | }; 61 | if (_l.print == null) { 62 | _l.print = function(o) { 63 | return toffee.__print(_l, o); 64 | }; 65 | } 66 | if (_l.json == null) { 67 | _l.json = function(o) { 68 | return toffee.__json(_l, o); 69 | }; 70 | } 71 | if (_l.raw == null) { 72 | _l.raw = function(o) { 73 | return toffee.__raw(_l, o); 74 | }; 75 | } 76 | if (_l.html == null) { 77 | _l.html = function(o) { 78 | return toffee.__html(_l, o); 79 | }; 80 | } 81 | if (_l.escape == null) { 82 | _l.escape = function(o) { 83 | return toffee.__escape(_l, o); 84 | }; 85 | } 86 | if (_l.partial == null) { 87 | _l.partial = function(path, vars) { 88 | return toffee.__partial(toffee.templates["" + bundle_path], _l, path, vars); 89 | }; 90 | } 91 | if (_l.snippet == null) { 92 | _l.snippet = function(path, vars) { 93 | return toffee.__snippet(toffee.templates["" + bundle_path], _l, path, vars); 94 | }; 95 | } 96 | if (_l.load == null) { 97 | _l.load = function(path, vars) { 98 | return toffee.__load(toffee.templates["" + bundle_path], _l, path, vars); 99 | }; 100 | } 101 | _t.print = _l.print; 102 | _t.json = _l.json; 103 | _t.raw = _l.raw; 104 | _t.html = _l.html; 105 | _t.escape = _l.escape; 106 | _t.partial = _l.partial; 107 | _t.snippet = _l.snippet; 108 | return _t.load = _l.load; 109 | }; 110 | 111 | toffee.__print = function(locals, o) { 112 | if (locals.__toffee.state === toffee.states.COFFEE) { 113 | locals.__toffee.out.push(o); 114 | return ''; 115 | } else { 116 | return "" + o; 117 | } 118 | }; 119 | 120 | toffee.__normalize = function(path) { 121 | var np, part, parts, _i, _len; 122 | if ((path == null) || path === "/") { 123 | return path; 124 | } else { 125 | parts = path.split("/"); 126 | np = []; 127 | if (parts[0]) { 128 | np.push(''); 129 | } 130 | for (_i = 0, _len = parts.length; _i < _len; _i++) { 131 | part = parts[_i]; 132 | if (part === "..") { 133 | if (np.length > 1) { 134 | np.pop(); 135 | } else { 136 | np.push(part); 137 | } 138 | } else { 139 | if (part !== ".") { 140 | np.push(part); 141 | } 142 | } 143 | } 144 | path = np.join("/"); 145 | if (!path) { 146 | path = "/"; 147 | } 148 | return path; 149 | } 150 | }; 151 | 152 | toffee.__partial = function(parent_tmpl, parent_locals, path, vars) { 153 | path = toffee.__normalize(parent_tmpl.bundlePath + "/../" + path); 154 | return toffee.__inlineInclude(path, vars, parent_locals); 155 | }; 156 | 157 | toffee.__snippet = function(parent_tmpl, parent_locals, path, vars) { 158 | path = toffee.__normalize(parent_tmpl.bundlePath + "/../" + path); 159 | vars = vars != null ? vars : {}; 160 | vars.__toffee = vars.__toffee || {}; 161 | vars.__toffee.noInheritance = true; 162 | return toffee.__inlineInclude(path, vars, parent_locals); 163 | }; 164 | 165 | toffee.__load = function(parent_tmpl, parent_locals, path, vars) { 166 | path = toffee.__normalize(parent_tmpl.bundlePath + "/../" + path); 167 | vars = vars != null ? vars : {}; 168 | vars.__toffee = vars.__toffee || {}; 169 | vars.__toffee.repress = true; 170 | return toffee.__inlineInclude(path, vars, parent_locals); 171 | }; 172 | 173 | toffee.__inlineInclude = function(path, locals, parent_locals) { 174 | var k, options, res, reserved, v, _i, _len, _ref, _ref1; 175 | options = locals || {}; 176 | options.passback = {}; 177 | options.__toffee = options.__toffee || {}; 178 | reserved = {}; 179 | _ref = ["passback", "load", "print", "partial", "snippet", "layout", "__toffee", "postProcess"]; 180 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 181 | k = _ref[_i]; 182 | reserved[k] = true; 183 | } 184 | if (!options.__toffee.noInheritance) { 185 | for (k in parent_locals) { 186 | v = parent_locals[k]; 187 | if ((locals != null ? locals[k] : void 0) == null) { 188 | if (reserved[k] == null) { 189 | options[k] = v; 190 | } 191 | } 192 | } 193 | } 194 | if (!toffee.templates[path]) { 195 | return "Inline toffee include: Could not find " + path; 196 | } else { 197 | res = toffee.templates[path].pub(options); 198 | _ref1 = options.passback; 199 | for (k in _ref1) { 200 | v = _ref1[k]; 201 | parent_locals[k] = v; 202 | } 203 | return res; 204 | } 205 | }; 206 | -------------------------------------------------------------------------------- /lib/command_line.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.7 2 | (function() { 3 | var compile, fs, getCommonHeadersJs, getVersionNumber, maybeAttachHeaders, mkdirp, path, program, recurseRun, ref, run, view; 4 | 5 | fs = require('fs'); 6 | 7 | path = require('path'); 8 | 9 | ref = require('../lib/view'), view = ref.view, getCommonHeadersJs = ref.getCommonHeadersJs; 10 | 11 | program = require('commander'); 12 | 13 | mkdirp = require('mkdirp'); 14 | 15 | getVersionNumber = function() { 16 | var o, p; 17 | p = fs.readFileSync(__dirname + "/../package.json", "utf8"); 18 | o = JSON.parse(p); 19 | return o.version; 20 | }; 21 | 22 | program.on('--help', function() { 23 | return console.log("\n Examples: \n \n toffee views # recurses through views and builds views.js \n toffee foo.toffee # builds foo.js \n toffee views -o templates # builds templates.js \n toffee -p foo.toffee # outputs JS to stdout \n \n \n Then use in your : \n \n \n \n"); 24 | }); 25 | 26 | program.version(getVersionNumber()).option('-o, --output [path]', 'file (bundles all output into a single .js)').option('-d, --output_dir [path]', 'compiles templates into parallel .js files').option('-p, --print', 'print to stdout').option('-c, --coffee', 'output to CoffeeScript (not JS)').option('-b, --bundle_path [path]', 'bundle_path (instead of "/") for templates').option('-n, --no_headers', 'exclude boilerplate toffee (requires toffee.js included separately)').parse(process.argv); 27 | 28 | compile = function(start_path, full_path) { 29 | 30 | /* 31 | e.g., if start_path is /foo/bar 32 | and path is /foo/bar/car/thing.toffee 33 | */ 34 | var bundle_path, output, source, v; 35 | source = fs.readFileSync(full_path, 'utf8'); 36 | bundle_path = full_path.slice(start_path.length); 37 | if (start_path === full_path) { 38 | bundle_path = "/" + path.basename(full_path); 39 | } 40 | if (program.bundle_path) { 41 | bundle_path = path.normalize(program.bundle_path + "/" + bundle_path); 42 | } 43 | v = new view(source, { 44 | fileName: full_path, 45 | bundlePath: bundle_path, 46 | browserMode: true 47 | }); 48 | if (program.coffee) { 49 | output = v.toCoffee(); 50 | } else { 51 | output = v.toJavaScript(); 52 | } 53 | if (v.error) { 54 | process.stderr.write(v.error.getPrettyPrintText()); 55 | process.exit(1); 56 | } 57 | return [output, bundle_path]; 58 | }; 59 | 60 | recurseRun = function(start_path, curr_path, out_text) { 61 | var comp, file, file_out_path, files, i, len, stats, sub_path, sub_stats; 62 | stats = fs.statSync(curr_path); 63 | if (stats.isDirectory()) { 64 | files = fs.readdirSync(curr_path); 65 | for (i = 0, len = files.length; i < len; i++) { 66 | file = files[i]; 67 | sub_path = path.normalize(curr_path + "/" + file); 68 | if (file.match(/\.toffee$/)) { 69 | out_text = recurseRun(start_path, sub_path, out_text); 70 | } else if (!(file === '.' || file === '..')) { 71 | sub_stats = fs.statSync(sub_path); 72 | if (sub_stats.isDirectory()) { 73 | out_text = recurseRun(start_path, sub_path, out_text); 74 | } 75 | } 76 | } 77 | } else { 78 | comp = compile(start_path, curr_path); 79 | out_text += "\n;\n" + comp[0]; 80 | if (program.output_dir) { 81 | file_out_path = path.normalize(program.output_dir + "/" + comp[1]); 82 | file_out_path = (path.dirname(file_out_path)) + "/" + (path.basename(file_out_path, '.toffee')); 83 | file_out_path += program.coffee ? '.coffee' : '.js'; 84 | if (!program.print) { 85 | console.log("Outputting " + file_out_path); 86 | } 87 | mkdirp.sync(path.dirname(file_out_path)); 88 | fs.writeFileSync(file_out_path, maybeAttachHeaders(comp[0]), "utf8"); 89 | } 90 | } 91 | return out_text; 92 | }; 93 | 94 | maybeAttachHeaders = function(pre_output) { 95 | var header_out; 96 | if (program.no_headers) { 97 | return pre_output; 98 | } else { 99 | header_out = getCommonHeadersJs(true, true, true); 100 | if (program.coffee) { 101 | return "`" + header_out + "`\n\n" + pre_output; 102 | } else { 103 | return header_out + "\n;\n" + pre_output; 104 | } 105 | } 106 | }; 107 | 108 | run = exports.run = function() { 109 | var e, out_text, start_path, template_out; 110 | if (program.args.length !== 1) { 111 | console.log("Unexpected input. toffee --help for examples"); 112 | console.log(program.args); 113 | return process.exit(1); 114 | } else { 115 | try { 116 | start_path = fs.realpathSync(program.args[0]); 117 | } catch (error) { 118 | e = error; 119 | console.log("Input file/path not found. toffee --help for examples"); 120 | process.exit(1); 121 | } 122 | if (program.output_dir) { 123 | try { 124 | mkdirp.sync(program.output_dir); 125 | } catch (error) { 126 | e = error; 127 | console.log("Couldn't make/use " + program.output_dir + "; " + e); 128 | process.exit(1); 129 | } 130 | } 131 | start_path = path.normalize(start_path); 132 | template_out = recurseRun(start_path, start_path, ''); 133 | out_text = maybeAttachHeaders(template_out); 134 | if (program.print) { 135 | console.log(out_text); 136 | } 137 | if (program.output) { 138 | try { 139 | console.log("Writing " + program.output); 140 | return fs.writeFileSync(program.output, out_text, "utf8"); 141 | } catch (error) { 142 | e = error; 143 | console.log(e); 144 | return process.exit(1); 145 | } 146 | } 147 | } 148 | }; 149 | 150 | if (require.main === module) { 151 | run(); 152 | } 153 | 154 | }).call(this); 155 | -------------------------------------------------------------------------------- /toffee.js: -------------------------------------------------------------------------------- 1 | var toffee; 2 | 3 | if (typeof toffee === "undefined" || toffee === null) { 4 | toffee = {}; 5 | } 6 | 7 | if (!toffee.templates) { 8 | toffee.templates = {}; 9 | } 10 | 11 | toffee.states = { 12 | "TOFFEE": 1, 13 | "COFFEE": 2 14 | }; 15 | 16 | toffee.__json = function(locals, o, opts) { 17 | opts || (opts = {}); 18 | opts.indent || (opts.indent = ""); 19 | if (o == null) { 20 | return "null"; 21 | } else { 22 | return "" + JSON.stringify(o, null, opts.indent).replace(//g, '\\u003E').replace(/&/g, '\\u0026').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029').replace(/\u200e/g, '\\u200e').replace(/\u200f/g, '\\u200f').replace(/\u202a/g, '\\u202a').replace(/\u202b/g, '\\u202b').replace(/\u202c/g, '\\u202c').replace(/\u202d/g, '\\u202d').replace(/\u202e/g, '\\u202e').replace(/\u206a/g, '\\u206a').replace(/\u206b/g, '\\u206b').replace(/\u206c/g, '\\u206c').replace(/\u206d/g, '\\u206d').replace(/\u206e/g, '\\u206e').replace(/\u206f/g, '\\u206f').replace(/\u2066/g, '\\u2066').replace(/\u2067/g, '\\u2067').replace(/\u2068/g, '\\u2068').replace(/\u2069/g, '\\u2069'); 23 | } 24 | }; 25 | 26 | toffee.__raw = function(locals, o) { 27 | return o; 28 | }; 29 | 30 | toffee.__html = function(locals, o) { 31 | return ("" + o).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/\u200e/g, '').replace(/\u200f/g, '').replace(/\u202a/g, '').replace(/\u202b/g, '').replace(/\u202c/g, '').replace(/\u202d/g, '').replace(/\u202e/g, '').replace(/\u206a/g, '').replace(/\u206b/g, '').replace(/\u206c/g, '').replace(/\u206d/g, '').replace(/\u206e/g, '').replace(/\u206f/g, '').replace(/\u2066/g, '').replace(/\u2067/g, '').replace(/\u2068/g, '').replace(/\u2069/g, ''); 32 | }; 33 | 34 | toffee.__escape = function(locals, o) { 35 | var ae; 36 | if (locals.__toffee.autoEscape != null) { 37 | ae = locals.__toffee.autoEscape; 38 | } else if (true) { 39 | ae = true; 40 | } else { 41 | ae = true; 42 | } 43 | if (ae) { 44 | if (o === void 0) { 45 | return ''; 46 | } 47 | if ((o != null) && (typeof o) === "object") { 48 | return locals.json(o); 49 | } 50 | return locals.html(o); 51 | } 52 | return o; 53 | }; 54 | 55 | toffee.__augmentLocals = function(locals, bundle_path) { 56 | var _l, _t; 57 | _l = locals; 58 | _t = _l.__toffee = { 59 | out: [] 60 | }; 61 | if (_l.print == null) { 62 | _l.print = function(o) { 63 | return toffee.__print(_l, o); 64 | }; 65 | } 66 | if (_l.json == null) { 67 | _l.json = function(o, opts) { 68 | return toffee.__json(_l, o, opts); 69 | }; 70 | } 71 | if (_l.raw == null) { 72 | _l.raw = function(o) { 73 | return toffee.__raw(_l, o); 74 | }; 75 | } 76 | if (_l.html == null) { 77 | _l.html = function(o) { 78 | return toffee.__html(_l, o); 79 | }; 80 | } 81 | if (_l.escape == null) { 82 | _l.escape = function(o) { 83 | return toffee.__escape(_l, o); 84 | }; 85 | } 86 | if (_l.partial == null) { 87 | _l.partial = function(path, vars) { 88 | return toffee.__partial(toffee.templates["" + bundle_path], _l, path, vars); 89 | }; 90 | } 91 | if (_l.snippet == null) { 92 | _l.snippet = function(path, vars) { 93 | return toffee.__snippet(toffee.templates["" + bundle_path], _l, path, vars); 94 | }; 95 | } 96 | if (_l.load == null) { 97 | _l.load = function(path, vars) { 98 | return toffee.__load(toffee.templates["" + bundle_path], _l, path, vars); 99 | }; 100 | } 101 | _t.print = _l.print; 102 | _t.json = _l.json; 103 | _t.raw = _l.raw; 104 | _t.html = _l.html; 105 | _t.escape = _l.escape; 106 | _t.partial = _l.partial; 107 | _t.snippet = _l.snippet; 108 | return _t.load = _l.load; 109 | }; 110 | 111 | toffee.__print = function(locals, o) { 112 | if (locals.__toffee.state === toffee.states.COFFEE) { 113 | locals.__toffee.out.push(o); 114 | return ''; 115 | } else { 116 | return "" + o; 117 | } 118 | }; 119 | 120 | toffee.__normalize = function(path) { 121 | var np, part, parts, _i, _len; 122 | if ((path == null) || path === "/") { 123 | return path; 124 | } else { 125 | parts = path.split("/"); 126 | np = []; 127 | if (parts[0]) { 128 | np.push(''); 129 | } 130 | for (_i = 0, _len = parts.length; _i < _len; _i++) { 131 | part = parts[_i]; 132 | if (part === "..") { 133 | if (np.length > 1) { 134 | np.pop(); 135 | } else { 136 | np.push(part); 137 | } 138 | } else { 139 | if (part !== ".") { 140 | np.push(part); 141 | } 142 | } 143 | } 144 | path = np.join("/"); 145 | if (!path) { 146 | path = "/"; 147 | } 148 | return path; 149 | } 150 | }; 151 | 152 | toffee.__partial = function(parent_tmpl, parent_locals, path, vars) { 153 | path = toffee.__normalize(parent_tmpl.bundlePath + "/../" + path); 154 | return toffee.__inlineInclude(path, vars, parent_locals); 155 | }; 156 | 157 | toffee.__snippet = function(parent_tmpl, parent_locals, path, vars) { 158 | path = toffee.__normalize(parent_tmpl.bundlePath + "/../" + path); 159 | vars = vars != null ? vars : {}; 160 | vars.__toffee = vars.__toffee || {}; 161 | vars.__toffee.noInheritance = true; 162 | return toffee.__inlineInclude(path, vars, parent_locals); 163 | }; 164 | 165 | toffee.__load = function(parent_tmpl, parent_locals, path, vars) { 166 | path = toffee.__normalize(parent_tmpl.bundlePath + "/../" + path); 167 | vars = vars != null ? vars : {}; 168 | vars.__toffee = vars.__toffee || {}; 169 | vars.__toffee.repress = true; 170 | return toffee.__inlineInclude(path, vars, parent_locals); 171 | }; 172 | 173 | toffee.__inlineInclude = function(path, locals, parent_locals) { 174 | var k, options, res, reserved, v, _i, _len, _ref, _ref1; 175 | options = locals || {}; 176 | options.passback = {}; 177 | options.__toffee = options.__toffee || {}; 178 | reserved = {}; 179 | _ref = ["passback", "load", "print", "partial", "snippet", "layout", "__toffee", "postProcess"]; 180 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 181 | k = _ref[_i]; 182 | reserved[k] = true; 183 | } 184 | if (!options.__toffee.noInheritance) { 185 | for (k in parent_locals) { 186 | v = parent_locals[k]; 187 | if ((locals != null ? locals[k] : void 0) == null) { 188 | if (reserved[k] == null) { 189 | options[k] = v; 190 | } 191 | } 192 | } 193 | } 194 | if (!toffee.templates[path]) { 195 | return "Inline toffee include: Could not find " + path; 196 | } else { 197 | res = toffee.templates[path].pub(options); 198 | _ref1 = options.passback; 199 | for (k in _ref1) { 200 | v = _ref1[k]; 201 | parent_locals[k] = v; 202 | } 203 | return res; 204 | } 205 | }; 206 | -------------------------------------------------------------------------------- /test/express4/public/javascripts/toffee.js: -------------------------------------------------------------------------------- 1 | var toffee; 2 | 3 | if (typeof toffee === "undefined" || toffee === null) { 4 | toffee = {}; 5 | } 6 | 7 | if (!toffee.templates) { 8 | toffee.templates = {}; 9 | } 10 | 11 | toffee.states = { 12 | "TOFFEE": 1, 13 | "COFFEE": 2 14 | }; 15 | 16 | toffee.__json = function(locals, o, opts) { 17 | opts || (opts = {}); 18 | opts.indent || (opts.indent = ""); 19 | if (o == null) { 20 | return "null"; 21 | } else { 22 | return "" + JSON.stringify(o, null, opts.indent).replace(//g, '\\u003E').replace(/&/g, '\\u0026').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029').replace(/\u200e/g, '\\u200e').replace(/\u200f/g, '\\u200f').replace(/\u202a/g, '\\u202a').replace(/\u202b/g, '\\u202b').replace(/\u202c/g, '\\u202c').replace(/\u202d/g, '\\u202d').replace(/\u202e/g, '\\u202e').replace(/\u206a/g, '\\u206a').replace(/\u206b/g, '\\u206b').replace(/\u206c/g, '\\u206c').replace(/\u206d/g, '\\u206d').replace(/\u206e/g, '\\u206e').replace(/\u206f/g, '\\u206f').replace(/\u2066/g, '\\u2066').replace(/\u2067/g, '\\u2067').replace(/\u2068/g, '\\u2068').replace(/\u2069/g, '\\u2069'); 23 | } 24 | }; 25 | 26 | toffee.__raw = function(locals, o) { 27 | return o; 28 | }; 29 | 30 | toffee.__html = function(locals, o) { 31 | return ("" + o).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/\u200e/g, '').replace(/\u200f/g, '').replace(/\u202a/g, '').replace(/\u202b/g, '').replace(/\u202c/g, '').replace(/\u202d/g, '').replace(/\u202e/g, '').replace(/\u206a/g, '').replace(/\u206b/g, '').replace(/\u206c/g, '').replace(/\u206d/g, '').replace(/\u206e/g, '').replace(/\u206f/g, '').replace(/\u2066/g, '').replace(/\u2067/g, '').replace(/\u2068/g, '').replace(/\u2069/g, ''); 32 | }; 33 | 34 | toffee.__escape = function(locals, o) { 35 | var ae; 36 | if (locals.__toffee.autoEscape != null) { 37 | ae = locals.__toffee.autoEscape; 38 | } else if (true) { 39 | ae = true; 40 | } else { 41 | ae = true; 42 | } 43 | if (ae) { 44 | if (o === void 0) { 45 | return ''; 46 | } 47 | if ((o != null) && (typeof o) === "object") { 48 | return locals.json(o); 49 | } 50 | return locals.html(o); 51 | } 52 | return o; 53 | }; 54 | 55 | toffee.__augmentLocals = function(locals, bundle_path) { 56 | var _l, _t; 57 | _l = locals; 58 | _t = _l.__toffee = { 59 | out: [] 60 | }; 61 | if (_l.print == null) { 62 | _l.print = function(o) { 63 | return toffee.__print(_l, o); 64 | }; 65 | } 66 | if (_l.json == null) { 67 | _l.json = function(o, opts) { 68 | return toffee.__json(_l, o, opts); 69 | }; 70 | } 71 | if (_l.raw == null) { 72 | _l.raw = function(o) { 73 | return toffee.__raw(_l, o); 74 | }; 75 | } 76 | if (_l.html == null) { 77 | _l.html = function(o) { 78 | return toffee.__html(_l, o); 79 | }; 80 | } 81 | if (_l.escape == null) { 82 | _l.escape = function(o) { 83 | return toffee.__escape(_l, o); 84 | }; 85 | } 86 | if (_l.partial == null) { 87 | _l.partial = function(path, vars) { 88 | return toffee.__partial(toffee.templates["" + bundle_path], _l, path, vars); 89 | }; 90 | } 91 | if (_l.snippet == null) { 92 | _l.snippet = function(path, vars) { 93 | return toffee.__snippet(toffee.templates["" + bundle_path], _l, path, vars); 94 | }; 95 | } 96 | if (_l.load == null) { 97 | _l.load = function(path, vars) { 98 | return toffee.__load(toffee.templates["" + bundle_path], _l, path, vars); 99 | }; 100 | } 101 | _t.print = _l.print; 102 | _t.json = _l.json; 103 | _t.raw = _l.raw; 104 | _t.html = _l.html; 105 | _t.escape = _l.escape; 106 | _t.partial = _l.partial; 107 | _t.snippet = _l.snippet; 108 | return _t.load = _l.load; 109 | }; 110 | 111 | toffee.__print = function(locals, o) { 112 | if (locals.__toffee.state === toffee.states.COFFEE) { 113 | locals.__toffee.out.push(o); 114 | return ''; 115 | } else { 116 | return "" + o; 117 | } 118 | }; 119 | 120 | toffee.__normalize = function(path) { 121 | var np, part, parts, _i, _len; 122 | if ((path == null) || path === "/") { 123 | return path; 124 | } else { 125 | parts = path.split("/"); 126 | np = []; 127 | if (parts[0]) { 128 | np.push(''); 129 | } 130 | for (_i = 0, _len = parts.length; _i < _len; _i++) { 131 | part = parts[_i]; 132 | if (part === "..") { 133 | if (np.length > 1) { 134 | np.pop(); 135 | } else { 136 | np.push(part); 137 | } 138 | } else { 139 | if (part !== ".") { 140 | np.push(part); 141 | } 142 | } 143 | } 144 | path = np.join("/"); 145 | if (!path) { 146 | path = "/"; 147 | } 148 | return path; 149 | } 150 | }; 151 | 152 | toffee.__partial = function(parent_tmpl, parent_locals, path, vars) { 153 | path = toffee.__normalize(parent_tmpl.bundlePath + "/../" + path); 154 | return toffee.__inlineInclude(path, vars, parent_locals); 155 | }; 156 | 157 | toffee.__snippet = function(parent_tmpl, parent_locals, path, vars) { 158 | path = toffee.__normalize(parent_tmpl.bundlePath + "/../" + path); 159 | vars = vars != null ? vars : {}; 160 | vars.__toffee = vars.__toffee || {}; 161 | vars.__toffee.noInheritance = true; 162 | return toffee.__inlineInclude(path, vars, parent_locals); 163 | }; 164 | 165 | toffee.__load = function(parent_tmpl, parent_locals, path, vars) { 166 | path = toffee.__normalize(parent_tmpl.bundlePath + "/../" + path); 167 | vars = vars != null ? vars : {}; 168 | vars.__toffee = vars.__toffee || {}; 169 | vars.__toffee.repress = true; 170 | return toffee.__inlineInclude(path, vars, parent_locals); 171 | }; 172 | 173 | toffee.__inlineInclude = function(path, locals, parent_locals) { 174 | var k, options, res, reserved, v, _i, _len, _ref, _ref1; 175 | options = locals || {}; 176 | options.passback = {}; 177 | options.__toffee = options.__toffee || {}; 178 | reserved = {}; 179 | _ref = ["passback", "load", "print", "partial", "snippet", "layout", "__toffee", "postProcess"]; 180 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 181 | k = _ref[_i]; 182 | reserved[k] = true; 183 | } 184 | if (!options.__toffee.noInheritance) { 185 | for (k in parent_locals) { 186 | v = parent_locals[k]; 187 | if ((locals != null ? locals[k] : void 0) == null) { 188 | if (reserved[k] == null) { 189 | options[k] = v; 190 | } 191 | } 192 | } 193 | } 194 | if (!toffee.templates[path]) { 195 | return "Inline toffee include: Could not find " + path; 196 | } else { 197 | res = toffee.templates[path].pub(options); 198 | _ref1 = options.passback; 199 | for (k in _ref1) { 200 | v = _ref1[k]; 201 | parent_locals[k] = v; 202 | } 203 | return res; 204 | } 205 | }; 206 | -------------------------------------------------------------------------------- /lib/coffee-script/repl.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var ACCESSOR, CoffeeScript, Module, REPL_PROMPT, REPL_PROMPT_CONTINUATION, REPL_PROMPT_MULTILINE, SIMPLEVAR, Script, autocomplete, backlog, completeAttribute, completeVariable, enableColours, error, getCompletions, inspect, multilineMode, pipedInput, readline, repl, run, stdin, stdout; 4 | 5 | stdin = process.openStdin(); 6 | 7 | stdout = process.stdout; 8 | 9 | CoffeeScript = require('./coffee-script'); 10 | 11 | readline = require('readline'); 12 | 13 | inspect = require('util').inspect; 14 | 15 | Script = require('vm').Script; 16 | 17 | Module = require('module'); 18 | 19 | REPL_PROMPT = 'coffee> '; 20 | 21 | REPL_PROMPT_MULTILINE = '------> '; 22 | 23 | REPL_PROMPT_CONTINUATION = '......> '; 24 | 25 | enableColours = false; 26 | 27 | if (process.platform !== 'win32') { 28 | enableColours = !process.env.NODE_DISABLE_COLORS; 29 | } 30 | 31 | error = function(err) { 32 | return stdout.write((err.stack || err.toString()) + '\n'); 33 | }; 34 | 35 | ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/; 36 | 37 | SIMPLEVAR = /(\w+)$/i; 38 | 39 | autocomplete = function(text) { 40 | return completeAttribute(text) || completeVariable(text) || [[], text]; 41 | }; 42 | 43 | completeAttribute = function(text) { 44 | var all, completions, match, obj, prefix, val; 45 | if (match = text.match(ACCESSOR)) { 46 | all = match[0], obj = match[1], prefix = match[2]; 47 | try { 48 | val = Script.runInThisContext(obj); 49 | } catch (error) { 50 | return; 51 | } 52 | completions = getCompletions(prefix, Object.getOwnPropertyNames(Object(val))); 53 | return [completions, prefix]; 54 | } 55 | }; 56 | 57 | completeVariable = function(text) { 58 | var completions, free, keywords, possibilities, r, vars, _ref; 59 | free = (_ref = text.match(SIMPLEVAR)) != null ? _ref[1] : void 0; 60 | if (text === "") { 61 | free = ""; 62 | } 63 | if (free != null) { 64 | vars = Script.runInThisContext('Object.getOwnPropertyNames(Object(this))'); 65 | keywords = (function() { 66 | var _i, _len, _ref1, _results; 67 | _ref1 = CoffeeScript.RESERVED; 68 | _results = []; 69 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 70 | r = _ref1[_i]; 71 | if (r.slice(0, 2) !== '__') { 72 | _results.push(r); 73 | } 74 | } 75 | return _results; 76 | })(); 77 | possibilities = vars.concat(keywords); 78 | completions = getCompletions(free, possibilities); 79 | return [completions, free]; 80 | } 81 | }; 82 | 83 | getCompletions = function(prefix, candidates) { 84 | var el, _i, _len, _results; 85 | _results = []; 86 | for (_i = 0, _len = candidates.length; _i < _len; _i++) { 87 | el = candidates[_i]; 88 | if (el.indexOf(prefix) === 0) { 89 | _results.push(el); 90 | } 91 | } 92 | return _results; 93 | }; 94 | 95 | process.on('uncaughtException', error); 96 | 97 | backlog = ''; 98 | 99 | run = function(buffer) { 100 | var code, returnValue, _; 101 | buffer = buffer.replace(/[\r\n]+$/, ""); 102 | if (multilineMode) { 103 | backlog += "" + buffer + "\n"; 104 | repl.setPrompt(REPL_PROMPT_CONTINUATION); 105 | repl.prompt(); 106 | return; 107 | } 108 | if (!buffer.toString().trim() && !backlog) { 109 | repl.prompt(); 110 | return; 111 | } 112 | code = backlog += buffer; 113 | if (code[code.length - 1] === '\\') { 114 | backlog = "" + backlog.slice(0, -1) + "\n"; 115 | repl.setPrompt(REPL_PROMPT_CONTINUATION); 116 | repl.prompt(); 117 | return; 118 | } 119 | repl.setPrompt(REPL_PROMPT); 120 | backlog = ''; 121 | try { 122 | _ = global._; 123 | returnValue = CoffeeScript["eval"]("_=(undefined\n;" + code + "\n)", { 124 | filename: 'repl', 125 | modulename: 'repl' 126 | }); 127 | if (returnValue === void 0) { 128 | global._ = _; 129 | } 130 | repl.output.write("" + (inspect(returnValue, false, 2, enableColours)) + "\n"); 131 | } catch (err) { 132 | error(err); 133 | } 134 | return repl.prompt(); 135 | }; 136 | 137 | if (stdin.readable) { 138 | pipedInput = ''; 139 | repl = { 140 | prompt: function() { 141 | return stdout.write(this._prompt); 142 | }, 143 | setPrompt: function(p) { 144 | return this._prompt = p; 145 | }, 146 | input: stdin, 147 | output: stdout, 148 | on: function() {} 149 | }; 150 | stdin.on('data', function(chunk) { 151 | return pipedInput += chunk; 152 | }); 153 | stdin.on('end', function() { 154 | var line, _i, _len, _ref; 155 | _ref = pipedInput.trim().split("\n"); 156 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 157 | line = _ref[_i]; 158 | stdout.write("" + line + "\n"); 159 | run(line); 160 | } 161 | stdout.write('\n'); 162 | return process.exit(0); 163 | }); 164 | } else { 165 | if (readline.createInterface.length < 3) { 166 | repl = readline.createInterface(stdin, autocomplete); 167 | stdin.on('data', function(buffer) { 168 | return repl.write(buffer); 169 | }); 170 | } else { 171 | repl = readline.createInterface(stdin, stdout, autocomplete); 172 | } 173 | } 174 | 175 | multilineMode = false; 176 | 177 | repl.input.on('keypress', function(char, key) { 178 | var cursorPos, newPrompt; 179 | if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) { 180 | return; 181 | } 182 | cursorPos = repl.cursor; 183 | repl.output.cursorTo(0); 184 | repl.output.clearLine(1); 185 | multilineMode = !multilineMode; 186 | if (!multilineMode && backlog) { 187 | repl._line(); 188 | } 189 | backlog = ''; 190 | repl.setPrompt((newPrompt = multilineMode ? REPL_PROMPT_MULTILINE : REPL_PROMPT)); 191 | repl.prompt(); 192 | return repl.output.cursorTo(newPrompt.length + (repl.cursor = cursorPos)); 193 | }); 194 | 195 | repl.input.on('keypress', function(char, key) { 196 | if (!(multilineMode && repl.line)) { 197 | return; 198 | } 199 | if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'd')) { 200 | return; 201 | } 202 | multilineMode = false; 203 | return repl._line(); 204 | }); 205 | 206 | repl.on('attemptClose', function() { 207 | if (multilineMode) { 208 | multilineMode = false; 209 | repl.output.cursorTo(0); 210 | repl.output.clearLine(1); 211 | repl._onLine(repl.line); 212 | return; 213 | } 214 | if (backlog) { 215 | backlog = ''; 216 | repl.output.write('\n'); 217 | repl.setPrompt(REPL_PROMPT); 218 | return repl.prompt(); 219 | } else { 220 | return repl.close(); 221 | } 222 | }); 223 | 224 | repl.on('close', function() { 225 | repl.output.write('\n'); 226 | return repl.input.destroy(); 227 | }); 228 | 229 | repl.on('line', run); 230 | 231 | repl.setPrompt(REPL_PROMPT); 232 | 233 | repl.prompt(); 234 | 235 | }).call(this); 236 | -------------------------------------------------------------------------------- /src/errorHandler.coffee: -------------------------------------------------------------------------------- 1 | path = require "path" 2 | util = require "util" 3 | 4 | errorTypes = exports.errorTypes = 5 | PARSER: 0 6 | STR_INTERPOLATE: 1 7 | COFFEE_COMPILE: 2 8 | RUNTIME: 3 9 | 10 | class toffeeError extends Error 11 | 12 | constructor: (view, err_type, e) -> 13 | @errType = err_type 14 | @view = view 15 | @e = e 16 | @toffeeSrc = view.txt 17 | switch @errType 18 | when errorTypes.PARSER then @offensiveSrc = @toffeeSrc 19 | when errorTypes.STR_INTERPOLATE then @offensiveSrc = @toffeeSrc 20 | when errorTypes.COFFEE_COMPILE then @offensiveSrc = @view.coffeeScript 21 | when errorTypes.RUNTIME then @offensiveSrc = @view.javaScript 22 | @toffeeSrcLines = @toffeeSrc.split "\n" 23 | @offensiveSrcLines = @offensiveSrc.split "\n" 24 | 25 | 26 | getConvertedError: -> 27 | 28 | ### -------------------------------------- 29 | returns a JS style error, but with some extras 30 | { 31 | stack: array of lines 32 | message: error message 33 | line_range: line range in the toffee file 34 | filename: filename, if available; or null 35 | ...etc... 36 | } 37 | ------------------------------------------ 38 | ### 39 | res = { 40 | stack: [] 41 | message: "" 42 | type: @errType 43 | full_path: @view.fileName 44 | dir_name: path.dirname @view.fileName 45 | file: path.basename @view.fileName 46 | line_range: null # will be a pair 47 | } 48 | 49 | if @e?.message? then res.message = @e.message 50 | 51 | # Error objects now support line numbers in certain cases. 52 | if @e?.location?.first_line? 53 | res.line_range = @_convertJsErrorRangeToToffeeRange @e.location 54 | 55 | switch @errType 56 | 57 | when errorTypes.PARSER 58 | if not res.line_range? 59 | line = @_extractOffensiveLineNo @e.message, /on line ([0-9]+)/ 60 | res.line_range = [line, line + 1] 61 | 62 | when errorTypes.STR_INTERPOLATE 63 | res.line_range = [@e.relayed_line_range[0], @e.relayed_line_range[1]] 64 | res.message = res.message.replace 'starting on line NaN', @_lineRangeToPhrase res.line_range 65 | res.message = res.message.replace 'missing }', 'unclosed `\#{}`' 66 | 67 | when errorTypes.COFFEE_COMPILE 68 | if not res.line_range? 69 | line = @_extractOffensiveLineNo @e.message, /on line ([0-9]+)/ 70 | res.line_range = @_convertOffensiveLineToToffeeRange line 71 | if res.message.indexOf('on line') isnt -1 72 | res.message = res.message.replace /on line [0-9]+/, @_lineRangeToPhrase res.line_range 73 | else 74 | res.message += " " + @_lineRangeToPhrase res.line_range 75 | 76 | 77 | when errorTypes.RUNTIME 78 | if not res.line_range? 79 | res.line_range = [0,0] 80 | if @e.stack 81 | res.stack = @e.stack.split "\n" 82 | @_convertRuntimeStackLines res 83 | res 84 | 85 | _convertRuntimeStackLines: (converted_err)-> 86 | ### 87 | a little more complicated, so extracted. Returns an array 88 | of dictionaries where there's extra info on each line in the stack. 89 | ### 90 | hit_pub_yet = false # beyond this the stack isn't so important 91 | stack = converted_err.stack 92 | for line, i in stack 93 | 94 | rxx_pub = /// 95 | Object[\.].*?pub[\s]\(undefined\:([0-9]+)\:[0-9]+ 96 | | 97 | tmpl[\.]render[\.]tmpl[\.]pub.*\(.*\:([0-9]+)\:[0-9]+ 98 | /// 99 | m = line.match rxx_pub 100 | in_src_file = false 101 | lrange = [null, null] 102 | at_pub_call = false 103 | if m?.length >= 2 104 | line = line.replace "undefined", converted_err.full_path 105 | lineno = @_extractOffensiveLineNo line, /([0-9]+)\:[0-9]+/ 106 | lrange = @_convertOffensiveLineToToffeeRange lineno 107 | line = line.replace /\:[0-9]+\:[0-9]+/, "" 108 | hit_pub_yet = true 109 | in_src_file = true 110 | at_pub_call = true 111 | 112 | rxx_inline = /// at[\s]undefined\:([0-9]+)\:[0-9]+ /// 113 | m = line.match rxx_inline 114 | if m?.length >= 2 115 | line = line.replace "undefined", converted_err.full_path 116 | lineno = @_extractOffensiveLineNo line, /([0-9]+)\:[0-9]+/ 117 | lrange = @_convertOffensiveLineToToffeeRange lineno 118 | line = line.replace /\:[0-9]+\:[0-9]+/, "" 119 | in_src_file = true 120 | 121 | stack[i] = 122 | line: line 123 | above_pub_call: not hit_pub_yet 124 | at_pub_call: at_pub_call 125 | in_src_file: in_src_file 126 | line_range: lrange 127 | 128 | if stack[i].line_range[0] and not converted_err.line_range[0] 129 | converted_err.line_range = stack[i].line_range 130 | 131 | getPrettyPrintText: -> 132 | ### 133 | returns a TEXT only blob explaining the error 134 | ### 135 | cerr = @getConvertedError() 136 | header = "#{cerr.dir_name}/#{cerr.file}: #{cerr.message}" 137 | res = """ 138 | ERROR 139 | ===== 140 | #{header} 141 | """ 142 | if cerr.stack?.length 143 | res += """\n 144 | STACK 145 | =====\n 146 | """ 147 | count = 0 148 | for item,i in cerr.stack 149 | if i is 0 150 | res += "#{count++} #{item.line}" 151 | else if item.in_src_file and (item.above_pub_call or item.at_pub_call) 152 | res += "#{count++} [#{@_lineRangeToPhrase item.line_range}] #{cerr.dir_name}/#{cerr.file}" 153 | else if item.in_src_file 154 | continue 155 | else 156 | res += "#{count++}#{item.line}" 157 | if i < cerr.stack.length - 1 158 | res += "\n" 159 | res += """\n""" 160 | res 161 | 162 | getPrettyPrint: -> 163 | ### 164 | returns an HTML blob explaining the error 165 | with lines highlighted 166 | ### 167 | cerr = @getConvertedError() 168 | res = "" 169 | header = "#{cerr.dir_name}/#{cerr.file}: #{_ppEscape cerr.message}" 170 | res += """ 171 |
172 | \n
#{header}
173 | \n
174 | \n
175 | """ 176 | if cerr.stack?.length 177 | res += "
" 178 | count = 0 179 | for item,i in cerr.stack 180 | if i is 0 181 | res += "
#{count++} #{item.line}
" 182 | else if item.in_src_file and (item.above_pub_call or item.at_pub_call) 183 | res += "
#{count++} [#{@_lineRangeToPhrase item.line_range}] #{cerr.dir_name}/#{cerr.file}
" 184 | else if item.in_src_file 185 | continue 186 | else 187 | res += "
#{count++}#{item.line}
" 188 | res += "
" 189 | 190 | for i in [(cerr.line_range[0]-3)...(cerr.line_range[1]+1)] 191 | if (i < 0) or i > @toffeeSrcLines.length - 1 192 | continue 193 | line = _ppEscape @toffeeSrcLines[i] 194 | padding_len = 5 - ("#{i+1}").length 195 | padding = (" " for j in [0...padding_len]).join "" 196 | if (cerr.line_range[0] - 1) <= (i) < cerr.line_range[1] 197 | extra = "" 198 | else 199 | extra = "" 200 | res+= "#{extra}\n#{i+1}: #{padding} #{line}
" 201 | res += """ 202 | \n
203 | \n
204 | """ 205 | res 206 | 207 | _lineRangeToPhrase: (lrange) -> 208 | if lrange[0] is lrange[1] 209 | "on line #{lrange[0]}" 210 | else 211 | "between lines #{lrange[0]} and #{lrange[1]}" 212 | 213 | _extractOffensiveLineNo: (msg, rxx) -> 214 | m = msg.match rxx 215 | if not (m?.length >= 2) then return null 216 | return parseInt m[1] 217 | 218 | _convertJsErrorRangeToToffeeRange: (loc) -> 219 | range = @_convertOffensiveLineToToffeeRange loc.first_line 220 | if loc.last_line? 221 | range2 = @_convertOffensiveLineToToffeeRange loc.last_line 222 | range[1] = range2[1] 223 | return range 224 | 225 | _convertOffensiveLineToToffeeRange: (lineno) -> 226 | ### 227 | Given the error line in a converted file, hunts for surrounding 228 | __toffee.lineno calls and returns a pair array with the error position 229 | range in the original toffee file. 230 | ### 231 | ol = @offensiveSrcLines 232 | tl = @toffeeSrcLines 233 | 234 | if (not lineno?) or isNaN lineno 235 | return [1,tl.length] 236 | 237 | prev = ol[0...lineno].join "\n" 238 | next = ol[lineno...].join "\n" 239 | prev_matches = prev.match /_ln[ ]*\(?[ ]*([0-9]+)/g 240 | next_matches = next.match /_ln[ ]*\(?[ ]*([0-9]+)/g 241 | res = [1,tl.length] 242 | 243 | if prev_matches?.length 244 | res[0] = parseInt prev_matches[prev_matches.length-1].match(/[0-9]+/)[0] 245 | if next_matches?.length 246 | res[1] = parseInt next_matches[0].match(/[0-9]+/)[0] 247 | res 248 | 249 | 250 | exports.toffeeError = toffeeError 251 | 252 | _ppEscape = (txt) -> 253 | txt = txt.replace(/&/g, '&').replace(/ 13 | vm.createContext({}) 14 | 15 | class engine 16 | 17 | constructor: (options) -> 18 | options = options or {} 19 | @verbose = options.verbose or false 20 | @pool = new Pool(sandboxCons, options.poolSize or MAX_CACHED_SANDBOXES) 21 | @prettyPrintErrors = if options.prettyPrintErrors? then options.prettyPrintErrors else true 22 | @prettyLogErrors = if options.prettyLogErrors? then options.prettyLogErrors else true 23 | @autoEscape = if options.autoEscape? then options.autoEscape else true 24 | @cache = if options.cache? then options.cache else true 25 | @additionalErrorHandler = options.additionalErrorHandler or null 26 | 27 | @viewCache = {} # filename -> view 28 | @fsErrorCache = {} # filename -> timestamp last failed 29 | 30 | @filenameCache = {} # caches dir -> filename -> path.normalize path.resolve dir, filename 31 | @fileLockTable = new LockTable() 32 | 33 | _log: (o) -> 34 | if @verbose 35 | if (typeof o) in ["string","number","boolean"] 36 | console.log "toffee: #{o}" 37 | else 38 | console.log "toffee: #{util.inspect o}" 39 | 40 | # basically returns `path.normalize path.resolve dir, filename`, but caches it to speed up multiple inclusions 41 | normalizeFilename: (dir, filename) -> 42 | cache = @filenameCache[dir] 43 | if not cache? 44 | @filenameCache[dir] = {} 45 | cache = {} 46 | normalized = cache[filename] 47 | if not normalized? 48 | normalized = path.normalize path.resolve dir, filename 49 | @filenameCache[dir][filename] = normalized 50 | return normalized 51 | 52 | 53 | render: (filename, options, cb) => @run filename, options, cb 54 | 55 | run: (filename, options, cb) => 56 | ### 57 | "options" contains the pub vars and may contain special items: 58 | layout: path to a template expecting a body var (express 2.x style, but for use with express 3.x) 59 | postProcess: a function which takes the string of output and post processes it (returning new string) 60 | __toffee.dir: path to look relative to 61 | __toffee.parent: parent file 62 | __toffee.noInheritance: if true, don't pass variables through unless explicitly passed 63 | __toffee.repress if true, don't output anything; useful with including definition files with passback of vars 64 | __toffee.autoEscape: if set as false, don't escape output of #{} vars by default 65 | ### 66 | 67 | if not options.prettyPrintErrors? then options.prettyPrintErrors = @prettyPrintErrors 68 | if not options.prettyLogErrors? then options.prettyLogErrors = @prettyLogErrors 69 | if not options.additionalErrorHandler? then options.additionalErrorHandler = @additionalErrorHandler 70 | if not options.autoEscape? then options.autoEscape = @autoEscape 71 | 72 | # we only want to pass post_process into the layout 73 | post_process = options.postProcess 74 | options.postProcess = null 75 | 76 | if options?.layout 77 | layout_options = {} 78 | layout_options[k] = v for k,v of options when k isnt "layout" 79 | 80 | [err, res] = @runSync filename, options 81 | 82 | # if we got an error but want to pretty-print by faking ok result 83 | if err and @prettyPrintErrors 84 | [err, res] = [null, err] 85 | 86 | # if we're using a layout, pub into that 87 | if (not err) and layout_options? 88 | layout_options.body = res 89 | [err, res] = @runSync options.layout, layout_options 90 | if err and @prettyPrintErrors 91 | [err, res] = [null, err] 92 | 93 | # post processing 94 | if (not err) and (typeof(post_process) is "function") 95 | [err, res] = @postProcess post_process, res 96 | 97 | cb err, res 98 | 99 | postProcess: (fn, res) -> 100 | err = null 101 | try 102 | res = fn res 103 | catch e 104 | err = e 105 | return [err, res] 106 | 107 | runSync: (filename, options) -> 108 | ### 109 | "options" the same as run() above 110 | ### 111 | 112 | start_time = Date.now() 113 | 114 | options = options or {} 115 | options.__toffee = options.__toffee or {} 116 | options.__toffee.dir = options.__toffee.dir or process.cwd() 117 | realpath = @normalizeFilename options.__toffee.dir, filename 118 | 119 | if @cache 120 | v = (@_viewCacheGet realpath) or (@_loadCacheAndMonitor realpath, options) 121 | else 122 | v = @_loadWithoutCache realpath, options 123 | 124 | if v 125 | if @fsErrorCache[realpath] 126 | [err, res] = [new Error("Couldn't load #{realpath}"), null] 127 | else 128 | options.__toffee.parent = realpath 129 | options.partial = options.partial or (fname, lvars) => @_fn_partial fname, lvars, realpath, options 130 | options.snippet = options.snippet or (fname, lvars) => @_fn_snippet fname, lvars, realpath, options 131 | options.load = options.load or (fname, lvars) => @_fn_load fname, lvars, realpath, options 132 | options.print = options.print or (txt) => @_fn_print txt, options 133 | if not options.console? then options.console = log: console.log 134 | ctx = @pool.get() 135 | [err, res] = v.run options, ctx 136 | @pool.release(ctx) 137 | else 138 | [err, res] = [new Error("Couldn't load #{realpath}"), null] 139 | 140 | @_log "#{realpath} run in #{Date.now() - start_time}ms" 141 | return [err, res] 142 | 143 | _viewCacheGet: (filename) -> 144 | if not @viewCache[filename]? 145 | return null 146 | else if not @fsErrorCache[filename]? 147 | return @viewCache[filename] 148 | else if (Date.now() - @fsErrorCache[filename]) < tweakables.MISSING_FILE_RECHECK 149 | return @viewCache[filename] 150 | else 151 | return null 152 | 153 | _inlineInclude: (filename, local_vars, parent_realpath, parent_options) => 154 | options = local_vars or {} 155 | options.passback = {} 156 | options.__toffee = options.__toffee or {} 157 | options.__toffee.dir = path.dirname parent_realpath 158 | options.__toffee.parent = parent_realpath 159 | noInheritance = options.__toffee.noInheritance 160 | repress = options.__toffee.repress 161 | 162 | # we need to make a shallow copy of parent variables 163 | reserved = {} 164 | reserved[k] = true for k in ["passback", "load", "print", "partial", "snippet", "layout", "__toffee", "postProcess"] 165 | if not noInheritance 166 | for k,v of parent_options when not local_vars?[k]? 167 | if not reserved[k]? 168 | options[k] = v 169 | 170 | [err, res] = @runSync filename, options 171 | 172 | for k,v of options.passback 173 | parent_options[k] = v 174 | 175 | return err or res 176 | 177 | _fn_load: (fname, lvars, realpath, options) => 178 | lvars = if lvars? then lvars else {} 179 | lvars.__toffee = lvars.__toffee or {} 180 | lvars.__toffee.repress = true 181 | @_inlineInclude fname, lvars, realpath, options 182 | 183 | _fn_snippet: (fname, lvars, realpath, options) => 184 | lvars = if lvars? then lvars else {} 185 | lvars.__toffee = lvars.__toffee or {} 186 | lvars.__toffee.noInheritance = true 187 | @_inlineInclude fname, lvars, realpath, options 188 | 189 | _fn_partial: (fname, lvars, realpath, options) => 190 | @_inlineInclude fname, lvars, realpath, options 191 | 192 | _fn_print: (txt, options) -> 193 | if options.__toffee.state is states.COFFEE 194 | options.__toffee.out.push txt 195 | return '' 196 | else 197 | return txt 198 | 199 | _loadWithoutCache: (filename, options) -> 200 | try 201 | txt = fs.readFileSync filename, 'utf8' 202 | catch e 203 | txt = "Error: Could not read #{filename}" 204 | if options.__toffee?.parent? then txt += " first requested in #{options.__toffee.parent}" 205 | 206 | view_options = @_generateViewOptions filename 207 | v = new view txt, view_options 208 | return v 209 | 210 | _loadCacheAndMonitor: (filename, options) -> 211 | previous_fs_err = @fsErrorCache[filename]? 212 | try 213 | txt = fs.readFileSync filename, 'utf8' 214 | if @fsErrorCache[filename]? then delete @fsErrorCache[filename] 215 | catch e 216 | txt = "Error: Could not read #{filename}" 217 | if options.__toffee?.parent? then txt += " first requested in #{options.__toffee.parent}" 218 | @fsErrorCache[filename] = Date.now() 219 | 220 | # if we hit an fs error and it already happened, just return that 221 | if (@fsErrorCache[filename] and previous_fs_err and @viewCache[filename]) 222 | return @viewCache[filename] 223 | else 224 | view_options = @_generateViewOptions filename 225 | v = new view txt, view_options 226 | @viewCache[filename] = v 227 | @_monitorForChanges filename, options 228 | return v 229 | 230 | _reloadFileInBkg: (filename, options) -> 231 | @_log "#{filename} acquiring lock to read" 232 | @fileLockTable.acquire2 {name: filename}, (lock) => 233 | fs.readFile filename, 'utf8', (err, txt) => 234 | @_log "#{Date.now()} - #{filename} changed to #{txt?.length} bytes. #{txt?.replace?(/\n/g , '')[...80]}" if not err 235 | waiting_for_view = false 236 | if err or (txt isnt @viewCache[filename].txt) 237 | if err 238 | @fsErrorCache[filename] = Date.now() 239 | txt = "Error: Could not read #{filename}" 240 | if options.__toffee?.parent? then txt += " requested in #{options.__toffee.parent}" 241 | if not (err and @viewCache[filename].fsError) # i.e., don't just create a new error view 242 | view_options = @_generateViewOptions filename 243 | ctx = @pool.get() 244 | view_options.ctx = ctx 245 | view_options.cb = (v) => 246 | @_log "#{filename} updated and ready" 247 | @viewCache[filename] = v 248 | @pool.release(ctx) 249 | @_log "#{filename} lock releasing (view_options.cb)" 250 | lock.release() 251 | waiting_for_view = true # do not release lock instantly 252 | if err 253 | view_options.fsError = true 254 | v = new view txt, view_options 255 | if not waiting_for_view 256 | @_log "#{filename} lock releasing (not waiting for view)" 257 | lock.release() 258 | 259 | _generateViewOptions: (filename) -> 260 | return { 261 | fileName: filename 262 | verbose: @verbose 263 | prettyPrintErrors: @prettyPrintErrors 264 | prettyLogErrors: @prettyLogErrors 265 | autoEscape: @autoEscape 266 | additionalErrorHandler: @additionalErrorHandler 267 | } 268 | 269 | _monitorForChanges: (filename, options) -> 270 | ### 271 | we must continuously unwatch/rewatch because some editors/systems invoke a "rename" 272 | event and we'll end up following the wrong, old 'file' as a new one 273 | is dropped in its place. 274 | 275 | Files that are missing are ignored here because they get picked up by new calls to _loadCacheAndMonitor 276 | ### 277 | if not @fsErrorCache[filename]? # if there's an fsError, this will get rechecked on-demand occasionally 278 | fsw = null 279 | try 280 | @_log "#{filename} starting fs.watch()" 281 | fsw = fs.watch filename, {persistent: true}, (change) => 282 | @_log "#{filename} closing fs.watch()" 283 | fsw.close() 284 | @_monitorForChanges filename, options 285 | @_reloadFileInBkg filename, options 286 | catch e 287 | @_log "fs.watch() failed for #{filename}; settings fsErrorCache = true" 288 | @fsErrorCache[filename] = Date.now() 289 | 290 | exports.engine = engine 291 | -------------------------------------------------------------------------------- /lib/errorHandler.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.7 2 | (function() { 3 | var _ppEscape, errorTypes, path, toffeeError, util, 4 | extend = 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; }, 5 | hasProp = {}.hasOwnProperty; 6 | 7 | path = require("path"); 8 | 9 | util = require("util"); 10 | 11 | errorTypes = exports.errorTypes = { 12 | PARSER: 0, 13 | STR_INTERPOLATE: 1, 14 | COFFEE_COMPILE: 2, 15 | RUNTIME: 3 16 | }; 17 | 18 | toffeeError = (function(superClass) { 19 | extend(toffeeError, superClass); 20 | 21 | function toffeeError(view, err_type, e) { 22 | this.errType = err_type; 23 | this.view = view; 24 | this.e = e; 25 | this.toffeeSrc = view.txt; 26 | switch (this.errType) { 27 | case errorTypes.PARSER: 28 | this.offensiveSrc = this.toffeeSrc; 29 | break; 30 | case errorTypes.STR_INTERPOLATE: 31 | this.offensiveSrc = this.toffeeSrc; 32 | break; 33 | case errorTypes.COFFEE_COMPILE: 34 | this.offensiveSrc = this.view.coffeeScript; 35 | break; 36 | case errorTypes.RUNTIME: 37 | this.offensiveSrc = this.view.javaScript; 38 | } 39 | this.toffeeSrcLines = this.toffeeSrc.split("\n"); 40 | this.offensiveSrcLines = this.offensiveSrc.split("\n"); 41 | } 42 | 43 | toffeeError.prototype.getConvertedError = function() { 44 | 45 | /* -------------------------------------- 46 | returns a JS style error, but with some extras 47 | { 48 | stack: array of lines 49 | message: error message 50 | line_range: line range in the toffee file 51 | filename: filename, if available; or null 52 | ...etc... 53 | } 54 | ------------------------------------------ 55 | */ 56 | var line, ref, ref1, ref2, res; 57 | res = { 58 | stack: [], 59 | message: "", 60 | type: this.errType, 61 | full_path: this.view.fileName, 62 | dir_name: path.dirname(this.view.fileName), 63 | file: path.basename(this.view.fileName), 64 | line_range: null 65 | }; 66 | if (((ref = this.e) != null ? ref.message : void 0) != null) { 67 | res.message = this.e.message; 68 | } 69 | if (((ref1 = this.e) != null ? (ref2 = ref1.location) != null ? ref2.first_line : void 0 : void 0) != null) { 70 | res.line_range = this._convertJsErrorRangeToToffeeRange(this.e.location); 71 | } 72 | switch (this.errType) { 73 | case errorTypes.PARSER: 74 | if (res.line_range == null) { 75 | line = this._extractOffensiveLineNo(this.e.message, /on line ([0-9]+)/); 76 | res.line_range = [line, line + 1]; 77 | } 78 | break; 79 | case errorTypes.STR_INTERPOLATE: 80 | res.line_range = [this.e.relayed_line_range[0], this.e.relayed_line_range[1]]; 81 | res.message = res.message.replace('starting on line NaN', this._lineRangeToPhrase(res.line_range)); 82 | res.message = res.message.replace('missing }', 'unclosed `\#{}`'); 83 | break; 84 | case errorTypes.COFFEE_COMPILE: 85 | if (res.line_range == null) { 86 | line = this._extractOffensiveLineNo(this.e.message, /on line ([0-9]+)/); 87 | res.line_range = this._convertOffensiveLineToToffeeRange(line); 88 | } 89 | if (res.message.indexOf('on line') !== -1) { 90 | res.message = res.message.replace(/on line [0-9]+/, this._lineRangeToPhrase(res.line_range)); 91 | } else { 92 | res.message += " " + this._lineRangeToPhrase(res.line_range); 93 | } 94 | break; 95 | case errorTypes.RUNTIME: 96 | if (res.line_range == null) { 97 | res.line_range = [0, 0]; 98 | } 99 | if (this.e.stack) { 100 | res.stack = this.e.stack.split("\n"); 101 | this._convertRuntimeStackLines(res); 102 | } 103 | } 104 | return res; 105 | }; 106 | 107 | toffeeError.prototype._convertRuntimeStackLines = function(converted_err) { 108 | 109 | /* 110 | a little more complicated, so extracted. Returns an array 111 | of dictionaries where there's extra info on each line in the stack. 112 | */ 113 | var at_pub_call, hit_pub_yet, i, in_src_file, k, len, line, lineno, lrange, m, results, rxx_inline, rxx_pub, stack; 114 | hit_pub_yet = false; 115 | stack = converted_err.stack; 116 | results = []; 117 | for (i = k = 0, len = stack.length; k < len; i = ++k) { 118 | line = stack[i]; 119 | rxx_pub = /Object[\.].*?pub[\s]\(undefined\:([0-9]+)\:[0-9]+|tmpl[\.]render[\.]tmpl[\.]pub.*\(.*\:([0-9]+)\:[0-9]+/; 120 | m = line.match(rxx_pub); 121 | in_src_file = false; 122 | lrange = [null, null]; 123 | at_pub_call = false; 124 | if ((m != null ? m.length : void 0) >= 2) { 125 | line = line.replace("undefined", converted_err.full_path); 126 | lineno = this._extractOffensiveLineNo(line, /([0-9]+)\:[0-9]+/); 127 | lrange = this._convertOffensiveLineToToffeeRange(lineno); 128 | line = line.replace(/\:[0-9]+\:[0-9]+/, ""); 129 | hit_pub_yet = true; 130 | in_src_file = true; 131 | at_pub_call = true; 132 | } 133 | rxx_inline = /at[\s]undefined\:([0-9]+)\:[0-9]+/; 134 | m = line.match(rxx_inline); 135 | if ((m != null ? m.length : void 0) >= 2) { 136 | line = line.replace("undefined", converted_err.full_path); 137 | lineno = this._extractOffensiveLineNo(line, /([0-9]+)\:[0-9]+/); 138 | lrange = this._convertOffensiveLineToToffeeRange(lineno); 139 | line = line.replace(/\:[0-9]+\:[0-9]+/, ""); 140 | in_src_file = true; 141 | } 142 | stack[i] = { 143 | line: line, 144 | above_pub_call: !hit_pub_yet, 145 | at_pub_call: at_pub_call, 146 | in_src_file: in_src_file, 147 | line_range: lrange 148 | }; 149 | if (stack[i].line_range[0] && !converted_err.line_range[0]) { 150 | results.push(converted_err.line_range = stack[i].line_range); 151 | } else { 152 | results.push(void 0); 153 | } 154 | } 155 | return results; 156 | }; 157 | 158 | toffeeError.prototype.getPrettyPrintText = function() { 159 | 160 | /* 161 | returns a TEXT only blob explaining the error 162 | */ 163 | var cerr, count, header, i, item, k, len, ref, ref1, res; 164 | cerr = this.getConvertedError(); 165 | header = cerr.dir_name + "/" + cerr.file + ": " + cerr.message; 166 | res = "ERROR\n=====\n" + header; 167 | if ((ref = cerr.stack) != null ? ref.length : void 0) { 168 | res += "\n\nSTACK\n=====\n"; 169 | count = 0; 170 | ref1 = cerr.stack; 171 | for (i = k = 0, len = ref1.length; k < len; i = ++k) { 172 | item = ref1[i]; 173 | if (i === 0) { 174 | res += (count++) + " " + item.line; 175 | } else if (item.in_src_file && (item.above_pub_call || item.at_pub_call)) { 176 | res += (count++) + " [" + (this._lineRangeToPhrase(item.line_range)) + "] " + cerr.dir_name + "/" + cerr.file; 177 | } else if (item.in_src_file) { 178 | continue; 179 | } else { 180 | res += "" + (count++) + item.line; 181 | } 182 | if (i < cerr.stack.length - 1) { 183 | res += "\n"; 184 | } 185 | } 186 | } 187 | res += "\n"; 188 | return res; 189 | }; 190 | 191 | toffeeError.prototype.getPrettyPrint = function() { 192 | 193 | /* 194 | returns an HTML blob explaining the error 195 | with lines highlighted 196 | */ 197 | var cerr, count, extra, header, i, item, j, k, l, len, line, padding, padding_len, ref, ref1, ref2, ref3, ref4, res; 198 | cerr = this.getConvertedError(); 199 | res = ""; 200 | header = cerr.dir_name + "/" + cerr.file + ": " + (_ppEscape(cerr.message)) + ""; 201 | res += "
\n \n
" + header + "
\n \n
\n \n
"; 202 | if ((ref = cerr.stack) != null ? ref.length : void 0) { 203 | res += "
"; 204 | count = 0; 205 | ref1 = cerr.stack; 206 | for (i = k = 0, len = ref1.length; k < len; i = ++k) { 207 | item = ref1[i]; 208 | if (i === 0) { 209 | res += "
" + (count++) + " " + item.line + "
"; 210 | } else if (item.in_src_file && (item.above_pub_call || item.at_pub_call)) { 211 | res += "
" + (count++) + " [" + (this._lineRangeToPhrase(item.line_range)) + "] " + cerr.dir_name + "/" + cerr.file + "
"; 212 | } else if (item.in_src_file) { 213 | continue; 214 | } else { 215 | res += "
" + (count++) + item.line + "
"; 216 | } 217 | } 218 | res += "
"; 219 | } 220 | for (i = l = ref2 = cerr.line_range[0] - 3, ref3 = cerr.line_range[1] + 1; ref2 <= ref3 ? l < ref3 : l > ref3; i = ref2 <= ref3 ? ++l : --l) { 221 | if ((i < 0) || i > this.toffeeSrcLines.length - 1) { 222 | continue; 223 | } 224 | line = _ppEscape(this.toffeeSrcLines[i]); 225 | padding_len = 5 - ("" + (i + 1)).length; 226 | padding = ((function() { 227 | var n, ref4, results; 228 | results = []; 229 | for (j = n = 0, ref4 = padding_len; 0 <= ref4 ? n < ref4 : n > ref4; j = 0 <= ref4 ? ++n : --n) { 230 | results.push(" "); 231 | } 232 | return results; 233 | })()).join(""); 234 | if (((cerr.line_range[0] - 1) <= (ref4 = i) && ref4 < cerr.line_range[1])) { 235 | extra = ""; 236 | } else { 237 | extra = ""; 238 | } 239 | res += extra + "\n" + (i + 1) + ": " + padding + " " + line + "
"; 240 | } 241 | res += " \n
\n\n
"; 242 | return res; 243 | }; 244 | 245 | toffeeError.prototype._lineRangeToPhrase = function(lrange) { 246 | if (lrange[0] === lrange[1]) { 247 | return "on line " + lrange[0]; 248 | } else { 249 | return "between lines " + lrange[0] + " and " + lrange[1]; 250 | } 251 | }; 252 | 253 | toffeeError.prototype._extractOffensiveLineNo = function(msg, rxx) { 254 | var m; 255 | m = msg.match(rxx); 256 | if (!((m != null ? m.length : void 0) >= 2)) { 257 | return null; 258 | } 259 | return parseInt(m[1]); 260 | }; 261 | 262 | toffeeError.prototype._convertJsErrorRangeToToffeeRange = function(loc) { 263 | var range, range2; 264 | range = this._convertOffensiveLineToToffeeRange(loc.first_line); 265 | if (loc.last_line != null) { 266 | range2 = this._convertOffensiveLineToToffeeRange(loc.last_line); 267 | range[1] = range2[1]; 268 | } 269 | return range; 270 | }; 271 | 272 | toffeeError.prototype._convertOffensiveLineToToffeeRange = function(lineno) { 273 | 274 | /* 275 | Given the error line in a converted file, hunts for surrounding 276 | __toffee.lineno calls and returns a pair array with the error position 277 | range in the original toffee file. 278 | */ 279 | var next, next_matches, ol, prev, prev_matches, res, tl; 280 | ol = this.offensiveSrcLines; 281 | tl = this.toffeeSrcLines; 282 | if ((lineno == null) || isNaN(lineno)) { 283 | return [1, tl.length]; 284 | } 285 | prev = ol.slice(0, lineno).join("\n"); 286 | next = ol.slice(lineno).join("\n"); 287 | prev_matches = prev.match(/_ln[ ]*\(?[ ]*([0-9]+)/g); 288 | next_matches = next.match(/_ln[ ]*\(?[ ]*([0-9]+)/g); 289 | res = [1, tl.length]; 290 | if (prev_matches != null ? prev_matches.length : void 0) { 291 | res[0] = parseInt(prev_matches[prev_matches.length - 1].match(/[0-9]+/)[0]); 292 | } 293 | if (next_matches != null ? next_matches.length : void 0) { 294 | res[1] = parseInt(next_matches[0].match(/[0-9]+/)[0]); 295 | } 296 | return res; 297 | }; 298 | 299 | return toffeeError; 300 | 301 | })(Error); 302 | 303 | exports.toffeeError = toffeeError; 304 | 305 | _ppEscape = function(txt) { 306 | var i, m; 307 | txt = txt.replace(/&/g, '&').replace(/ ref; i = 0 <= ref ? ++k : --k) { 313 | results.push(" "); 314 | } 315 | return results; 316 | })()).join("")); 317 | return txt; 318 | }; 319 | 320 | }).call(this); 321 | -------------------------------------------------------------------------------- /lib/coffee-script/rewriter.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, SINGLE_CLOSERS, SINGLE_LINERS, left, rite, _i, _len, _ref, 4 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, 5 | __slice = [].slice; 6 | 7 | exports.Rewriter = (function() { 8 | 9 | Rewriter.name = 'Rewriter'; 10 | 11 | function Rewriter() {} 12 | 13 | Rewriter.prototype.rewrite = function(tokens) { 14 | this.tokens = tokens; 15 | this.removeLeadingNewlines(); 16 | this.removeMidExpressionNewlines(); 17 | this.closeOpenCalls(); 18 | this.closeOpenIndexes(); 19 | this.addImplicitIndentation(); 20 | this.tagPostfixConditionals(); 21 | this.addImplicitBraces(); 22 | this.addImplicitParentheses(); 23 | return this.tokens; 24 | }; 25 | 26 | Rewriter.prototype.scanTokens = function(block) { 27 | var i, token, tokens; 28 | tokens = this.tokens; 29 | i = 0; 30 | while (token = tokens[i]) { 31 | i += block.call(this, token, i, tokens); 32 | } 33 | return true; 34 | }; 35 | 36 | Rewriter.prototype.detectEnd = function(i, condition, action) { 37 | var levels, token, tokens, _ref, _ref1; 38 | tokens = this.tokens; 39 | levels = 0; 40 | while (token = tokens[i]) { 41 | if (levels === 0 && condition.call(this, token, i)) { 42 | return action.call(this, token, i); 43 | } 44 | if (!token || levels < 0) { 45 | return action.call(this, token, i - 1); 46 | } 47 | if (_ref = token[0], __indexOf.call(EXPRESSION_START, _ref) >= 0) { 48 | levels += 1; 49 | } else if (_ref1 = token[0], __indexOf.call(EXPRESSION_END, _ref1) >= 0) { 50 | levels -= 1; 51 | } 52 | i += 1; 53 | } 54 | return i - 1; 55 | }; 56 | 57 | Rewriter.prototype.removeLeadingNewlines = function() { 58 | var i, tag, _i, _len, _ref; 59 | _ref = this.tokens; 60 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { 61 | tag = _ref[i][0]; 62 | if (tag !== 'TERMINATOR') { 63 | break; 64 | } 65 | } 66 | if (i) { 67 | return this.tokens.splice(0, i); 68 | } 69 | }; 70 | 71 | Rewriter.prototype.removeMidExpressionNewlines = function() { 72 | return this.scanTokens(function(token, i, tokens) { 73 | var _ref; 74 | if (!(token[0] === 'TERMINATOR' && (_ref = this.tag(i + 1), __indexOf.call(EXPRESSION_CLOSE, _ref) >= 0))) { 75 | return 1; 76 | } 77 | tokens.splice(i, 1); 78 | return 0; 79 | }); 80 | }; 81 | 82 | Rewriter.prototype.closeOpenCalls = function() { 83 | var action, condition; 84 | condition = function(token, i) { 85 | var _ref; 86 | return ((_ref = token[0]) === ')' || _ref === 'CALL_END') || token[0] === 'OUTDENT' && this.tag(i - 1) === ')'; 87 | }; 88 | action = function(token, i) { 89 | return this.tokens[token[0] === 'OUTDENT' ? i - 1 : i][0] = 'CALL_END'; 90 | }; 91 | return this.scanTokens(function(token, i) { 92 | if (token[0] === 'CALL_START') { 93 | this.detectEnd(i + 1, condition, action); 94 | } 95 | return 1; 96 | }); 97 | }; 98 | 99 | Rewriter.prototype.closeOpenIndexes = function() { 100 | var action, condition; 101 | condition = function(token, i) { 102 | var _ref; 103 | return (_ref = token[0]) === ']' || _ref === 'INDEX_END'; 104 | }; 105 | action = function(token, i) { 106 | return token[0] = 'INDEX_END'; 107 | }; 108 | return this.scanTokens(function(token, i) { 109 | if (token[0] === 'INDEX_START') { 110 | this.detectEnd(i + 1, condition, action); 111 | } 112 | return 1; 113 | }); 114 | }; 115 | 116 | Rewriter.prototype.addImplicitBraces = function() { 117 | var action, condition, sameLine, stack, start, startIndent, startsLine; 118 | stack = []; 119 | start = null; 120 | startsLine = null; 121 | sameLine = true; 122 | startIndent = 0; 123 | condition = function(token, i) { 124 | var one, tag, three, two, _ref, _ref1; 125 | _ref = this.tokens.slice(i + 1, (i + 3) + 1 || 9e9), one = _ref[0], two = _ref[1], three = _ref[2]; 126 | if ('HERECOMMENT' === (one != null ? one[0] : void 0)) { 127 | return false; 128 | } 129 | tag = token[0]; 130 | if (__indexOf.call(LINEBREAKS, tag) >= 0) { 131 | sameLine = false; 132 | } 133 | return (((tag === 'TERMINATOR' || tag === 'OUTDENT') || (__indexOf.call(IMPLICIT_END, tag) >= 0 && sameLine)) && ((!startsLine && this.tag(i - 1) !== ',') || !((two != null ? two[0] : void 0) === ':' || (one != null ? one[0] : void 0) === '@' && (three != null ? three[0] : void 0) === ':'))) || (tag === ',' && one && ((_ref1 = one[0]) !== 'IDENTIFIER' && _ref1 !== 'NUMBER' && _ref1 !== 'STRING' && _ref1 !== '@' && _ref1 !== 'TERMINATOR' && _ref1 !== 'OUTDENT')); 134 | }; 135 | action = function(token, i) { 136 | var tok; 137 | tok = this.generate('}', '}', token[2]); 138 | return this.tokens.splice(i, 0, tok); 139 | }; 140 | return this.scanTokens(function(token, i, tokens) { 141 | var ago, idx, prevTag, tag, tok, value, _ref, _ref1; 142 | if (_ref = (tag = token[0]), __indexOf.call(EXPRESSION_START, _ref) >= 0) { 143 | stack.push([(tag === 'INDENT' && this.tag(i - 1) === '{' ? '{' : tag), i]); 144 | return 1; 145 | } 146 | if (__indexOf.call(EXPRESSION_END, tag) >= 0) { 147 | start = stack.pop(); 148 | return 1; 149 | } 150 | if (!(tag === ':' && ((ago = this.tag(i - 2)) === ':' || ((_ref1 = stack[stack.length - 1]) != null ? _ref1[0] : void 0) !== '{'))) { 151 | return 1; 152 | } 153 | sameLine = true; 154 | stack.push(['{']); 155 | idx = ago === '@' ? i - 2 : i - 1; 156 | while (this.tag(idx - 2) === 'HERECOMMENT') { 157 | idx -= 2; 158 | } 159 | prevTag = this.tag(idx - 1); 160 | startsLine = !prevTag || (__indexOf.call(LINEBREAKS, prevTag) >= 0); 161 | value = new String('{'); 162 | value.generated = true; 163 | tok = this.generate('{', value, token[2]); 164 | tokens.splice(idx, 0, tok); 165 | this.detectEnd(i + 2, condition, action); 166 | return 2; 167 | }); 168 | }; 169 | 170 | Rewriter.prototype.addImplicitParentheses = function() { 171 | var action, condition, noCall, seenControl, seenSingle; 172 | noCall = seenSingle = seenControl = false; 173 | condition = function(token, i) { 174 | var post, tag, _ref, _ref1; 175 | tag = token[0]; 176 | if (!seenSingle && token.fromThen) { 177 | return true; 178 | } 179 | if (tag === 'IF' || tag === 'ELSE' || tag === 'CATCH' || tag === '->' || tag === '=>' || tag === 'CLASS') { 180 | seenSingle = true; 181 | } 182 | if (tag === 'IF' || tag === 'ELSE' || tag === 'SWITCH' || tag === 'TRY' || tag === '=') { 183 | seenControl = true; 184 | } 185 | if ((tag === '.' || tag === '?.' || tag === '::') && this.tag(i - 1) === 'OUTDENT') { 186 | return true; 187 | } 188 | return !token.generated && this.tag(i - 1) !== ',' && (__indexOf.call(IMPLICIT_END, tag) >= 0 || (tag === 'INDENT' && !seenControl)) && (tag !== 'INDENT' || (((_ref = this.tag(i - 2)) !== 'CLASS' && _ref !== 'EXTENDS') && (_ref1 = this.tag(i - 1), __indexOf.call(IMPLICIT_BLOCK, _ref1) < 0) && !((post = this.tokens[i + 1]) && post.generated && post[0] === '{'))); 189 | }; 190 | action = function(token, i) { 191 | return this.tokens.splice(i, 0, this.generate('CALL_END', ')', token[2])); 192 | }; 193 | return this.scanTokens(function(token, i, tokens) { 194 | var callObject, current, next, prev, tag, _ref, _ref1, _ref2; 195 | tag = token[0]; 196 | if (tag === 'CLASS' || tag === 'IF' || tag === 'FOR' || tag === 'WHILE') { 197 | noCall = true; 198 | } 199 | _ref = tokens.slice(i - 1, (i + 1) + 1 || 9e9), prev = _ref[0], current = _ref[1], next = _ref[2]; 200 | callObject = !noCall && tag === 'INDENT' && next && next.generated && next[0] === '{' && prev && (_ref1 = prev[0], __indexOf.call(IMPLICIT_FUNC, _ref1) >= 0); 201 | seenSingle = false; 202 | seenControl = false; 203 | if (__indexOf.call(LINEBREAKS, tag) >= 0) { 204 | noCall = false; 205 | } 206 | if (prev && !prev.spaced && tag === '?') { 207 | token.call = true; 208 | } 209 | if (token.fromThen) { 210 | return 1; 211 | } 212 | if (!(callObject || (prev != null ? prev.spaced : void 0) && (prev.call || (_ref2 = prev[0], __indexOf.call(IMPLICIT_FUNC, _ref2) >= 0)) && (__indexOf.call(IMPLICIT_CALL, tag) >= 0 || !(token.spaced || token.newLine) && __indexOf.call(IMPLICIT_UNSPACED_CALL, tag) >= 0))) { 213 | return 1; 214 | } 215 | tokens.splice(i, 0, this.generate('CALL_START', '(', token[2])); 216 | this.detectEnd(i + 1, condition, action); 217 | if (prev[0] === '?') { 218 | prev[0] = 'FUNC_EXIST'; 219 | } 220 | return 2; 221 | }); 222 | }; 223 | 224 | Rewriter.prototype.addImplicitIndentation = function() { 225 | var action, condition, indent, outdent, starter; 226 | starter = indent = outdent = null; 227 | condition = function(token, i) { 228 | var _ref; 229 | return token[1] !== ';' && (_ref = token[0], __indexOf.call(SINGLE_CLOSERS, _ref) >= 0) && !(token[0] === 'ELSE' && (starter !== 'IF' && starter !== 'THEN')); 230 | }; 231 | action = function(token, i) { 232 | return this.tokens.splice((this.tag(i - 1) === ',' ? i - 1 : i), 0, outdent); 233 | }; 234 | return this.scanTokens(function(token, i, tokens) { 235 | var tag, _ref, _ref1; 236 | tag = token[0]; 237 | if (tag === 'TERMINATOR' && this.tag(i + 1) === 'THEN') { 238 | tokens.splice(i, 1); 239 | return 0; 240 | } 241 | if (tag === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') { 242 | tokens.splice.apply(tokens, [i, 0].concat(__slice.call(this.indentation(token)))); 243 | return 2; 244 | } 245 | if (tag === 'CATCH' && ((_ref = this.tag(i + 2)) === 'OUTDENT' || _ref === 'TERMINATOR' || _ref === 'FINALLY')) { 246 | tokens.splice.apply(tokens, [i + 2, 0].concat(__slice.call(this.indentation(token)))); 247 | return 4; 248 | } 249 | if (__indexOf.call(SINGLE_LINERS, tag) >= 0 && this.tag(i + 1) !== 'INDENT' && !(tag === 'ELSE' && this.tag(i + 1) === 'IF')) { 250 | starter = tag; 251 | _ref1 = this.indentation(token, true), indent = _ref1[0], outdent = _ref1[1]; 252 | if (starter === 'THEN') { 253 | indent.fromThen = true; 254 | } 255 | tokens.splice(i + 1, 0, indent); 256 | this.detectEnd(i + 2, condition, action); 257 | if (tag === 'THEN') { 258 | tokens.splice(i, 1); 259 | } 260 | return 1; 261 | } 262 | return 1; 263 | }); 264 | }; 265 | 266 | Rewriter.prototype.tagPostfixConditionals = function() { 267 | var action, condition, original; 268 | original = null; 269 | condition = function(token, i) { 270 | var _ref; 271 | return (_ref = token[0]) === 'TERMINATOR' || _ref === 'INDENT'; 272 | }; 273 | action = function(token, i) { 274 | if (token[0] !== 'INDENT' || (token.generated && !token.fromThen)) { 275 | return original[0] = 'POST_' + original[0]; 276 | } 277 | }; 278 | return this.scanTokens(function(token, i) { 279 | if (token[0] !== 'IF') { 280 | return 1; 281 | } 282 | original = token; 283 | this.detectEnd(i + 1, condition, action); 284 | return 1; 285 | }); 286 | }; 287 | 288 | Rewriter.prototype.indentation = function(token, implicit) { 289 | var indent, outdent; 290 | if (implicit == null) { 291 | implicit = false; 292 | } 293 | indent = ['INDENT', 2, token[2]]; 294 | outdent = ['OUTDENT', 2, token[2]]; 295 | if (implicit) { 296 | indent.generated = outdent.generated = true; 297 | } 298 | return [indent, outdent]; 299 | }; 300 | 301 | Rewriter.prototype.generate = function(tag, value, line) { 302 | var tok; 303 | tok = [tag, value, line]; 304 | tok.generated = true; 305 | return tok; 306 | }; 307 | 308 | Rewriter.prototype.tag = function(i) { 309 | var _ref; 310 | return (_ref = this.tokens[i]) != null ? _ref[0] : void 0; 311 | }; 312 | 313 | return Rewriter; 314 | 315 | })(); 316 | 317 | BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['CALL_START', 'CALL_END'], ['PARAM_START', 'PARAM_END'], ['INDEX_START', 'INDEX_END']]; 318 | 319 | exports.INVERSES = INVERSES = {}; 320 | 321 | EXPRESSION_START = []; 322 | 323 | EXPRESSION_END = []; 324 | 325 | for (_i = 0, _len = BALANCED_PAIRS.length; _i < _len; _i++) { 326 | _ref = BALANCED_PAIRS[_i], left = _ref[0], rite = _ref[1]; 327 | EXPRESSION_START.push(INVERSES[rite] = left); 328 | EXPRESSION_END.push(INVERSES[left] = rite); 329 | } 330 | 331 | EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END); 332 | 333 | IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']; 334 | 335 | IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'UNARY', 'SUPER', '@', '->', '=>', '[', '(', '{', '--', '++']; 336 | 337 | IMPLICIT_UNSPACED_CALL = ['+', '-']; 338 | 339 | IMPLICIT_BLOCK = ['->', '=>', '{', '[', ',']; 340 | 341 | IMPLICIT_END = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY', 'LOOP', 'TERMINATOR']; 342 | 343 | SINGLE_LINERS = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN']; 344 | 345 | SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']; 346 | 347 | LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT']; 348 | 349 | }).call(this); 350 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TOFFEE 2 | 3 | **_Toffee_ is barely maintained. Please consider another templating language.** 4 | 5 | _Toffee_ is a templating language, based on the simplicity and beauty of CoffeeScript. 6 | 7 | - it works with Node.js 8 | - it works in the browser, too -- even the advanced features. 9 | 10 | Newest feature: 11 | 12 | - post-processing! You can let Toffee do your server-side code highighting, and other magic. 13 | 14 | Toffee has many cool features. Keep on reading. 15 | 16 | # Table of Contents 17 | 18 | - [1. Language Basics](#section_1) 19 | - [2. Notes on Escaping](#section_2) 20 | - [3. Common Questions](#section_3) 21 | - [4. Installation & Usage (Node, Express, and the browser)](#section_4) 22 | 23 | ## Language Basics 24 | 25 | Printing variables in Toffee is easy. Just use CoffeeScript's #{} syntax: 26 | 27 | ```html 28 |
29 | Hey, #{user.name}. #{flirty_welcome_msg} 30 |
31 | ``` 32 | 33 | The `#{}` syntax is powerful, so be responsible. 34 | 35 | ```html 36 |

37 | You have #{(sheep for sheep in flock when sheep.color is 'black').length} 38 | black sheep in the flock. 39 |

40 | ``` 41 | 42 | Including other files is possible thanks to the function `partial`. This works in both Express and the browser. 43 | 44 | ```html 45 |

46 | #{partial "navigation.toffee", {username: user.name, age: 22} } 47 |

48 | ``` 49 | 50 | But the greatest pleasure arises when you enter 51 | `coffee mode`. Note the `{# ... #}` region, where you can write multiple lines of CoffeeScript. 52 | 53 | ```html 54 |

55 | {# ten_numbers = [1,3,2,4,5,8,6,7,69, Math.random()] ten_numbers.sort (a,b) -> 56 | b - a #} The largest number I can even think of is #{ten_numbers[0]}. 57 |

58 | ``` 59 | 60 | Against all odds, inside `coffee mode`, you can switch back to `toffee mode` with `{: ... :}`. It's endlessly nestable. 61 | 62 | ```html 63 |
64 |
65 | {# if projects.length for project in projects {: 66 |
67 | #{project.name} 68 |
69 | :} #} 70 |
71 |
72 | ``` 73 | 74 | This bracket and nesting syntax avoids a lot of large, ugly regions, such 75 | as EJS's unethical `<% } %>`. It's been wrong for thousands of years 76 | to have control markers surrounded by other control 77 | markers, and it is still wrong. Witness: 78 | 79 | EJS, verbose and weak. 80 | 81 | ``` 82 | <% for(var i=0; i 83 |
  • <%= supplies[i] %>
  • 84 | <% } %> 85 | ``` 86 | 87 | TOFFEE, so elegant and proud. 88 | 89 | ```html 90 | {# for supply in supplies {: 91 |
  • #{supply}
  • 92 | :} #} 93 | ``` 94 | 95 | Or, using Toffee's `print`: 96 | 97 | ```html 98 | {# for supply in supplies print " 99 |
  • #{supply}
  • 100 | " #} 101 | ``` 102 | 103 | These are slightly different, as `print` outputs raw text, while `#{}` used in toffee mode safely escapes HTML or JSON. This escaping 104 | escaping is customizable. More on that below. 105 | 106 | With nested code, indentation of your CoffeeScript is magically maintained. 107 | 108 | ```html 109 | {# if user.is_drunk for name, profile of friends when profile.is_responsible {: 110 |

    111 | You know, #{name} would make a great designated driver. And she only lives 112 | #{profile.distance}km away. {# if profile.car? {: And wow, she drives a 113 | #{profile.car.model} :} else {: But, alas, she has no wheels. :} #} 114 |

    115 | :} #} 116 | ``` 117 | 118 | ### Partials (including other files), both for output and configuration 119 | 120 | Including other files in Toffee is easy with the `partial` function, which includes another template file. 121 | 122 | ```html 123 |
    124 | #{partial '../main_navigation.toffee'} 125 |
    126 | ``` 127 | 128 | Shallow copies of variables are passed through from the parent document, however you can pass additional variables with a dictionary. 129 | 130 | ```html 131 |
    132 | #{partial '../main_navigation.toffee', {user: elon_musk, iq: 180} } 133 |
    134 | ``` 135 | 136 | Again, toffee's `print` function allows you to use partials when in coffeescript mode: 137 | 138 | ```html 139 | {# if user? print partial "logged_in_template.toffee" #} 140 | ``` 141 | 142 | For your safety and convenience, variables are shallow-copied into a template. This means if you redefine or create a variable in a 143 | child template, it won't be available back in the parent template. However, you can relay variables by modifying the special `passback` dictionary 144 | in a the child template. 145 | 146 | ```html 147 | 148 | {# partial './config.toffee' #} 149 |

    Our site's name is #{site_name}.

    150 | 151 | 152 | {# passback.site_name = "gittub.com" #} 153 | ``` 154 | 155 | For your naming convenience, you can also use the `load` function, which is identical to `partial` but withholds output. 156 | 157 | ### Post-processing 158 | 159 | New in the latest version of Toffee, you can pass a `postProcess` function to Toffee. This works for individual partials or even 160 | an entire document. The `postProcess` function performs a final transformation on your output. 161 | 162 | One smart use of postProcess is to find anything inside triple tick marks and perform a code higlighting. 163 | 164 | ```html 165 | {# print partial './something.toffee', { foo: 1000 postProcess: (s) -> 166 | find_and_higlight_code_in s } #} 167 | ``` 168 | 169 | You could define `find_and_highlight_code_in` anywhere in your publishing stack. You can pass it from your webserver, define it above, whatever. If you're 170 | doing this server-side, consider the popular Node module _highlight.js_. In that case, define a highlight function that hunts for 171 | triple tick marks and then uses highlight's highlighter to transform it. 172 | 173 | Your users shouldn't have to wait for client-side JS to re-process your pages. 174 | 175 | ### Indentation 176 | 177 | Since CoffeeScript is sensitive to indenting, so is Toffee. 178 | 179 | But...Toffee doesn't care where you start your CoffeeScript. When you want to create a coffee block, 180 | you can indent it however you like, and all that matters is that the internal, 181 | relative indenting is correct. For example, these are identical: 182 | 183 | ```html 184 |

    185 | {# if user.is_awesome {: YAY! :} #} 186 |

    187 |

    188 | {# if user.is_awesome {: YAY! :} #} 189 |

    190 | ``` 191 | 192 | In other words, feel free to pick whatever indentation baseline you want when entering a region of CoffeeScript. 193 | 194 | Note that where you put your toffee mode tokens (`{:`) is important, as the following illustrates: 195 | 196 | ```html 197 |

    198 | {# if x is true if y is true if z is true w = true {: x is true! Dunno 199 | anything about y or z, though. :} #} 200 |

    201 | ``` 202 | 203 | Why? Because this is roughly the same as saying: 204 | 205 | ```html 206 |

    207 | {# if x is true if y is true if z is true w = true print "\n x is true! Dunno 208 | anything about y or z, though.\n " #} 209 |

    210 | ``` 211 | 212 | One syntactic convenience: if you start a `{:` on the same line as some preceeding CoffeeScript, it's 213 | treated the same as putting it on a new line and indenting one level. 214 | So the following three conditionals are the same: 215 | 216 | ```html 217 | {# if x is true {:yay:} #} 218 | ``` 219 | 220 | ```html 221 | {# if x is true {:yay:} #} 222 | ``` 223 | 224 | ```html 225 | {# if x is true {: yay :} #} 226 | ``` 227 | 228 | The third example has extra whitespace around the "yay," but otherwise the three are logically identical. 229 | 230 | ### One gotcha with indenting 231 | 232 | THIS IS AN ERROR 233 | 234 | ```html 235 | {# if x is 0 {:Yay!:} else {:Burned:} #} 236 | ``` 237 | 238 | Note that the indentations before the 'if' and the 'else' are technically different, 239 | as the `if` has only 1 space before it, and the `else` has 2. This is better style anyway: 240 | 241 | GOOD 242 | 243 | ```html 244 | {# if x is 0 {:Yay!:} else {:Burned:} #} 245 | ``` 246 | 247 | With a single line of CoffeeScript, feel free to keep it all on one line: 248 | 249 | GOOD 250 | 251 | ```html 252 |
    {# foo = "bar" #}
    253 | ``` 254 | 255 | ## Commenting out a block of toffee 256 | 257 | In toffee mode, you can comment out a region with `{##` and `##}`. 258 | 259 | ```html 260 |
    261 | I don't want to output this anymore... {## 262 |

    An ode to Ruby on Rails

    263 |

    #{partial 'ode.toffee'}

    264 | ##} 265 |
    266 | ``` 267 | 268 | ## Escaping 269 | 270 | In your CoffeeScript, the `print` function lets you print the raw value of a variable: 271 | 272 | ``` 273 | {# 274 | danger_code = "" 275 | print danger_code 276 | #} 277 | ``` 278 | 279 | But in toffee mode, `#{some_expression}` output is escaped intelligently by default: 280 | 281 | ```html 282 | 283 |

    #{danger_code}

    284 | ``` 285 | 286 | You can control the escaping, but here are the defaults: 287 | 288 | - if it's a string or scalar, it is escaped for HTML safety. 289 | - it's an array or object, it is converted to JSON. 290 | 291 | ### Custom Escaping 292 | 293 | You can bypass the above rules. 294 | 295 | - `#{json foo}`: this outputs foo as JSON. 296 | - `#{raw foo}`: this outputs foo in raw text. 297 | - `#{html foo}`: this outputs foo, escaped as HTML. For a scalar, it's the same as `#{foo}`, but it's available in case you 298 | (1) override the default escaping or (2) turn off auto-escaping (both explained below). 299 | - `#{partial "foo.toffee"}` and `#{snippet "foo.toffee"}`: unescaped, since you don't want to escape your own templates 300 | 301 | When any of the functions mentioned above are leftmost in a `#{}` token in toffee mode, their output is left untouched by the 302 | built in escape function. 303 | 304 | These functions are also available to you in coffee mode. 305 | 306 | ```html 307 |

    308 | Want to read some JSON, human? {# foo = [1,2,3, {bar: "none"}] 309 | foo_as_json_as_html = html json foo print foo_as_json_as_html #} 310 |

    311 | ``` 312 | 313 | _Note!_ if you pass a variable to the template called `json`, `raw`, or `html`, Toffee won't create these helper functions, which would override your vars. 314 | In this case, you can access the escape functions through their official titles, `__toffee.raw`, etc. 315 | 316 | Overriding the default `escape`: 317 | 318 | - If you pass a variable to your template called `escape`, this will be used as the default escape. In toffee mode, everything inside `#{}` that isn't subject to an above-mentioned exception will go through your `escape` function. 319 | 320 | Turning off autoescaping entirely: 321 | 322 | - If you set `autoEscape: false` when creating the engine, the default will be raw across your project. (See more on that below under Express 3.x settings.) 323 | - Alternatively, you could pass the var `escape: (x) -> x` to turn off escaping for a given template. 324 | 325 | ## Common Questions 326 | 327 | #### How does it compare to eco? 328 | 329 | Eco is another CoffeeScript templating language and inspiration for Toffee. 330 | The syntaxes are pretty different, so pick the one you prefer. 331 | 332 | One big Toffee advantage: multiple lines of CoffeeScript just look like CoffeeScript. Compare: 333 | 334 | ECO 335 | 336 | ``` 337 | <% if @projects.length: %> 338 | <% for project in @projects: %> 339 | <% if project.is_active: %> 340 |

    <%= project.name %> | <%= project.description %>

    341 | <% end %> 342 | <% end %> 343 | <% end %> 344 | ``` 345 | 346 | TOFFEE 347 | 348 | ```html 349 | {# if projects.length for project in projects if project.is_active {: 350 |

    #{project.name} | #{project.description}

    351 | :} #} 352 | ``` 353 | 354 | With Toffee's syntax, brackets enclose regions not directives, so your editor 355 | will let you collapse and expand sections of code. And if you click on one of the brackets in most 356 | editors, it will highlight the matching bracket. 357 | 358 | #### Does it cache templates? 359 | 360 | In Express 2.0, that's up to Express. When used in Express 3.0, Toffee asynchronously monitors known templates and recompiles them in the background when necessary. So you don't need to restart your production webserver whenever you edit a template. 361 | 362 | #### Does it find line numbers in errors? 363 | 364 | Yes, Toffee does a very good job of that. There are 3 possible places you can hit an error in Toffee: 365 | 366 | - in the language itself, say a parse error 367 | - in the CoffeeScript, preventing it from compiling to JS 368 | - runtime, in the final JS 369 | 370 | Stack traces are converted to lines in Toffee and show you where the problem is. 371 | By default when Toffee hits an error it replies with some pretty-printed HTML showing you the problem. 372 | This can be overridden, as explained below in the Express 3.0 section. 373 | 374 | ### Does it support partials? (a.k.a includes) 375 | 376 | Yes. In Express 2.0, Express itself is responsible for including other files, and they call this system "partials." In Express 3.0 and in the browser, 377 | Toffee defines the `partial` function, and it works as you'd expect. 378 | 379 | ```html 380 |
    #{partial '../foo/bar.toffee', name: "Chris"}
    381 | ``` 382 | 383 | Inside a region of CoffeeScript, you can print or capture the result of a partial. 384 | 385 | ```html 386 |
    387 | {# if session print partial 'user_menu.toffee', info: session.info else print 388 | partial 'guest_menu.toffee' #} 389 |
    390 | ``` 391 | 392 | Like Express's `partial` function, Toffee's function passes all available vars to the child template. 393 | For example, in the above code, `session` would also be available in the user_menu.toffee file. If you don't want this scoping, use Toffee's `snippet` function, which sandboxes it: 394 | 395 | ``` 396 | {# 397 | if session 398 | print partial 'user.toffee', info: session.info # session will also be passed 399 | print snippet 'user.toffee', info: session.info # session will not be passed 400 | #} 401 | ``` 402 | 403 | #### Does it support `layout`? 404 | 405 | Yes, this works in NodeJS and Express 3.0, emulating the Express 2.0 way. The var `layout` is considered special, and should 406 | be the path to your layout file. 407 | 408 | ## Installation & Usage 409 | 410 | - [Using Toffee in NodeJS](https://github.com/malgorithms/toffee/wiki/NodeJS-Usage) 411 | - [Using Toffee in Express 3](https://github.com/malgorithms/toffee/wiki/Express3-Usage) 412 | - [Using Toffee in Express 2](https://github.com/malgorithms/toffee/wiki/Express2-Usage) 413 | - [Using Toffee in the Browser](https://github.com/malgorithms/toffee/wiki/Browser-Usage) 414 | 415 | # contributing & asking for fixes. 416 | 417 | If you have a problem with Toffee let me know, and I'll fix it ASAP. 418 | 419 | Also, I'm likely to accept good pull requests. 420 | 421 | If you'd like to edit code for this project, note that you should always edit the `.coffee` files, 422 | as the `.js` files are generated automatically by building. 423 | 424 | To build and test your changes 425 | 426 | ``` 427 | # icake is iced-coffee-script's version of cake 428 | > icake build 429 | > icake test 430 | ``` 431 | -------------------------------------------------------------------------------- /lib/engine.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.7 2 | (function() { 3 | var LockTable, MAX_CACHED_SANDBOXES, Pool, engine, fs, path, ref, sandboxCons, states, tweakables, util, utils, view, vm, 4 | bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 5 | 6 | view = require('./view').view; 7 | 8 | ref = require('./consts'), states = ref.states, tweakables = ref.tweakables; 9 | 10 | Pool = require('./pool').Pool; 11 | 12 | utils = require('./utils'); 13 | 14 | fs = require('fs'); 15 | 16 | path = require('path'); 17 | 18 | util = require('util'); 19 | 20 | vm = require('vm'); 21 | 22 | LockTable = require('iced-lock').Table; 23 | 24 | MAX_CACHED_SANDBOXES = 100; 25 | 26 | sandboxCons = function() { 27 | return vm.createContext({}); 28 | }; 29 | 30 | engine = (function() { 31 | function engine(options) { 32 | this._fn_partial = bind(this._fn_partial, this); 33 | this._fn_snippet = bind(this._fn_snippet, this); 34 | this._fn_load = bind(this._fn_load, this); 35 | this._inlineInclude = bind(this._inlineInclude, this); 36 | this.run = bind(this.run, this); 37 | this.render = bind(this.render, this); 38 | options = options || {}; 39 | this.verbose = options.verbose || false; 40 | this.pool = new Pool(sandboxCons, options.poolSize || MAX_CACHED_SANDBOXES); 41 | this.prettyPrintErrors = options.prettyPrintErrors != null ? options.prettyPrintErrors : true; 42 | this.prettyLogErrors = options.prettyLogErrors != null ? options.prettyLogErrors : true; 43 | this.autoEscape = options.autoEscape != null ? options.autoEscape : true; 44 | this.cache = options.cache != null ? options.cache : true; 45 | this.additionalErrorHandler = options.additionalErrorHandler || null; 46 | this.viewCache = {}; 47 | this.fsErrorCache = {}; 48 | this.filenameCache = {}; 49 | this.fileLockTable = new LockTable(); 50 | } 51 | 52 | engine.prototype._log = function(o) { 53 | var ref1; 54 | if (this.verbose) { 55 | if ((ref1 = typeof o) === "string" || ref1 === "number" || ref1 === "boolean") { 56 | return console.log("toffee: " + o); 57 | } else { 58 | return console.log("toffee: " + (util.inspect(o))); 59 | } 60 | } 61 | }; 62 | 63 | engine.prototype.normalizeFilename = function(dir, filename) { 64 | var cache, normalized; 65 | cache = this.filenameCache[dir]; 66 | if (cache == null) { 67 | this.filenameCache[dir] = {}; 68 | cache = {}; 69 | } 70 | normalized = cache[filename]; 71 | if (normalized == null) { 72 | normalized = path.normalize(path.resolve(dir, filename)); 73 | this.filenameCache[dir][filename] = normalized; 74 | } 75 | return normalized; 76 | }; 77 | 78 | engine.prototype.render = function(filename, options, cb) { 79 | return this.run(filename, options, cb); 80 | }; 81 | 82 | engine.prototype.run = function(filename, options, cb) { 83 | 84 | /* 85 | "options" contains the pub vars and may contain special items: 86 | layout: path to a template expecting a body var (express 2.x style, but for use with express 3.x) 87 | postProcess: a function which takes the string of output and post processes it (returning new string) 88 | __toffee.dir: path to look relative to 89 | __toffee.parent: parent file 90 | __toffee.noInheritance: if true, don't pass variables through unless explicitly passed 91 | __toffee.repress if true, don't output anything; useful with including definition files with passback of vars 92 | __toffee.autoEscape: if set as false, don't escape output of #{} vars by default 93 | */ 94 | var err, k, layout_options, post_process, ref1, ref2, ref3, ref4, ref5, res, v; 95 | if (options.prettyPrintErrors == null) { 96 | options.prettyPrintErrors = this.prettyPrintErrors; 97 | } 98 | if (options.prettyLogErrors == null) { 99 | options.prettyLogErrors = this.prettyLogErrors; 100 | } 101 | if (options.additionalErrorHandler == null) { 102 | options.additionalErrorHandler = this.additionalErrorHandler; 103 | } 104 | if (options.autoEscape == null) { 105 | options.autoEscape = this.autoEscape; 106 | } 107 | post_process = options.postProcess; 108 | options.postProcess = null; 109 | if (options != null ? options.layout : void 0) { 110 | layout_options = {}; 111 | for (k in options) { 112 | v = options[k]; 113 | if (k !== "layout") { 114 | layout_options[k] = v; 115 | } 116 | } 117 | } 118 | ref1 = this.runSync(filename, options), err = ref1[0], res = ref1[1]; 119 | if (err && this.prettyPrintErrors) { 120 | ref2 = [null, err], err = ref2[0], res = ref2[1]; 121 | } 122 | if ((!err) && (layout_options != null)) { 123 | layout_options.body = res; 124 | ref3 = this.runSync(options.layout, layout_options), err = ref3[0], res = ref3[1]; 125 | if (err && this.prettyPrintErrors) { 126 | ref4 = [null, err], err = ref4[0], res = ref4[1]; 127 | } 128 | } 129 | if ((!err) && (typeof post_process === "function")) { 130 | ref5 = this.postProcess(post_process, res), err = ref5[0], res = ref5[1]; 131 | } 132 | return cb(err, res); 133 | }; 134 | 135 | engine.prototype.postProcess = function(fn, res) { 136 | var e, err; 137 | err = null; 138 | try { 139 | res = fn(res); 140 | } catch (error) { 141 | e = error; 142 | err = e; 143 | } 144 | return [err, res]; 145 | }; 146 | 147 | engine.prototype.runSync = function(filename, options) { 148 | 149 | /* 150 | "options" the same as run() above 151 | */ 152 | var ctx, err, realpath, ref1, ref2, ref3, res, start_time, v; 153 | start_time = Date.now(); 154 | options = options || {}; 155 | options.__toffee = options.__toffee || {}; 156 | options.__toffee.dir = options.__toffee.dir || process.cwd(); 157 | realpath = this.normalizeFilename(options.__toffee.dir, filename); 158 | if (this.cache) { 159 | v = (this._viewCacheGet(realpath)) || (this._loadCacheAndMonitor(realpath, options)); 160 | } else { 161 | v = this._loadWithoutCache(realpath, options); 162 | } 163 | if (v) { 164 | if (this.fsErrorCache[realpath]) { 165 | ref1 = [new Error("Couldn't load " + realpath), null], err = ref1[0], res = ref1[1]; 166 | } else { 167 | options.__toffee.parent = realpath; 168 | options.partial = options.partial || (function(_this) { 169 | return function(fname, lvars) { 170 | return _this._fn_partial(fname, lvars, realpath, options); 171 | }; 172 | })(this); 173 | options.snippet = options.snippet || (function(_this) { 174 | return function(fname, lvars) { 175 | return _this._fn_snippet(fname, lvars, realpath, options); 176 | }; 177 | })(this); 178 | options.load = options.load || (function(_this) { 179 | return function(fname, lvars) { 180 | return _this._fn_load(fname, lvars, realpath, options); 181 | }; 182 | })(this); 183 | options.print = options.print || (function(_this) { 184 | return function(txt) { 185 | return _this._fn_print(txt, options); 186 | }; 187 | })(this); 188 | if (options.console == null) { 189 | options.console = { 190 | log: console.log 191 | }; 192 | } 193 | ctx = this.pool.get(); 194 | ref2 = v.run(options, ctx), err = ref2[0], res = ref2[1]; 195 | this.pool.release(ctx); 196 | } 197 | } else { 198 | ref3 = [new Error("Couldn't load " + realpath), null], err = ref3[0], res = ref3[1]; 199 | } 200 | this._log(realpath + " run in " + (Date.now() - start_time) + "ms"); 201 | return [err, res]; 202 | }; 203 | 204 | engine.prototype._viewCacheGet = function(filename) { 205 | if (this.viewCache[filename] == null) { 206 | return null; 207 | } else if (this.fsErrorCache[filename] == null) { 208 | return this.viewCache[filename]; 209 | } else if ((Date.now() - this.fsErrorCache[filename]) < tweakables.MISSING_FILE_RECHECK) { 210 | return this.viewCache[filename]; 211 | } else { 212 | return null; 213 | } 214 | }; 215 | 216 | engine.prototype._inlineInclude = function(filename, local_vars, parent_realpath, parent_options) { 217 | var err, i, k, len, noInheritance, options, ref1, ref2, ref3, repress, res, reserved, v; 218 | options = local_vars || {}; 219 | options.passback = {}; 220 | options.__toffee = options.__toffee || {}; 221 | options.__toffee.dir = path.dirname(parent_realpath); 222 | options.__toffee.parent = parent_realpath; 223 | noInheritance = options.__toffee.noInheritance; 224 | repress = options.__toffee.repress; 225 | reserved = {}; 226 | ref1 = ["passback", "load", "print", "partial", "snippet", "layout", "__toffee", "postProcess"]; 227 | for (i = 0, len = ref1.length; i < len; i++) { 228 | k = ref1[i]; 229 | reserved[k] = true; 230 | } 231 | if (!noInheritance) { 232 | for (k in parent_options) { 233 | v = parent_options[k]; 234 | if ((local_vars != null ? local_vars[k] : void 0) == null) { 235 | if (reserved[k] == null) { 236 | options[k] = v; 237 | } 238 | } 239 | } 240 | } 241 | ref2 = this.runSync(filename, options), err = ref2[0], res = ref2[1]; 242 | ref3 = options.passback; 243 | for (k in ref3) { 244 | v = ref3[k]; 245 | parent_options[k] = v; 246 | } 247 | return err || res; 248 | }; 249 | 250 | engine.prototype._fn_load = function(fname, lvars, realpath, options) { 251 | lvars = lvars != null ? lvars : {}; 252 | lvars.__toffee = lvars.__toffee || {}; 253 | lvars.__toffee.repress = true; 254 | return this._inlineInclude(fname, lvars, realpath, options); 255 | }; 256 | 257 | engine.prototype._fn_snippet = function(fname, lvars, realpath, options) { 258 | lvars = lvars != null ? lvars : {}; 259 | lvars.__toffee = lvars.__toffee || {}; 260 | lvars.__toffee.noInheritance = true; 261 | return this._inlineInclude(fname, lvars, realpath, options); 262 | }; 263 | 264 | engine.prototype._fn_partial = function(fname, lvars, realpath, options) { 265 | return this._inlineInclude(fname, lvars, realpath, options); 266 | }; 267 | 268 | engine.prototype._fn_print = function(txt, options) { 269 | if (options.__toffee.state === states.COFFEE) { 270 | options.__toffee.out.push(txt); 271 | return ''; 272 | } else { 273 | return txt; 274 | } 275 | }; 276 | 277 | engine.prototype._loadWithoutCache = function(filename, options) { 278 | var e, ref1, txt, v, view_options; 279 | try { 280 | txt = fs.readFileSync(filename, 'utf8'); 281 | } catch (error) { 282 | e = error; 283 | txt = "Error: Could not read " + filename; 284 | if (((ref1 = options.__toffee) != null ? ref1.parent : void 0) != null) { 285 | txt += " first requested in " + options.__toffee.parent; 286 | } 287 | } 288 | view_options = this._generateViewOptions(filename); 289 | v = new view(txt, view_options); 290 | return v; 291 | }; 292 | 293 | engine.prototype._loadCacheAndMonitor = function(filename, options) { 294 | var e, previous_fs_err, ref1, txt, v, view_options; 295 | previous_fs_err = this.fsErrorCache[filename] != null; 296 | try { 297 | txt = fs.readFileSync(filename, 'utf8'); 298 | if (this.fsErrorCache[filename] != null) { 299 | delete this.fsErrorCache[filename]; 300 | } 301 | } catch (error) { 302 | e = error; 303 | txt = "Error: Could not read " + filename; 304 | if (((ref1 = options.__toffee) != null ? ref1.parent : void 0) != null) { 305 | txt += " first requested in " + options.__toffee.parent; 306 | } 307 | this.fsErrorCache[filename] = Date.now(); 308 | } 309 | if (this.fsErrorCache[filename] && previous_fs_err && this.viewCache[filename]) { 310 | return this.viewCache[filename]; 311 | } else { 312 | view_options = this._generateViewOptions(filename); 313 | v = new view(txt, view_options); 314 | this.viewCache[filename] = v; 315 | this._monitorForChanges(filename, options); 316 | return v; 317 | } 318 | }; 319 | 320 | engine.prototype._reloadFileInBkg = function(filename, options) { 321 | this._log(filename + " acquiring lock to read"); 322 | return this.fileLockTable.acquire2({ 323 | name: filename 324 | }, (function(_this) { 325 | return function(lock) { 326 | return fs.readFile(filename, 'utf8', function(err, txt) { 327 | var ctx, ref1, v, view_options, waiting_for_view; 328 | if (!err) { 329 | _this._log((Date.now()) + " - " + filename + " changed to " + (txt != null ? txt.length : void 0) + " bytes. " + (txt != null ? typeof txt.replace === "function" ? txt.replace(/\n/g, '').slice(0, 80) : void 0 : void 0)); 330 | } 331 | waiting_for_view = false; 332 | if (err || (txt !== _this.viewCache[filename].txt)) { 333 | if (err) { 334 | _this.fsErrorCache[filename] = Date.now(); 335 | txt = "Error: Could not read " + filename; 336 | if (((ref1 = options.__toffee) != null ? ref1.parent : void 0) != null) { 337 | txt += " requested in " + options.__toffee.parent; 338 | } 339 | } 340 | if (!(err && _this.viewCache[filename].fsError)) { 341 | view_options = _this._generateViewOptions(filename); 342 | ctx = _this.pool.get(); 343 | view_options.ctx = ctx; 344 | view_options.cb = function(v) { 345 | _this._log(filename + " updated and ready"); 346 | _this.viewCache[filename] = v; 347 | _this.pool.release(ctx); 348 | _this._log(filename + " lock releasing (view_options.cb)"); 349 | return lock.release(); 350 | }; 351 | waiting_for_view = true; 352 | if (err) { 353 | view_options.fsError = true; 354 | } 355 | v = new view(txt, view_options); 356 | } 357 | } 358 | if (!waiting_for_view) { 359 | _this._log(filename + " lock releasing (not waiting for view)"); 360 | return lock.release(); 361 | } 362 | }); 363 | }; 364 | })(this)); 365 | }; 366 | 367 | engine.prototype._generateViewOptions = function(filename) { 368 | return { 369 | fileName: filename, 370 | verbose: this.verbose, 371 | prettyPrintErrors: this.prettyPrintErrors, 372 | prettyLogErrors: this.prettyLogErrors, 373 | autoEscape: this.autoEscape, 374 | additionalErrorHandler: this.additionalErrorHandler 375 | }; 376 | }; 377 | 378 | engine.prototype._monitorForChanges = function(filename, options) { 379 | 380 | /* 381 | we must continuously unwatch/rewatch because some editors/systems invoke a "rename" 382 | event and we'll end up following the wrong, old 'file' as a new one 383 | is dropped in its place. 384 | 385 | Files that are missing are ignored here because they get picked up by new calls to _loadCacheAndMonitor 386 | */ 387 | var e, fsw; 388 | if (this.fsErrorCache[filename] == null) { 389 | fsw = null; 390 | try { 391 | this._log(filename + " starting fs.watch()"); 392 | return fsw = fs.watch(filename, { 393 | persistent: true 394 | }, (function(_this) { 395 | return function(change) { 396 | _this._log(filename + " closing fs.watch()"); 397 | fsw.close(); 398 | _this._monitorForChanges(filename, options); 399 | return _this._reloadFileInBkg(filename, options); 400 | }; 401 | })(this)); 402 | } catch (error) { 403 | e = error; 404 | this._log("fs.watch() failed for " + filename + "; settings fsErrorCache = true"); 405 | return this.fsErrorCache[filename] = Date.now(); 406 | } 407 | } 408 | }; 409 | 410 | return engine; 411 | 412 | })(); 413 | 414 | exports.engine = engine; 415 | 416 | }).call(this); 417 | -------------------------------------------------------------------------------- /lib/coffee-script/command.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var BANNER, CoffeeScript, EventEmitter, SWITCHES, compileJoin, compileOptions, compilePath, compileScript, compileStdio, exec, forkNode, fs, helpers, hidden, joinTimeout, lint, loadRequires, notSources, optionParser, optparse, opts, outputPath, parseOptions, path, printLine, printTokens, printWarn, removeSource, sourceCode, sources, spawn, timeLog, unwatchDir, usage, version, wait, watch, watchDir, watchers, writeJs, _ref; 4 | 5 | fs = require('fs'); 6 | 7 | path = require('path'); 8 | 9 | helpers = require('./helpers'); 10 | 11 | optparse = require('./optparse'); 12 | 13 | CoffeeScript = require('./coffee-script'); 14 | 15 | _ref = require('child_process'), spawn = _ref.spawn, exec = _ref.exec; 16 | 17 | EventEmitter = require('events').EventEmitter; 18 | 19 | helpers.extend(CoffeeScript, new EventEmitter); 20 | 21 | printLine = function(line) { 22 | return process.stdout.write(line + '\n'); 23 | }; 24 | 25 | printWarn = function(line) { 26 | return process.stderr.write(line + '\n'); 27 | }; 28 | 29 | hidden = function(file) { 30 | return /^\.|~$/.test(file); 31 | }; 32 | 33 | BANNER = 'Usage: coffee [options] path/to/script.coffee -- [args]\n\nIf called without options, `coffee` will run your script.'; 34 | 35 | SWITCHES = [['-b', '--bare', 'compile without a top-level function wrapper'], ['-c', '--compile', 'compile to JavaScript and save as .js files'], ['-e', '--eval', 'pass a string from the command line as input'], ['-h', '--help', 'display this help message'], ['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling'], ['-l', '--lint', 'pipe the compiled JavaScript through JavaScript Lint'], ['-n', '--nodes', 'print out the parse tree that the parser produces'], ['--nodejs [ARGS]', 'pass options directly to the "node" binary'], ['-o', '--output [DIR]', 'set the output directory for compiled JavaScript'], ['-p', '--print', 'print out the compiled JavaScript'], ['-r', '--require [FILE*]', 'require a library before executing your script'], ['-s', '--stdio', 'listen for and compile scripts over stdio'], ['-t', '--tokens', 'print out the tokens that the lexer/rewriter produce'], ['-v', '--version', 'display the version number'], ['-w', '--watch', 'watch scripts for changes and rerun commands']]; 36 | 37 | opts = {}; 38 | 39 | sources = []; 40 | 41 | sourceCode = []; 42 | 43 | notSources = {}; 44 | 45 | watchers = {}; 46 | 47 | optionParser = null; 48 | 49 | exports.run = function() { 50 | var literals, source, _i, _len, _results; 51 | parseOptions(); 52 | if (opts.nodejs) { 53 | return forkNode(); 54 | } 55 | if (opts.help) { 56 | return usage(); 57 | } 58 | if (opts.version) { 59 | return version(); 60 | } 61 | if (opts.require) { 62 | loadRequires(); 63 | } 64 | if (opts.interactive) { 65 | return require('./repl'); 66 | } 67 | if (opts.watch && !fs.watch) { 68 | return printWarn("The --watch feature depends on Node v0.6.0+. You are running " + process.version + "."); 69 | } 70 | if (opts.stdio) { 71 | return compileStdio(); 72 | } 73 | if (opts["eval"]) { 74 | return compileScript(null, sources[0]); 75 | } 76 | if (!sources.length) { 77 | return require('./repl'); 78 | } 79 | literals = opts.run ? sources.splice(1) : []; 80 | process.argv = process.argv.slice(0, 2).concat(literals); 81 | process.argv[0] = 'coffee'; 82 | process.execPath = require.main.filename; 83 | _results = []; 84 | for (_i = 0, _len = sources.length; _i < _len; _i++) { 85 | source = sources[_i]; 86 | _results.push(compilePath(source, true, path.normalize(source))); 87 | } 88 | return _results; 89 | }; 90 | 91 | compilePath = function(source, topLevel, base) { 92 | return fs.stat(source, function(err, stats) { 93 | if (err && err.code !== 'ENOENT') { 94 | throw err; 95 | } 96 | if ((err != null ? err.code : void 0) === 'ENOENT') { 97 | if (topLevel && source.slice(-7) !== '.coffee') { 98 | source = sources[sources.indexOf(source)] = "" + source + ".coffee"; 99 | return compilePath(source, topLevel, base); 100 | } 101 | if (topLevel) { 102 | console.error("File not found: " + source); 103 | process.exit(1); 104 | } 105 | return; 106 | } 107 | if (stats.isDirectory()) { 108 | if (opts.watch) { 109 | watchDir(source, base); 110 | } 111 | return fs.readdir(source, function(err, files) { 112 | var file, index, _i, _len, _ref1, _ref2, _results; 113 | if (err && err.code !== 'ENOENT') { 114 | throw err; 115 | } 116 | if ((err != null ? err.code : void 0) === 'ENOENT') { 117 | return; 118 | } 119 | index = sources.indexOf(source); 120 | [].splice.apply(sources, [index, index - index + 1].concat(_ref1 = (function() { 121 | var _i, _len, _results; 122 | _results = []; 123 | for (_i = 0, _len = files.length; _i < _len; _i++) { 124 | file = files[_i]; 125 | _results.push(path.join(source, file)); 126 | } 127 | return _results; 128 | })())), _ref1; 129 | [].splice.apply(sourceCode, [index, index - index + 1].concat(_ref2 = files.map(function() { 130 | return null; 131 | }))), _ref2; 132 | _results = []; 133 | for (_i = 0, _len = files.length; _i < _len; _i++) { 134 | file = files[_i]; 135 | if (!hidden(file)) { 136 | _results.push(compilePath(path.join(source, file), false, base)); 137 | } 138 | } 139 | return _results; 140 | }); 141 | } else if (topLevel || path.extname(source) === '.coffee') { 142 | if (opts.watch) { 143 | watch(source, base); 144 | } 145 | return fs.readFile(source, function(err, code) { 146 | if (err && err.code !== 'ENOENT') { 147 | throw err; 148 | } 149 | if ((err != null ? err.code : void 0) === 'ENOENT') { 150 | return; 151 | } 152 | return compileScript(source, code.toString(), base); 153 | }); 154 | } else { 155 | notSources[source] = true; 156 | return removeSource(source, base); 157 | } 158 | }); 159 | }; 160 | 161 | compileScript = function(file, input, base) { 162 | var o, options, t, task; 163 | o = opts; 164 | options = compileOptions(file); 165 | try { 166 | t = task = { 167 | file: file, 168 | input: input, 169 | options: options 170 | }; 171 | CoffeeScript.emit('compile', task); 172 | if (o.tokens) { 173 | return printTokens(CoffeeScript.tokens(t.input)); 174 | } else if (o.nodes) { 175 | return printLine(CoffeeScript.nodes(t.input).toString().trim()); 176 | } else if (o.run) { 177 | return CoffeeScript.run(t.input, t.options); 178 | } else if (o.join && t.file !== o.join) { 179 | sourceCode[sources.indexOf(t.file)] = t.input; 180 | return compileJoin(); 181 | } else { 182 | t.output = CoffeeScript.compile(t.input, t.options); 183 | CoffeeScript.emit('success', task); 184 | if (o.print) { 185 | return printLine(t.output.trim()); 186 | } else if (o.compile) { 187 | return writeJs(t.file, t.output, base); 188 | } else if (o.lint) { 189 | return lint(t.file, t.output); 190 | } 191 | } 192 | } catch (err) { 193 | CoffeeScript.emit('failure', err, task); 194 | if (CoffeeScript.listeners('failure').length) { 195 | return; 196 | } 197 | if (o.watch) { 198 | return printLine(err.message + '\x07'); 199 | } 200 | printWarn(err instanceof Error && err.stack || ("ERROR: " + err)); 201 | return process.exit(1); 202 | } 203 | }; 204 | 205 | compileStdio = function() { 206 | var code, stdin; 207 | code = ''; 208 | stdin = process.openStdin(); 209 | stdin.on('data', function(buffer) { 210 | if (buffer) { 211 | return code += buffer.toString(); 212 | } 213 | }); 214 | return stdin.on('end', function() { 215 | return compileScript(null, code); 216 | }); 217 | }; 218 | 219 | joinTimeout = null; 220 | 221 | compileJoin = function() { 222 | if (!opts.join) { 223 | return; 224 | } 225 | if (!sourceCode.some(function(code) { 226 | return code === null; 227 | })) { 228 | clearTimeout(joinTimeout); 229 | return joinTimeout = wait(100, function() { 230 | return compileScript(opts.join, sourceCode.join('\n'), opts.join); 231 | }); 232 | } 233 | }; 234 | 235 | loadRequires = function() { 236 | var realFilename, req, _i, _len, _ref1; 237 | realFilename = module.filename; 238 | module.filename = '.'; 239 | _ref1 = opts.require; 240 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 241 | req = _ref1[_i]; 242 | require(req); 243 | } 244 | return module.filename = realFilename; 245 | }; 246 | 247 | watch = function(source, base) { 248 | var compile, compileTimeout, prevStats, rewatch, watchErr, watcher; 249 | prevStats = null; 250 | compileTimeout = null; 251 | watchErr = function(e) { 252 | if (e.code === 'ENOENT') { 253 | if (sources.indexOf(source) === -1) { 254 | return; 255 | } 256 | try { 257 | rewatch(); 258 | return compile(); 259 | } catch (e) { 260 | removeSource(source, base, true); 261 | return compileJoin(); 262 | } 263 | } else { 264 | throw e; 265 | } 266 | }; 267 | compile = function() { 268 | clearTimeout(compileTimeout); 269 | return compileTimeout = wait(25, function() { 270 | return fs.stat(source, function(err, stats) { 271 | if (err) { 272 | return watchErr(err); 273 | } 274 | if (prevStats && stats.size === prevStats.size && stats.mtime.getTime() === prevStats.mtime.getTime()) { 275 | return rewatch(); 276 | } 277 | prevStats = stats; 278 | return fs.readFile(source, function(err, code) { 279 | if (err) { 280 | return watchErr(err); 281 | } 282 | compileScript(source, code.toString(), base); 283 | return rewatch(); 284 | }); 285 | }); 286 | }); 287 | }; 288 | try { 289 | watcher = fs.watch(source, compile); 290 | } catch (e) { 291 | watchErr(e); 292 | } 293 | return rewatch = function() { 294 | if (watcher != null) { 295 | watcher.close(); 296 | } 297 | return watcher = fs.watch(source, compile); 298 | }; 299 | }; 300 | 301 | watchDir = function(source, base) { 302 | var readdirTimeout, watcher; 303 | readdirTimeout = null; 304 | try { 305 | return watcher = fs.watch(source, function() { 306 | clearTimeout(readdirTimeout); 307 | return readdirTimeout = wait(25, function() { 308 | return fs.readdir(source, function(err, files) { 309 | var file, _i, _len, _results; 310 | if (err) { 311 | if (err.code !== 'ENOENT') { 312 | throw err; 313 | } 314 | watcher.close(); 315 | return unwatchDir(source, base); 316 | } 317 | _results = []; 318 | for (_i = 0, _len = files.length; _i < _len; _i++) { 319 | file = files[_i]; 320 | if (!(!hidden(file) && !notSources[file])) { 321 | continue; 322 | } 323 | file = path.join(source, file); 324 | if (sources.some(function(s) { 325 | return s.indexOf(file) >= 0; 326 | })) { 327 | continue; 328 | } 329 | sources.push(file); 330 | sourceCode.push(null); 331 | _results.push(compilePath(file, false, base)); 332 | } 333 | return _results; 334 | }); 335 | }); 336 | }); 337 | } catch (e) { 338 | if (e.code !== 'ENOENT') { 339 | throw e; 340 | } 341 | } 342 | }; 343 | 344 | unwatchDir = function(source, base) { 345 | var file, prevSources, toRemove, _i, _len; 346 | prevSources = sources.slice(0); 347 | toRemove = (function() { 348 | var _i, _len, _results; 349 | _results = []; 350 | for (_i = 0, _len = sources.length; _i < _len; _i++) { 351 | file = sources[_i]; 352 | if (file.indexOf(source) >= 0) { 353 | _results.push(file); 354 | } 355 | } 356 | return _results; 357 | })(); 358 | for (_i = 0, _len = toRemove.length; _i < _len; _i++) { 359 | file = toRemove[_i]; 360 | removeSource(file, base, true); 361 | } 362 | if (!sources.some(function(s, i) { 363 | return prevSources[i] !== s; 364 | })) { 365 | return; 366 | } 367 | return compileJoin(); 368 | }; 369 | 370 | removeSource = function(source, base, removeJs) { 371 | var index, jsPath; 372 | index = sources.indexOf(source); 373 | sources.splice(index, 1); 374 | sourceCode.splice(index, 1); 375 | if (removeJs && !opts.join) { 376 | jsPath = outputPath(source, base); 377 | return path.exists(jsPath, function(exists) { 378 | if (exists) { 379 | return fs.unlink(jsPath, function(err) { 380 | if (err && err.code !== 'ENOENT') { 381 | throw err; 382 | } 383 | return timeLog("removed " + source); 384 | }); 385 | } 386 | }); 387 | } 388 | }; 389 | 390 | outputPath = function(source, base) { 391 | var baseDir, dir, filename, srcDir; 392 | filename = path.basename(source, path.extname(source)) + '.js'; 393 | srcDir = path.dirname(source); 394 | baseDir = base === '.' ? srcDir : srcDir.substring(base.length); 395 | dir = opts.output ? path.join(opts.output, baseDir) : srcDir; 396 | return path.join(dir, filename); 397 | }; 398 | 399 | writeJs = function(source, js, base) { 400 | var compile, jsDir, jsPath; 401 | jsPath = outputPath(source, base); 402 | jsDir = path.dirname(jsPath); 403 | compile = function() { 404 | if (js.length <= 0) { 405 | js = ' '; 406 | } 407 | return fs.writeFile(jsPath, js, function(err) { 408 | if (err) { 409 | return printLine(err.message); 410 | } else if (opts.compile && opts.watch) { 411 | return timeLog("compiled " + source); 412 | } 413 | }); 414 | }; 415 | return path.exists(jsDir, function(exists) { 416 | if (exists) { 417 | return compile(); 418 | } else { 419 | return exec("mkdir -p " + jsDir, compile); 420 | } 421 | }); 422 | }; 423 | 424 | wait = function(milliseconds, func) { 425 | return setTimeout(func, milliseconds); 426 | }; 427 | 428 | timeLog = function(message) { 429 | return console.log("" + ((new Date).toLocaleTimeString()) + " - " + message); 430 | }; 431 | 432 | lint = function(file, js) { 433 | var conf, jsl, printIt; 434 | printIt = function(buffer) { 435 | return printLine(file + ':\t' + buffer.toString().trim()); 436 | }; 437 | conf = __dirname + '/../../extras/jsl.conf'; 438 | jsl = spawn('jsl', ['-nologo', '-stdin', '-conf', conf]); 439 | jsl.stdout.on('data', printIt); 440 | jsl.stderr.on('data', printIt); 441 | jsl.stdin.write(js); 442 | return jsl.stdin.end(); 443 | }; 444 | 445 | printTokens = function(tokens) { 446 | var strings, tag, token, value; 447 | strings = (function() { 448 | var _i, _len, _ref1, _results; 449 | _results = []; 450 | for (_i = 0, _len = tokens.length; _i < _len; _i++) { 451 | token = tokens[_i]; 452 | _ref1 = [token[0], token[1].toString().replace(/\n/, '\\n')], tag = _ref1[0], value = _ref1[1]; 453 | _results.push("[" + tag + " " + value + "]"); 454 | } 455 | return _results; 456 | })(); 457 | return printLine(strings.join(' ')); 458 | }; 459 | 460 | parseOptions = function() { 461 | var i, o, source, _i, _len; 462 | optionParser = new optparse.OptionParser(SWITCHES, BANNER); 463 | o = opts = optionParser.parse(process.argv.slice(2)); 464 | o.compile || (o.compile = !!o.output); 465 | o.run = !(o.compile || o.print || o.lint); 466 | o.print = !!(o.print || (o["eval"] || o.stdio && o.compile)); 467 | sources = o["arguments"]; 468 | for (i = _i = 0, _len = sources.length; _i < _len; i = ++_i) { 469 | source = sources[i]; 470 | sourceCode[i] = null; 471 | } 472 | }; 473 | 474 | compileOptions = function(filename) { 475 | return { 476 | filename: filename, 477 | bare: opts.bare, 478 | header: opts.compile 479 | }; 480 | }; 481 | 482 | forkNode = function() { 483 | var args, nodeArgs; 484 | nodeArgs = opts.nodejs.split(/\s+/); 485 | args = process.argv.slice(1); 486 | args.splice(args.indexOf('--nodejs'), 2); 487 | return spawn(process.execPath, nodeArgs.concat(args), { 488 | cwd: process.cwd(), 489 | env: process.env, 490 | customFds: [0, 1, 2] 491 | }); 492 | }; 493 | 494 | usage = function() { 495 | return printLine((new optparse.OptionParser(SWITCHES, BANNER)).help()); 496 | }; 497 | 498 | version = function() { 499 | return printLine("CoffeeScript version " + CoffeeScript.VERSION); 500 | }; 501 | 502 | }).call(this); 503 | --------------------------------------------------------------------------------