├── 1 ├── 1.spec.coffee ├── 1.coffee └── 1.js ├── 2 ├── listings │ ├── house_roast.txt │ ├── barista.html │ ├── 2.3.spec.coffee │ ├── 2.2.clean.js │ ├── 2.3.coffee │ ├── 2.4.coffee │ ├── barista.coffee │ ├── 2.1.spec.coffee │ ├── 2.1.coffee │ ├── 2.2.coffee │ └── barista.js └── 2.coffee ├── 3 ├── listings │ ├── friends.txt │ ├── file2.txt │ ├── file1.txt │ ├── partygoers.txt │ ├── 3.2.clean.js │ ├── 3.3.coffee │ ├── 3.1.coffee │ ├── 3.2.coffee │ ├── 3.4.coffee │ ├── 3.6.coffee │ ├── 3.5.coffee │ └── 3.7.coffee └── 3.coffee ├── 4 ├── 4.coffee └── listings │ ├── 4.4.coffee │ ├── 4.6.coffee │ ├── 4.7.coffee │ ├── 4.5.spec │ ├── 4.3.coffee │ ├── 4.7.spec │ ├── 4.8.spec │ ├── 4.5.coffee │ ├── 4.1.coffee │ ├── 4.2.spec │ ├── 4.8.coffee │ ├── 4.9.spec │ ├── 4.9.coffee │ └── 4.2.coffee ├── 5 ├── 5.coffee ├── listings │ ├── news.coffee │ ├── images │ │ ├── camera.png │ │ ├── gallery1.png │ │ ├── gallery2.png │ │ ├── gallery3.png │ │ ├── product.png │ │ └── skateboard.png │ ├── 5.9.json │ ├── 5.1.coffee │ ├── 5.6.coffee │ ├── 5.11.coffee │ ├── 5.7.coffee │ ├── data.coffee │ ├── 5.8.coffee │ ├── 5.2.coffee │ ├── 5.5.coffee │ ├── 5.3.coffee │ └── 5.12.coffee └── 5.spec.coffee ├── 6 ├── 6.spec.coffee └── listings │ ├── stock.json │ ├── users.json │ ├── products.json │ ├── 6.1.coffee │ ├── 6.7.coffee │ ├── 6.3.coffee │ ├── db.spec.coffee │ ├── 6.6.coffee │ ├── db.coffee │ ├── 6.4.coffee │ └── 6.2.coffee ├── 7 └── listings │ ├── 7.6.coffee │ ├── 7.1.coffee │ ├── 7.2.coffee │ ├── 7.3.coffee │ ├── server.coffee │ └── 7.5.coffee ├── 8 ├── 8.spec.coffee ├── .#8.spec.coffee ├── listings │ ├── 8.1.spec.coffee │ ├── elephant.coffee │ ├── 8.6.coffee │ ├── 8.4.coffee │ ├── 8.5.coffee │ ├── 8.2.coffee │ ├── 8.3.coffee │ ├── 8.7.coffee │ ├── 8.1.litcoffee │ ├── 8.9.coffee │ └── 8.8.coffee └── the_wild_swans_at_coole.litcoffee ├── 9 ├── 9.spec.coffee └── listings │ ├── competitors-5.txt │ ├── phone_numbers.csv │ ├── 9.5.css │ ├── 9.5.html │ ├── competitors-16.txt │ ├── 9.2.coffee │ ├── with_events_tests.coffee │ ├── 9.1.coffee │ ├── 9.1.timed.dsd.coffee │ ├── 9.1.timed.coffee │ ├── 9.3.coffee │ └── 9.5.coffee ├── 10 ├── 10.coffee ├── listings │ ├── src │ │ ├── example.coffee │ │ └── sample.coffee │ ├── spec │ │ └── sample.spec.coffee │ ├── sample.spec.coffee │ ├── word_utils.coffee │ ├── 10.7.coffee │ ├── 10.6.coffee │ ├── fact.coffee │ ├── 10.8.coffee │ ├── 10.5.coffee │ ├── 10.9 │ ├── 10.4.coffee │ ├── 10.1.coffee │ ├── 10.2.coffee │ └── 10.3.coffee ├── raw.coffee ├── word_utils_v1.coffee ├── word_utils.coffee ├── dependencies.coffee ├── word_utils.spec.coffee ├── 10.dependencies.coffee ├── word_utils_v1.spec.coffee └── add_class.coffee ├── 11 ├── 11.spec.coffee └── listings │ ├── server.coffee │ ├── 11.1.html │ ├── api-server.coffee │ ├── package.json │ ├── socket-server.coffee │ ├── 11.3.coffee │ ├── 11.6.coffee │ ├── 11.2.coffee │ ├── 11.5.coffee │ ├── 11.4.coffee │ ├── 11.7.coffee │ └── handlers.coffee ├── 12 ├── 12.spec.coffee └── listings │ ├── blog.agtron.co │ ├── VERSION │ ├── .gitignore │ ├── app │ │ ├── models │ │ │ ├── index.coffee │ │ │ ├── model.coffee │ │ │ └── post.coffee │ │ ├── controllers │ │ │ ├── index.coffee │ │ │ ├── static.coffee │ │ │ ├── controller.coffee │ │ │ └── blog.coffee │ │ ├── views │ │ │ ├── index.coffee │ │ │ ├── list.coffee │ │ │ ├── js.coffee │ │ │ ├── view.coffee │ │ │ └── post.coffee │ │ ├── config.coffee │ │ ├── load.coffee │ │ └── server.coffee │ ├── content │ │ ├── post2.txt │ │ └── this-is-my-first-post.txt │ ├── artifact.tar │ ├── artifact.1.tar │ ├── compiled │ │ ├── app │ │ │ ├── models │ │ │ │ ├── index.js │ │ │ │ ├── model.js │ │ │ │ └── post.js │ │ │ ├── controllers │ │ │ │ ├── index.js │ │ │ │ ├── static.js │ │ │ │ └── controller.js │ │ │ ├── views │ │ │ │ ├── index.js │ │ │ │ ├── view.js │ │ │ │ ├── js.js │ │ │ │ ├── post.js │ │ │ │ └── list.js │ │ │ ├── config.js │ │ │ ├── server.js │ │ │ ├── client │ │ │ │ ├── main.js │ │ │ │ └── comments.js │ │ │ └── load.js │ │ └── spec │ │ │ ├── models │ │ │ └── post_spec.js │ │ │ ├── controllers │ │ │ └── blog_spec.js │ │ │ └── client │ │ │ └── comments_spec.js │ ├── package.json │ ├── spec │ │ ├── models │ │ │ └── post_spec.coffee │ │ ├── controllers │ │ │ └── blog_spec.coffee │ │ └── client │ │ │ └── comments_spec.coffee │ ├── lib │ │ └── modules.coffee │ ├── Makefile │ ├── client │ │ ├── main.coffee │ │ └── comments.coffee │ ├── make_client.coffee │ ├── Cakefile │ └── build_utilities.coffee │ ├── 12.1.coffee │ ├── 12.14.coffee │ └── 12.12.coffee ├── 13 ├── 13.spec.coffee └── listings │ ├── 13.6.coffee │ ├── 13.3.coffee │ ├── 13.2.coffee │ ├── 13.1.coffee │ ├── 13.4.coffee │ └── 13.5.coffee ├── .gitignore ├── exercises ├── 5.8.1.coffee ├── attendees ├── 5.8.1 │ ├── news.coffee │ ├── images │ │ ├── camera.png │ │ ├── gallery1.png │ │ ├── gallery2.png │ │ ├── gallery3.png │ │ ├── product.png │ │ └── skateboard.png │ ├── data.coffee │ └── client.coffee ├── 7.5.4.coffee ├── 4.2.3-2.coffee ├── 5.3.3.coffee ├── 2.5.3.coffee ├── 2.8.4.coffee ├── 2.3.3.coffee ├── 4.8.3.coffee ├── 4.7.2.coffee ├── 10.6.4.coffee ├── 3.3.4.coffee ├── 2.4.4.coffee ├── 4.6.3.coffee ├── 2.6.5.coffee ├── 3.1.5.coffee ├── 3.4.4.coffee ├── 4.2.3-1.coffee ├── 4.2.3.coffee ├── 10.4.4.coffee └── 7.2.5.coffee ├── Cakefile ├── README.md └── package.json /10/10.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /4/4.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /5/5.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /1/1.spec.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /11/11.spec.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /12/12.spec.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /13/13.spec.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /6/6.spec.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /8/8.spec.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /9/9.spec.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /6/listings/stock.json: -------------------------------------------------------------------------------- 1 | {"stock":35} -------------------------------------------------------------------------------- /6/listings/users.json: -------------------------------------------------------------------------------- 1 | {"xs":123} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | \#*# 3 | *~ -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/VERSION: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /3/listings/friends.txt: -------------------------------------------------------------------------------- 1 | 2,1,2,2,1,2 2 | -------------------------------------------------------------------------------- /2/listings/house_roast.txt: -------------------------------------------------------------------------------- 1 | Yirgacheffe 2 | -------------------------------------------------------------------------------- /8/.#8.spec.coffee: -------------------------------------------------------------------------------- 1 | plee@wraithe.local.36571 -------------------------------------------------------------------------------- /3/3.coffee: -------------------------------------------------------------------------------- 1 | 2 | # Nothing here. See the spec. -------------------------------------------------------------------------------- /3/listings/file2.txt: -------------------------------------------------------------------------------- 1 | file 2 contents here 2 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/.gitignore: -------------------------------------------------------------------------------- 1 | compiled 2 | -------------------------------------------------------------------------------- /3/listings/file1.txt: -------------------------------------------------------------------------------- 1 | file 1 contents are here 2 | -------------------------------------------------------------------------------- /exercises/5.8.1.coffee: -------------------------------------------------------------------------------- 1 | 2 | # See folder 5.8.1 for full solution -------------------------------------------------------------------------------- /exercises/attendees: -------------------------------------------------------------------------------- 1 | Bob,Sally,Jenny,Kandice,Rodney,Penelope,Andrew -------------------------------------------------------------------------------- /10/listings/src/example.coffee: -------------------------------------------------------------------------------- 1 | 2 | exports.example = -> 3 | '123' 4 | -------------------------------------------------------------------------------- /10/listings/src/sample.coffee: -------------------------------------------------------------------------------- 1 | 2 | exports.example = -> 3 | '123' 4 | -------------------------------------------------------------------------------- /2/2.coffee: -------------------------------------------------------------------------------- 1 | 2 | # Nothing here for this chapter. See 2.spec.coffee. 3 | -------------------------------------------------------------------------------- /3/listings/partygoers.txt: -------------------------------------------------------------------------------- 1 | alfred,thomas,peter,sally,james,alan,jane,bill 2 | -------------------------------------------------------------------------------- /10/listings/spec/sample.spec.coffee: -------------------------------------------------------------------------------- 1 | fact 'example', -> 2 | assert.equal 'x', 'x' -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/models/index.coffee: -------------------------------------------------------------------------------- 1 | 2 | exports.Post = require('./post').Post 3 | -------------------------------------------------------------------------------- /5/listings/news.coffee: -------------------------------------------------------------------------------- 1 | news = 2 | breaking: 'Free shipping today!' 3 | 4 | exports.all = news -------------------------------------------------------------------------------- /6/listings/products.json: -------------------------------------------------------------------------------- 1 | {"x1":{"costPrice":100,"overhead":20},"x2":{"costPrice":40,"overhead":10}} -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/controllers/index.coffee: -------------------------------------------------------------------------------- 1 | 2 | exports.Blog = require('./blog').Blog 3 | -------------------------------------------------------------------------------- /exercises/5.8.1/news.coffee: -------------------------------------------------------------------------------- 1 | news = 2 | breaking: 'Free shipping today!' 3 | 4 | exports.all = news -------------------------------------------------------------------------------- /13/listings/13.6.coffee: -------------------------------------------------------------------------------- 1 | 2 | class Formulaic 3 | 4 | constructor: (@root, @selector, @http, @url) -> 5 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/content/post2.txt: -------------------------------------------------------------------------------- 1 | Pretty cool 2 | 3 | This was rather simpler than I anticipated. 4 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/content/this-is-my-first-post.txt: -------------------------------------------------------------------------------- 1 | The title goes on the first line. 2 | 3 | Hola! 4 | -------------------------------------------------------------------------------- /8/listings/8.1.spec.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | assert = require 'assert' 3 | 4 | src = fs.readFileSync './8.1.litcoffee' -------------------------------------------------------------------------------- /5/listings/images/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/5/listings/images/camera.png -------------------------------------------------------------------------------- /8/listings/elephant.coffee: -------------------------------------------------------------------------------- 1 | 2 | class Elephant 3 | walk: -> 4 | 'Walking now' 5 | forget: -> 6 | 'I never forget' -------------------------------------------------------------------------------- /5/listings/images/gallery1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/5/listings/images/gallery1.png -------------------------------------------------------------------------------- /5/listings/images/gallery2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/5/listings/images/gallery2.png -------------------------------------------------------------------------------- /5/listings/images/gallery3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/5/listings/images/gallery3.png -------------------------------------------------------------------------------- /5/listings/images/product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/5/listings/images/product.png -------------------------------------------------------------------------------- /1/1.coffee: -------------------------------------------------------------------------------- 1 | 2 | square = (x) -> 3 | x * x 4 | 5 | ### 6 | var square = function (x) { 7 | return x * x; 8 | }; 9 | ### 10 | -------------------------------------------------------------------------------- /5/listings/images/skateboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/5/listings/images/skateboard.png -------------------------------------------------------------------------------- /exercises/5.8.1/images/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/exercises/5.8.1/images/camera.png -------------------------------------------------------------------------------- /exercises/5.8.1/images/gallery1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/exercises/5.8.1/images/gallery1.png -------------------------------------------------------------------------------- /exercises/5.8.1/images/gallery2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/exercises/5.8.1/images/gallery2.png -------------------------------------------------------------------------------- /exercises/5.8.1/images/gallery3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/exercises/5.8.1/images/gallery3.png -------------------------------------------------------------------------------- /exercises/5.8.1/images/product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/exercises/5.8.1/images/product.png -------------------------------------------------------------------------------- /exercises/5.8.1/images/skateboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/exercises/5.8.1/images/skateboard.png -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/artifact.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/12/listings/blog.agtron.co/artifact.tar -------------------------------------------------------------------------------- /10/listings/sample.spec.coffee: -------------------------------------------------------------------------------- 1 | {example} = require './src/example' 2 | 3 | fact 'example', -> 4 | assert.equal 'x', 'x' 5 | assert.equal example(), '123' -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/artifact.1.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boundvariable/coffeescript-in-action/HEAD/12/listings/blog.agtron.co/artifact.1.tar -------------------------------------------------------------------------------- /9/listings/competitors-5.txt: -------------------------------------------------------------------------------- 1 | 0212: Turnbill, Geralyn 2 | 0055: Spielvogel, Cierra 3 | 0072: Renyer, Connie 4 | 0011: Engholm, Ciara 5 | 0088: Gitting, Estrella 6 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/models/model.coffee: -------------------------------------------------------------------------------- 1 | 2 | class Model 3 | dirify: (s) -> s.toLowerCase().replace /[^a-zA-Z0-9-]/gi, '-' 4 | 5 | exports.Model = Model 6 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/models/index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | exports.Post = require('./post').Post; 4 | 5 | }).call(this); 6 | -------------------------------------------------------------------------------- /4/listings/4.4.coffee: -------------------------------------------------------------------------------- 1 | 2 | properties = (prop for prop of movie) 3 | 4 | ### JavaScript 5 | var properties = []; 6 | for (var prop in movie) { 7 | properties.push(p); 8 | } 9 | ### -------------------------------------------------------------------------------- /11/listings/server.coffee: -------------------------------------------------------------------------------- 1 | 2 | {makeApiServer} = require './api-server' 3 | {attachSocketServer} = require './socket-server' 4 | 5 | attachSocketServer makeApiServer() 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/controllers/index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | exports.Blog = require('./blog').Blog; 4 | 5 | }).call(this); 6 | -------------------------------------------------------------------------------- /9/listings/phone_numbers.csv: -------------------------------------------------------------------------------- 1 | hannibal,555-5551,friend 2 | darth,555-5552,colleague 3 | hal_9000,disconnected,friend 4 | freddy,555-5554,friend 5 | T-800,555-5555,colleague 6 | dolly,555-3322,associate 7 | -------------------------------------------------------------------------------- /3/listings/3.2.clean.js: -------------------------------------------------------------------------------- 1 | var countWords = function (s, del) { 2 | var words; 3 | if (s) { 4 | words = s.split(del); 5 | return words.length; 6 | } else { 7 | return 0; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /1/1.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | var square; 3 | 4 | square = function(x) { 5 | return x * x; 6 | }; 7 | 8 | /* 9 | var square = function (x) { 10 | return x * x; 11 | }; 12 | */ 13 | 14 | -------------------------------------------------------------------------------- /9/listings/9.5.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | #pong { 7 | width: 100%; 8 | height: 100%; 9 | position: absolute; 10 | top: 0; 11 | left: 0; 12 | } -------------------------------------------------------------------------------- /exercises/7.5.4.coffee: -------------------------------------------------------------------------------- 1 | people = [ 'bill', 'ted' ] 2 | greetings = {} 3 | 4 | for person in people 5 | greetings[person] = -> 6 | "My name is #{person}" 7 | 8 | greetings.bill() 9 | # My name is ted 10 | -------------------------------------------------------------------------------- /10/raw.coffee: -------------------------------------------------------------------------------- 1 | 2 | do add_word_should_add_one_word = -> 3 | input = "ultra mega" 4 | expected_output = "ultra mega ok" 5 | actual_output = add_word input, "ok" 6 | 7 | assert.equal expected_output, actual_output 8 | 9 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/views/index.coffee: -------------------------------------------------------------------------------- 1 | 2 | views = 3 | post: require('./post').Post 4 | list: require('./list').List 5 | js: require('./js').Js 6 | 7 | exports.views = (name, data) -> 8 | new views[name](data) 9 | -------------------------------------------------------------------------------- /5/listings/5.9.json: -------------------------------------------------------------------------------- 1 | { 2 | "Fuji-X100": { 3 | "description": "a camera", 4 | "stock": 5 5 | }, 6 | "Perall Powalta": { 7 | "description": "a skateboard", 8 | "stock":6, 9 | "special": "two for one" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /exercises/4.2.3-2.coffee: -------------------------------------------------------------------------------- 1 | css = (element, styles) -> 2 | element.style ?= {} 3 | for key, value of styles 4 | element.style[key] = value 5 | 6 | class Element 7 | div = new Element 8 | css div, width: 10 9 | 10 | div.style.width 11 | # 10 -------------------------------------------------------------------------------- /3/listings/3.3.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | 3 | file = process.argv[2] 4 | fs.readFile file, 'utf-8', (error, contents) -> #A 5 | if error 6 | console.log error #B 7 | else 8 | console.log contents #C 9 | -------------------------------------------------------------------------------- /exercises/5.3.3.coffee: -------------------------------------------------------------------------------- 1 | class Product 2 | # any implementation of Product 3 | 4 | class Camera extends Product 5 | cameras = [] 6 | @alphabetical = -> 7 | cameras.sort (a, b) -> a.name > b.name 8 | constructor: -> 9 | all.push @ 10 | super 11 | -------------------------------------------------------------------------------- /10/word_utils_v1.coffee: -------------------------------------------------------------------------------- 1 | removeWord = (text, word) -> 2 | replaced = text.replace word, '' #A 3 | replaced.replace(/^\s\s*/, '').replace(/\s\s*$/, '') #B 4 | 5 | addWord = (text, word) -> 6 | "#{text} #{word}" 7 | 8 | exports.addWord = addWord 9 | exports.removeWord = removeWord 10 | -------------------------------------------------------------------------------- /2/listings/barista.html: -------------------------------------------------------------------------------- 1 | 2 | Barista 3 | 4 |
5 | 6 | 7 |
8 | The barista. 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /10/word_utils.coffee: -------------------------------------------------------------------------------- 1 | 2 | addWord = (text, word) -> 3 | if text isnt '' then "#{text} #{word}" 4 | else word 5 | 6 | removeWord = (text, remove) -> 7 | words = text.split /\s/ 8 | (word for word in words when word isnt remove).join ' ' 9 | 10 | exports.addWord = addWord 11 | exports.removeWord = removeWord -------------------------------------------------------------------------------- /10/listings/word_utils.coffee: -------------------------------------------------------------------------------- 1 | 2 | addWord = (text, word) -> 3 | if text isnt '' then "#{text} #{word}" 4 | else word 5 | 6 | removeWord = (text, remove) -> 7 | words = text.split /\s/ 8 | (word for word in words when word isnt remove).join ' ' 9 | 10 | exports.addWord = addWord 11 | exports.removeWord = removeWord -------------------------------------------------------------------------------- /5/listings/5.1.coffee: -------------------------------------------------------------------------------- 1 | class Camera 2 | constructor: (name, info) -> #A 3 | @name = name #A 4 | @info = info #A 5 | render: -> #B 6 | "Camera: #{@name}: #{@info.description} (#{@info.stock} in stock)" #B 7 | purchase: -> #C 8 | -------------------------------------------------------------------------------- /5/listings/5.6.coffee: -------------------------------------------------------------------------------- 1 | class Simple 2 | constructor: -> 3 | @name = "simple object" 4 | 5 | ### Compiled with CoffeeScript 1.1.3 6 | simple = new Simple var Simple = (function() { 7 | function Simple() { 8 | this.name = 'simple'; 9 | } 10 | return Simple; 11 | })(); 12 | 13 | simple = new Simple(); 14 | ### 15 | -------------------------------------------------------------------------------- /10/listings/10.7.coffee: -------------------------------------------------------------------------------- 1 | dependencies = #A 2 | 'Tracking': '../tracking' #A 3 | 'User': '../user' #A 4 | 'fact': '../fact' 5 | 6 | for dependency, path of dependencies #A 7 | exports[dependency] = require(path)[dependency] #A 8 | -------------------------------------------------------------------------------- /2/listings/2.3.spec.coffee: -------------------------------------------------------------------------------- 1 | {divisibleBy} = require './2.3' 2 | {it, stub, describe} = require 'chromic' 3 | 4 | describe "divisibleBy" , -> 5 | it "should return empty array for no range", -> 6 | divisibleBy(null, 5).shouldBe [] 7 | 8 | it "should include boundaries", -> 9 | divisibleBy([1..40], 5).shouldBe [5,10,15,20,25,30,35,40] -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/views/list.coffee: -------------------------------------------------------------------------------- 1 | {View} = require './view' 2 | 3 | class List extends View 4 | constructor: (@posts) -> 5 | render: -> 6 | all = ("
  • #{post.title}
  • " for post in @posts).join '' 7 | @wrap """ 8 | 9 | """ 10 | 11 | exports.List = List 12 | -------------------------------------------------------------------------------- /3/listings/3.1.coffee: -------------------------------------------------------------------------------- 1 | text = process.argv[2] #A 2 | 3 | if text #B 4 | words = text.split /,/ #B 5 | console.log "#{words.length} partygoers" #B 6 | else #C 7 | console.log 'usage: coffee 3.1.coffee [text]' #C 8 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog.agtron.co", 3 | "version": "1.0.0", 4 | "description": "Agtron's blog", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "cake test" 8 | }, 9 | "dependencies": { 10 | "chromic": "0.0.x" 11 | }, 12 | "author": "You", 13 | "license": "BSD-2-Clause" 14 | } 15 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | 2 | {exec} = require 'child_process' 3 | 4 | task 'spec', 'Run chapter snippet specs', -> 5 | [1..13].forEach (chapter) -> 6 | exec "coffee #{chapter}/#{chapter}.spec.coffee", (err, data) -> 7 | if err 8 | console.error "FAIL chapter #{chapter}" 9 | console.error err 10 | else 11 | console.log data 12 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/config.coffee: -------------------------------------------------------------------------------- 1 | # The production config host needs to be replaced 2 | # with a host that you have shell access to. 3 | 4 | config = 5 | development: 6 | host: 'localhost' 7 | port: '8080' 8 | production: 9 | host: 'xx.agtron.co' 10 | port: '8888' 11 | 12 | for key, value of config 13 | exports[key] = value 14 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/views/js.coffee: -------------------------------------------------------------------------------- 1 | 2 | {View} = require './view' 3 | 4 | fs = require 'fs' 5 | 6 | class Js extends View 7 | src = {} #lets just memoise it! 8 | constructor: (@file) -> 9 | unless src[@file] 10 | src[@file] = fs.readFileSync "#{__dirname}/../client/#{@file}", 'utf-8' 11 | render: -> src[@file] 12 | 13 | exports.Js = Js -------------------------------------------------------------------------------- /9/listings/9.5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pong 6 | 7 | 8 | 9 |
    10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /3/listings/3.2.coffee: -------------------------------------------------------------------------------- 1 | # CoffeeScript 2 | 3 | countWords = (s, del) -> 4 | if s 5 | words = s.split del 6 | words.length 7 | else 8 | 0 9 | 10 | ### JavaScript 11 | 12 | var countWords = function (s, del) { 13 | var words; 14 | if (s) { 15 | words = s.split(del); 16 | return words.length; 17 | } else { 18 | return 0; 19 | } 20 | } 21 | ### -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/views/index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var views; 4 | 5 | views = { 6 | post: require('./post').Post, 7 | list: require('./list').List, 8 | js: require('./js').Js 9 | }; 10 | 11 | exports.views = function(name, data) { 12 | return new views[name](data); 13 | }; 14 | 15 | }).call(this); 16 | -------------------------------------------------------------------------------- /11/listings/11.1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Radiator 6 | 7 | 8 | 9 | 10 |
    11 | 12 | 13 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/load.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | {Post} = require './models/post' 3 | 4 | load = (dir) -> 5 | fs.readdir dir, (err, files) -> 6 | for file in files when /.*[.]txt$/.test file 7 | fs.readFile "#{dir}/#{file}", 'utf-8', (err, data) -> 8 | [title, content...] = data.split '\n' 9 | new Post title, content.join '\n' 10 | 11 | exports.load = load 12 | -------------------------------------------------------------------------------- /4/listings/4.6.coffee: -------------------------------------------------------------------------------- 1 | response = { #A 2 | "results": { 3 | "23446": { 4 | "user": "Guard", 5 | "text": "Found them? In Mercia?! The coconut's tropical!" 6 | }, 7 | "23445": { 8 | "user": "Arthur", 9 | "text": "We found them." 10 | }, 11 | "23443": { 12 | "user": "Guard", 13 | "text": "Where'd you get the coconuts?" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/models/model.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var Model; 4 | 5 | Model = (function() { 6 | function Model() {} 7 | 8 | Model.prototype.dirify = function(s) { 9 | return s.toLowerCase().replace(/[^a-zA-Z0-9-]/gi, '-'); 10 | }; 11 | 12 | return Model; 13 | 14 | })(); 15 | 16 | exports.Model = Model; 17 | 18 | }).call(this); 19 | -------------------------------------------------------------------------------- /4/listings/4.7.coffee: -------------------------------------------------------------------------------- 1 | views = 2 | clear: -> 3 | @pages = {} #A 4 | increment: (key) -> 5 | @pages ?= {} 6 | @pages[key] ?= 0 7 | @pages[key] = @pages[key] + 1 8 | total: -> 9 | sum = 0 10 | for own page, count of @pages 11 | sum = sum + count 12 | sum 13 | 14 | exports.businessViews = Object.create views 15 | exports.personalViews = Object.create views -------------------------------------------------------------------------------- /8/listings/8.6.coffee: -------------------------------------------------------------------------------- 1 | 2 | fs = require 'fs' 3 | coffee = require 'coffee-script' 4 | 5 | evalScruffyCoffeeFile = (fileName) -> 6 | fs.readFile fileName, 'utf-8', (err, scruffyCode) -> 7 | coffeeCode = scruffyCode.replace /λ([a-zA-Z]+).([a-zA-Z]+)/g, '($1) -> $2' 8 | coffee.eval coffeeCode 9 | 10 | fileName = process.argv[2] 11 | process.exit 'No file specified' unless fileName 12 | evalScruffyCoffeeFile fileName 13 | -------------------------------------------------------------------------------- /9/listings/competitors-16.txt: -------------------------------------------------------------------------------- 1 | 02120: Turnbill, Geralyn 2 | 04553: Spielvogel, Cierra 3 | 00723: Renyer, Connie 4 | 00112: Engholm, Ciara 5 | 00881: Gitting, Estrella 6 | 00995: Hobgood, Naomi 7 | 20114: Statton, Roslyn 8 | 11222: Saab, Neil 9 | 05452: Vacek, Jamie 10 | 55224: Lubinsky, Lilia 11 | 17715: Cordle, Melisa 12 | 11113: Hiler, Erik 13 | 78807: Dollison, Tameka 14 | 22332: Chaput, Neva 15 | 44551: Riser, Kurt 16 | 11009: Keogh, Avis 17 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/server.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | {load} = require './load' 3 | {Blog} = require './controllers' 4 | 5 | load './content' 6 | 7 | throw new Error 'No evironment defined' unless process.env.NODE_ENV 8 | 9 | config = require('./config')[process.env.NODE_ENV] 10 | 11 | process.exit() unless config? 12 | 13 | server = new http.Server() 14 | server.listen config.port, config.host 15 | 16 | blog = new Blog server 17 | -------------------------------------------------------------------------------- /4/listings/4.5.spec: -------------------------------------------------------------------------------- 1 | {views, viewsIncrement, total} = require './4.5' 2 | 3 | {it, stub, describe} = require 'chromic' 4 | 5 | describe "listing 4.5", -> 6 | for n in [1..31] 7 | viewsIncrement "ABCDEF" 8 | for n in [1..20] 9 | viewsIncrement "A" 10 | for n in [1..10] 11 | viewsIncrement "B" 12 | 13 | it "should increment views object", -> 14 | views["ABCDEF"].shouldBe 31 15 | 16 | it "should have total", -> 17 | total().shouldBe 61 18 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/config.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var config, key, value; 4 | 5 | config = { 6 | development: { 7 | host: 'localhost', 8 | port: '8080' 9 | }, 10 | production: { 11 | host: 'blog.agtron.co', 12 | port: '80' 13 | } 14 | }; 15 | 16 | for (key in config) { 17 | value = config[key]; 18 | exports[key] = value; 19 | } 20 | 21 | }).call(this); 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CoffeeScript in Action 2 | ====================== 3 | 4 | Source code for the Manning book [CoffeeScript in Action](http://manning.com/lee/). 5 | 6 | Chapter listings will be available here after chapters are released to MEAP. 7 | 8 | The code here is all in camelCase whereas existing MEAP chapters will have underscores. 9 | 10 | If you find the camelCase hard to read, something like [Glasses Mode](http://emacswiki.org/emacs/GlassesMode) for emacs might help. -------------------------------------------------------------------------------- /exercises/2.5.3.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Get the collective animal name to be output in a string like the following. 3 | "The collective of cobra is quiver" 4 | ### 5 | animal = "cobra" 6 | collective = switch animal 7 | when "antelope" then "herd" 8 | when "baboon" then "rumpus" 9 | when "badger" then "cete" 10 | when "cobra" then "quiver" 11 | when "crocodile" then "bask" 12 | 13 | "The collective of #{animal} is #{collective}" 14 | # The collectibe of cobra is quiver 15 | -------------------------------------------------------------------------------- /4/listings/4.3.coffee: -------------------------------------------------------------------------------- 1 | 2 | movie = 3 | title: 'From Dusk till Dawn' 4 | released: '1996' 5 | director: 'Robert Rodriguez' 6 | writer: 'Quentin Tarantino' 7 | 8 | for property of movie 9 | console.log property 10 | 11 | ### JavaScript 12 | var movie = { 13 | title: 'From Dusk till Dawn', 14 | released: '1996', 15 | director: 'Robert Rodriguez', 16 | writer: 'Quentin Tarantino' 17 | } 18 | 19 | for (var property in movie) { 20 | console.log(property); 21 | } 22 | ### -------------------------------------------------------------------------------- /11/listings/api-server.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | url = require 'url' 3 | 4 | handlers = require './handlers' 5 | 6 | apiServer = (request, response) -> 7 | {pathname} = url.parse request.url 8 | requested = (pathname.split /\//)[1] 9 | if requested of handlers 10 | (handlers[requested] request, response) 11 | else 12 | response.end handlers.views request, response 13 | 14 | exports.makeApiServer = -> 15 | (http.createServer apiServer).listen 8080, '127.0.0.1' 16 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/controllers/static.coffee: -------------------------------------------------------------------------------- 1 | {Controller} = require '../framework/lib' 2 | 3 | fs = require 'fs' 4 | 5 | class Static extends Controller 6 | 7 | #@route '/(.*\.js)', 'js' 8 | #js: (@request, @response, file) => 9 | # 'qw' 10 | ### 11 | fs.readFile "../client/#{file}", 'utf-8', (err, data) -> 12 | if err then throw err 13 | @reponse.writeHead 200, 'Content-Type': 'text/html' 14 | data 15 | ### 16 | 17 | exports.Static = Static -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/models/post.coffee: -------------------------------------------------------------------------------- 1 | 2 | {Model} = require './model' 3 | 4 | class Post extends Model 5 | posts = [] 6 | constructor: (@title, @body) -> 7 | throw 'requires title' unless @title 8 | @comments = [] 9 | super 10 | @slug = @dirify @title 11 | posts.push @ 12 | 13 | @all: -> posts 14 | 15 | @get: (slug) -> (post for post in posts when post.slug is slug)[0] 16 | 17 | @purge = -> 18 | posts = [] 19 | 20 | exports.Post = Post 21 | -------------------------------------------------------------------------------- /4/listings/4.7.spec: -------------------------------------------------------------------------------- 1 | { personalViews, businessViews, views } = require './4.7' 2 | 3 | {it, stub, describe} = require 'chromic' 4 | 5 | describe "listing 4.7", -> 6 | 7 | it 'should increment business views', -> 8 | for n in [1..10] 9 | businessViews.increment() 10 | 11 | businessViews.total().shouldBe 10 12 | 13 | it 'should not increment personal views when incrementing business views', -> 14 | businessViews.increment() 15 | personalViews.total().shouldBe 0 16 | -------------------------------------------------------------------------------- /5/listings/5.11.coffee: -------------------------------------------------------------------------------- 1 | class Mixin 2 | constructor: (methods) -> 3 | for name, body of methods 4 | @[name] = body 5 | include: (klass) -> #A 6 | for key, value of @ #A 7 | klass::[key] = value #A 8 | 9 | htmlRenderer = new Mixin #B 10 | render: -> "rendered" #B 11 | 12 | class Camera #C 13 | htmlRenderer.include @ #C 14 | 15 | leica = new Camera() 16 | 17 | leica.render() #D 18 | #rendered #D 19 | -------------------------------------------------------------------------------- /10/listings/10.6.coffee: -------------------------------------------------------------------------------- 1 | class User 2 | constructor: (@options, @http) -> #A 3 | visitPage: (url, callback) -> 4 | @options.path = url 5 | @options.method = 'GET' 6 | callback() 7 | clickMouse: (callback) -> #B 8 | request = @http.request @options, (request, response) -> #B 9 | callback() #B 10 | request.end() #B 11 | 12 | exports.User = User 13 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/views/view.coffee: -------------------------------------------------------------------------------- 1 | 2 | class View 3 | render: -> 4 | 'Lost?' 5 | 6 | wrap: (content='') -> 7 | """ 8 | 9 | 10 | 11 | 12 | 13 | Agtron's blog 14 | 15 | #{content} 16 | 17 | 18 | """ 19 | 20 | exports.View = View 21 | -------------------------------------------------------------------------------- /4/listings/4.8.spec: -------------------------------------------------------------------------------- 1 | { Views } = require './4.8' 2 | 3 | {it, stub, describe} = require 'chromic' 4 | 5 | describe "listing 4.8", -> 6 | 7 | it "should increment views", -> 8 | views = new Views 9 | for n in [1..31] 10 | views.increment "ABCDEF" 11 | views.pages["ABCDEF"].shouldBe 31 12 | 13 | it "should have total", -> 14 | views = new Views 15 | for n in [1..20] 16 | views.increment "A" 17 | for n in [1..10] 18 | views.increment "B" 19 | views.total().shouldBe 30 -------------------------------------------------------------------------------- /11/listings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Patrick Lee ", 3 | "name": "CH12", 4 | "description": "Chapter 12", 5 | "version": "0.0.1", 6 | "homepage": "www.boundvariable.com", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/boundvariable/coffeescript-in-action.git" 10 | }, 11 | "dependencies": { 12 | "courier": "0.7.x", 13 | "coffee-script": "1.3.x", 14 | "websocket": "latest" 15 | }, 16 | "engines": { 17 | "node": "*" 18 | } 19 | } -------------------------------------------------------------------------------- /6/listings/6.1.coffee: -------------------------------------------------------------------------------- 1 | profit = (salePrice) -> 2 | overhead = 140 #A 3 | costPrice = 100 #A 4 | numberSold = (salePrice) -> #A 5 | 50 + 20/10 * (200 - salePrice) #A 6 | revenue = (salePrice) -> 7 | (numberSold salePrice) * salePrice 8 | cost = (salePrice) -> 9 | overhead + (numberSold salePrice) * costPrice 10 | 11 | (revenue salePrice) - (cost salePrice) 12 | 13 | 14 | assert = require 'assert' 15 | assert.ok (profit 200) == 4860 16 | -------------------------------------------------------------------------------- /4/listings/4.5.coffee: -------------------------------------------------------------------------------- 1 | views = {} #1 2 | 3 | viewsIncrement = (key) -> #2 4 | views[key] ?= 0 #2 5 | views[key] = views[key] + 1 #2 6 | 7 | total = -> 8 | sum = 0 #3 9 | for own url, count of views #3 10 | sum = sum + count #3 11 | sum 12 | 13 | exports.views = views 14 | exports.viewsIncrement = viewsIncrement 15 | exports.total = total -------------------------------------------------------------------------------- /4/listings/4.1.coffee: -------------------------------------------------------------------------------- 1 | 2 | # yaml literal 3 | futurama = 4 | characters: [ 5 | 'Fry' 6 | 'Leela' 7 | 'Bender' 8 | 'The Professor' 9 | 'Scruffy' 10 | ] 11 | quotes: [ 12 | 'Good news everyone!' 13 | 'Bite my shiny metal' 14 | ] 15 | 16 | # brace_literal 17 | futurama = { 18 | characters: [ 19 | 'Fry', 20 | 'Leela', 21 | 'Bender', 22 | 'The Professor', 23 | 'Scruffy' 24 | ], 25 | quotes: [ 26 | 'Good news everyone!' 27 | 'Bite my shiny metal' 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /5/listings/5.7.coffee: -------------------------------------------------------------------------------- 1 | class SteamShovel 2 | constructor (name) -> 3 | @name = name 4 | speak = -> 5 | "Hurry up!" 6 | 7 | gus = new SteamShovel 8 | gus.speak 9 | # Hurry up! 10 | 11 | ### Compiled with CoffeeScript 1.1.3 12 | var SteamShovel = (function() { 13 | function SteamShovel(name) { 14 | this.name = name; 15 | } 16 | SteamShovel.prototype.speak = 17 | function() { 18 | return "Hurry up!"; 19 | }; 20 | return SteamShovel; 21 | }; 22 | gus = new SteamShovel(); 23 | gus.speak 24 | // Hurry up! 25 | ### 26 | -------------------------------------------------------------------------------- /6/listings/6.7.coffee: -------------------------------------------------------------------------------- 1 | beforeAsync = (decoration) -> 2 | (base) -> 3 | (params..., callback) -> 4 | result = undefined 5 | applyBase = => 6 | result = base.apply @, (params.concat callback) 7 | decoration.apply @, (params.concat applyBase) 8 | result 9 | 10 | afterAsync = (decoration) -> 11 | (base) -> 12 | (params..., callback) -> 13 | decorated = (params...) => 14 | decoration.apply @, (params.concat -> (callback.apply @, params)) 15 | base.apply @, (params.concat decorated) 16 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/spec/models/post_spec.coffee: -------------------------------------------------------------------------------- 1 | {describe, it} = require 'chromic' 2 | 3 | {Post} = require '../../app/models/post' 4 | 5 | describe 'Post', -> 6 | 7 | it 'should return all posts', -> 8 | Post.purge() 9 | new Post 'A post', 'with contents' 10 | new Post 'Another post', 'with contents' 11 | 12 | Post.all().length.should_be 2 13 | 14 | it 'should return a specific post', -> 15 | Post.purge() 16 | post = new Post 'Elephant Stampede', 'Contents' 17 | Post.get(post.slug).should_be post 18 | 19 | -------------------------------------------------------------------------------- /10/listings/fact.coffee: -------------------------------------------------------------------------------- 1 | 2 | success = 0 3 | failure = 0 4 | 5 | exports.fact = (description, fn) -> 6 | try 7 | fn() 8 | success++ 9 | console.log "#{description}: OK" 10 | catch e 11 | console.error "#{description}: " 12 | throw e 13 | 14 | 15 | exports.report = (suite) -> 16 | console.log """ 17 | ------------------------------------------------------------------- 18 | #{suite} finished with #{success} successes and #{failure} failures 19 | ------------------------------------------------------------------- 20 | """ -------------------------------------------------------------------------------- /10/listings/10.8.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | coffee = require 'coffee-script' 3 | 4 | test = (file) -> 5 | fs.readFile file, 'utf-8', (err, data) -> 6 | it = """ 7 | {fact, report} = require './fact' 8 | assert = require 'assert' 9 | #{data} 10 | report '#{file}' 11 | """ 12 | coffee.run it, filename: file #A 13 | 14 | spec = (file) -> 15 | /[^.]*\.spec\.coffee$/.test file 16 | 17 | fs.readdir '.', (err, files) -> #B 18 | for file in files #B 19 | test file if spec file #B 20 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/views/post.coffee: -------------------------------------------------------------------------------- 1 | 2 | {View} = require './view' 3 | 4 | class Post extends View 5 | constructor: (@post) -> 6 | render: -> 7 | @wrap """ 8 |

    #{@post.title}

    9 |
    10 | #{@post.body} 11 |
    12 |
    13 | #{@post.comments} 14 |
    15 |
    16 | Enter your comment here: 17 | 18 | 19 |
    20 | """ 21 | 22 | 23 | exports.Post = Post 24 | -------------------------------------------------------------------------------- /exercises/2.8.4.coffee: -------------------------------------------------------------------------------- 1 | houseRoast = null 2 | 3 | hasMilk = (style) -> 4 | switch style 5 | when "latte", "cappucino" 6 | yes 7 | else 8 | no 9 | 10 | makeCoffee = (requestedStyle) -> 11 | style = requestedStyle || 'Espresso' 12 | if houseRoast? 13 | "#{houseRoast} #{style}" 14 | else 15 | style 16 | 17 | barista = (style) -> 18 | time = (new Date()).getHours() 19 | if hasMilk(style) and time > 12 then "No!" 20 | else 21 | coffee = makeCoffee style 22 | "Enjoy your #{coffee}!" 23 | 24 | order = process.argv[2] 25 | console.log barista order 26 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/lib/modules.coffee: -------------------------------------------------------------------------------- 1 | 2 | do -> #A 3 | modules = {} 4 | cache = {} 5 | @require = (raw_name) -> #B 6 | name = raw_name.replace /[^a-z]/gi, '' 7 | return cache[name].exports if cache[name] 8 | if modules[name] 9 | module = exports: {} 10 | cache[name] = module 11 | modules[name]((name) -> 12 | require name 13 | , module.exports) 14 | module.exports 15 | else throw "No such module #{name}" 16 | 17 | @defmodule = (bundle) -> 18 | for own key of bundle 19 | modules[key] = bundle[key] 20 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/Makefile: -------------------------------------------------------------------------------- 1 | rwildcard = $(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2)) 2 | 3 | all: build 4 | 5 | test: build 6 | coffee -c -o compiled/spec spec 7 | compiled = $(call rwildcard, node compiled/spec/, *_spec.js) 8 | node compiled 9 | 10 | build: clean npm client compile 11 | cp -R content compiled 12 | tar -cvf artifact.tar compiled 13 | 14 | client: compile 15 | mkdir compiled/app/client 16 | coffee ./make_client.coffee 17 | 18 | npm: 19 | npm install 20 | 21 | compile: 22 | coffee -c -o compiled/app app 23 | 24 | clean: 25 | rm -rf compiled 26 | -------------------------------------------------------------------------------- /2/listings/2.2.clean.js: -------------------------------------------------------------------------------- 1 | var hasMilk = function (style) { 2 | switch (style) { 3 | case "latte": 4 | case "cappucino": 5 | return true; 6 | default: 7 | return false; 8 | } 9 | }; 10 | 11 | var makeCoffee = function (style) { 12 | return style || 'Espresso'; 13 | }; 14 | 15 | var barista = function (style) { 16 | var now = new Date(); 17 | var time = now.getHours(); 18 | var coffee; 19 | if (hasMilk(style) && time > 12) { 20 | return "No"; 21 | } else { 22 | coffee = makeCoffee(style); 23 | return "Enjoy your "+coffee+"!"; 24 | } 25 | }; 26 | 27 | barista("latte"); 28 | -------------------------------------------------------------------------------- /exercises/2.3.3.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Suppose you just obtained two items, a torch and an umbrella. 3 | One of the items you purchased, and the other was a gift, 4 | but you’re not sure which is which. Both of these are objects: 5 | torch = {} 6 | umbrella = {} 7 | Either the torch or the umbrella has a price property, but the other does not. 8 | Write an expression for the combined cost of the torch and umbrella. Hint: use the default operator. 9 | ### 10 | 11 | torch = price: 21 # torch has a price 12 | umbrella = {} # umbrella does not 13 | 14 | combinedCost = (torch.price || 0) + (umbrella.price || 0) 15 | # 21 16 | -------------------------------------------------------------------------------- /10/listings/10.5.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | fact = require('./fact').fact 3 | http = require 'http' 4 | 5 | {Tracking} = require './10.4' 6 | {User} = require './10.6' 7 | 8 | SERVER_OPTIONS = 9 | host: 'localhost' 10 | port: 8080 11 | 12 | fact 'the tracking application tracks a user mouse click', -> 13 | tracking = new Tracking SERVER_OPTIONS, http 14 | 15 | tracking.start -> 16 | assert.equal tracking.total(), 0 17 | fred = new User SERVER_OPTIONS, http 18 | fred.visitPage '/some/url', -> 19 | fred.clickMouse -> 20 | assert.equal tracking.total(), 1 21 | tracking.stop() 22 | -------------------------------------------------------------------------------- /exercises/4.8.3.coffee: -------------------------------------------------------------------------------- 1 | class GranTurismo 2 | constructor: (options) -> 3 | @options = options 4 | summary: -> 5 | ("#{key}: #{value}" for key, value of @options).join "\n" 6 | 7 | options = 8 | wheels: 'phat' 9 | dice: 'fluffy' 10 | 11 | scruffysGranTurismo = new GranTurismo options 12 | 13 | scruffysGranTurismo.summary() 14 | # wheels: phat 15 | # dice: fluffy 16 | 17 | # The constructor could use the shorthand for arguments: 18 | class GranTurismo 19 | constructor: (@options) -> 20 | summary: -> 21 | ("#{key}: #{value}" for key, value of @options).join "\n" 22 | 23 | scruffysGranTurismo.summary() -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/client/main.coffee: -------------------------------------------------------------------------------- 1 | 2 | http_request = (url, callback) -> 3 | http = new XMLHttpRequest 4 | http.open 'POST', url, true 5 | http.onreadystatechange = -> 6 | if http.readyState is 4 then callback http.responseText 7 | http.send() 8 | 9 | init = -> 10 | comment_form = document.querySelector('#comment') 11 | if comment_form? 12 | {Comments} = require './comments' 13 | comments_node = document.querySelector '.comments' 14 | comments = new Comments(window.location.href, comments_node, http_request) 15 | comments.bind comment_form, 'submit' 16 | 17 | exports.init = init 18 | 19 | 20 | -------------------------------------------------------------------------------- /4/listings/4.2.spec: -------------------------------------------------------------------------------- 1 | listing42 = require './4.2' 2 | 3 | {it, stub, describe} = require 'chromic' 4 | 5 | 6 | describe "phonebook", -> 7 | phonebook = listing42.phonebook 8 | 9 | it "should add a number", -> 10 | phonebook.numbers = {} 11 | phonebook.add("Xenomorph", "123-1234") 12 | phonebook.numbers.shouldContain "Xenomorph": "123-1234" 13 | 14 | it "should get a number that is present", -> 15 | phonebook.add "Frank Booth", "999-9999" 16 | phonebook.get("Frank Booth").shouldBe "Frank Booth: 999-9999" 17 | 18 | it "should not get a number that is not present", -> 19 | phonebook.get("!").shouldBe "! not found" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coffeescript-in-action", 3 | "version": "1.0.0", 4 | "description": "Source code for the Manning Book CoffeeScript in Action", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "cake spec" 8 | }, 9 | "dependencies": { 10 | "jsdom": "0.8.x", 11 | "coffee-script": "1.6.x", 12 | "chromic": "0.0.x" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/boundvariable/coffeescript-in-action.git" 17 | }, 18 | "author": "Patrick Lee", 19 | "bugs": { 20 | "url": "https://github.com/boundvariable/coffeescript-in-action/issues" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /exercises/4.7.2.coffee: -------------------------------------------------------------------------------- 1 | views = 2 | excluded: [] 3 | pages: {} 4 | clear: -> 5 | @pages = {} 6 | increment: (key) -> 7 | unless key in @excluded 8 | @pages[key] ?= 0 9 | @pages[key] = @pages[key] + 1 10 | ignore: (page) -> 11 | @excluded = @excluded.concat page 12 | total: -> 13 | sum = 0 14 | for own page, count of @pages 15 | sum = sum + count 16 | sum 17 | 18 | views.excluded.push '/x' 19 | 20 | views.increment '/y' 21 | views.increment '/y' 22 | views.increment '/a' 23 | 24 | views.increment '/x' 25 | views.increment '/x' 26 | views.increment '/x' 27 | views.increment '/x' 28 | 29 | views.total() 30 | # 3 -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/spec/controllers/blog_spec.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | 3 | {describe, it} = require 'chromic' 4 | {Blog} = require '../../app/controllers' 5 | {Post} = require '../../app/models' 6 | 7 | describe 'Blog controller', -> 8 | server = {} 9 | blog = {} 10 | response = {} 11 | setup = -> 12 | server = new http.Server 13 | blog = new Blog server 14 | response = (new http.ServerResponse {}).double 15 | 16 | it 'should write headers and end response', -> 17 | setup() 18 | 19 | response.should_receive 'writeHead' 20 | response.should_receive 'end' 21 | 22 | server.emit 'request', url: '/', response 23 | -------------------------------------------------------------------------------- /exercises/10.6.4.coffee: -------------------------------------------------------------------------------- 1 | test = (file) -> 2 | fs.readFile file, 'utf-8', (err, data) -> 3 | it = """ 4 | {fact, report} = require '../fact' 5 | assert = require 'assert' 6 | #{data} 7 | """ 8 | coffee.run it, filename: file 9 | 10 | spec = (file) -> 11 | if /\#/.test file then false 12 | else /\.spec\.coffee$/.test file 13 | 14 | exports.test = test 15 | exports.spec = spec 16 | 17 | # This means that the full file path is passed to both test and spec. 18 | # The tests function should be changed to match: 19 | tests = -> 20 | fs.readdir SPEC_PATH, (err, files) -> 21 | for file in files 22 | test SPEC_PATH + file if spec file 23 | -------------------------------------------------------------------------------- /exercises/3.3.4.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | fs = require 'fs' 3 | 4 | sourceFile = 'attendees' 5 | fileContents = 'File not read yet.' 6 | 7 | readSourceFile = -> 8 | fs.readFile sourceFile, 'utf-8', (error, data) -> 9 | if error 10 | console.log error 11 | else 12 | fileContents = data 13 | 14 | fs.watchFile sourceFile, readSourceFile 15 | 16 | countWords = (text) -> 17 | text.split(/,/gi).length 18 | 19 | readSourceFile sourceFile 20 | 21 | server = http.createServer (request, response) -> 22 | response.end "#{countWords(fileContents)}" 23 | 24 | server.listen 8081, '127.0.0.1' 25 | console.log 'Visit http://127.0.0.1:8081 in your browser' 26 | -------------------------------------------------------------------------------- /7/listings/7.6.coffee: -------------------------------------------------------------------------------- 1 | chain = (receiver) -> 2 | wrapper = Object.create receiver #A 3 | for key, value of wrapper #B 4 | if value?.call #C 5 | do -> #D 6 | proxied = value #E 7 | wrapper[key] = (args...) -> #F 8 | proxied.call receiver, args... #G 9 | wrapper #H 10 | 11 | wrapper #I 12 | 13 | turtle = 14 | forward: (distance) -> 15 | console.log "moving forward by #{distance}" 16 | rotate: (degrees) -> 17 | console.log "rotating #{degrees} degrees" 18 | 19 | chain(turtle) #J 20 | .forward(5) #J 21 | .rotate(90) #J 22 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/server.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var Blog, blog, config, http, load, server; 4 | 5 | http = require('http'); 6 | 7 | load = require('./load').load; 8 | 9 | Blog = require('./controllers').Blog; 10 | 11 | load('./content'); 12 | 13 | if (!process.env.NODE_ENV) { 14 | throw new Error('No evironment defined'); 15 | } 16 | 17 | config = require('./config')[process.env.NODE_ENV]; 18 | 19 | if (config == null) { 20 | process.exit(); 21 | } 22 | 23 | server = new http.Server(); 24 | 25 | server.listen(config.port, config.host); 26 | 27 | blog = new Blog(server); 28 | 29 | }).call(this); 30 | -------------------------------------------------------------------------------- /10/dependencies.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | assert = require 'assert' 3 | 4 | 5 | requestTopicData = (http, topic, callback) -> 6 | options = 7 | host: 'www.boundvariable.com' 8 | port: 80 9 | path: "/data/#{topic}" 10 | 11 | http.get(options, (res) -> 12 | callback res 13 | ).on 'error', (e) -> 14 | console.log e 15 | 16 | expected = 17 | a: 'a' 18 | b: 'b' 19 | 20 | originalRequestTopicData = requestTopicData 21 | 22 | requestTopicData = (topic, callback) -> 23 | fakeHttp = 24 | get: (options, callback) -> 25 | callback expected 26 | on: -> 27 | 28 | originalRequestTopicData fake_http, topic, callback 29 | 30 | exports.requestTopicData = requestTopicData -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/client/comments.coffee: -------------------------------------------------------------------------------- 1 | 2 | class Comments 3 | constructor: (@url, @out, @httpRequest) -> 4 | @httpRequest "#{@url}/comments", @render 5 | post: (comment) -> 6 | @httpRequest "#{@url}/comments?insert=#{comment}", @render 7 | bind: (element, event) -> 8 | comment = element.querySelector 'textarea' 9 | element["on#{event}"] = => 10 | @post comment.value 11 | false 12 | render: (data) => 13 | inLi = (text) -> "
  • #{text}
  • " 14 | if data isnt '' 15 | comments = JSON.parse data 16 | if comments.map? 17 | formatted = comments.map(inLi).join '' 18 | @out.innerHTML = "" 19 | 20 | 21 | exports.Comments = Comments 22 | -------------------------------------------------------------------------------- /exercises/2.4.4.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Suppose you have a variable animal that contains a string with the 3 | singular name for one of the animals: antelope, baboon, badger, cobra, 4 | or crocodile. 5 | 6 | Write some code to get the collective name for the animal. The collective 7 | names for the possible animals in the same order areherd, rumpus, 8 | cete, quiver, and bask. 9 | ### 10 | 11 | animal = 'crocodile' # The variable animal 12 | collective = switch animal # Switch expression to get the collective 13 | when "antelope" then "herd" 14 | when "baboon" then "rumpus" 15 | when "badger" then "cete" 16 | when "cobra" then "quiver" 17 | when "crocodile" then "bask" 18 | # bask 19 | -------------------------------------------------------------------------------- /9/listings/9.2.coffee: -------------------------------------------------------------------------------- 1 | decorateSortUndecorate = (array, sortRule) -> 2 | decorate = (array) -> #A 3 | {original: item, sortOn: sortRule item} for item in array #A 4 | 5 | undecorate = (array) -> #2 6 | item.original for item in array #2 7 | 8 | comparator = (left, right) -> #3 9 | if left.sortOn > right.sortOn #3 10 | 1 #3 11 | else #3 12 | -1 #3 13 | 14 | decorated = decorate array #4 15 | sorted = decorated.sort comparator #5 16 | undecorate sorted #6 17 | 18 | assert = require 'assert' 19 | -------------------------------------------------------------------------------- /11/listings/socket-server.coffee: -------------------------------------------------------------------------------- 1 | {EventEmitter} = require 'events' 2 | WebSocketServer = (require 'websocket').server 3 | 4 | seconds = (n) -> n*1000 5 | 6 | emitRandomNumbers = (emitter, event, interval) -> 7 | setInterval -> 8 | emitter.emit event, Math.floor Math.random()*100 9 | , interval 10 | 11 | source = new EventEmitter 12 | emitRandomNumbers source, 'update', seconds(4) 13 | 14 | attachSocketServer = (server) -> 15 | socketServer = new WebSocketServer httpServer: server 16 | socketServer.on 'request', (request) -> 17 | connection = request.accept 'graph', request.origin 18 | source.on 'update', (data) -> 19 | console.log 'got update' 20 | connection.sendUTF JSON.stringify data 21 | 22 | exports.attachSocketServer = attachSocketServer -------------------------------------------------------------------------------- /5/listings/data.coffee: -------------------------------------------------------------------------------- 1 | oneMonthFromNow = new Date() 2 | oneMonthFromNow.setMonth(oneMonthFromNow.getMonth()+1) 3 | 4 | products = 5 | camera: 6 | 'Fuji-X100': 7 | description: 'a camera' 8 | stock: 5 9 | arrives: oneMonthFromNow 10 | megapixels: 12.3, 11 | gallery: [ 12 | "/images/gallery1.png" 13 | "/images/gallery2.png" 14 | "/images/gallery3.png" 15 | ] 16 | 17 | skateboard: 18 | 'Powell-Peralta': 19 | description: 'a skateboard' 20 | stock: 3 21 | arrives: oneMonthFromNow 22 | length: "23.3 inches", 23 | special: 'Buy one get one free!' 24 | 25 | unkown: 26 | 'Some product': 27 | description: 'Not sure what this is' 28 | stock: 100 29 | 30 | exports.all = products -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/views/view.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var View; 4 | 5 | View = (function() { 6 | function View() {} 7 | 8 | View.prototype.render = function() { 9 | return 'Lost?'; 10 | }; 11 | 12 | View.prototype.wrap = function(content) { 13 | if (content == null) { 14 | content = ''; 15 | } 16 | return "\n\n\n\n\nAgtron's blog\n\n" + content + "\n\n"; 17 | }; 18 | 19 | return View; 20 | 21 | })(); 22 | 23 | exports.View = View; 24 | 25 | }).call(this); 26 | -------------------------------------------------------------------------------- /exercises/5.8.1/data.coffee: -------------------------------------------------------------------------------- 1 | oneMonthFromNow = new Date() 2 | oneMonthFromNow.setMonth(oneMonthFromNow.getMonth()+1) 3 | 4 | products = 5 | camera: 6 | 'Fuji-X100': 7 | description: 'a camera' 8 | stock: 5 9 | arrives: oneMonthFromNow 10 | megapixels: 12.3, 11 | gallery: [ 12 | "/images/gallery1.png" 13 | "/images/gallery2.png" 14 | "/images/gallery3.png" 15 | ] 16 | 17 | skateboard: 18 | 'Powell-Peralta': 19 | description: 'a skateboard' 20 | stock: 3 21 | arrives: oneMonthFromNow 22 | length: "23.3 inches", 23 | special: 'Buy one get one free!' 24 | 25 | unkown: 26 | 'Some product': 27 | description: 'Not sure what this is' 28 | stock: 100 29 | 30 | exports.all = products -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/spec/models/post_spec.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Post, describe, it, _ref; 3 | 4 | _ref = require('chromic'), describe = _ref.describe, it = _ref.it; 5 | 6 | Post = require('../../app/models/post').Post; 7 | 8 | describe('Post', function() { 9 | it('should return all posts', function() { 10 | Post.purge(); 11 | new Post('A post', 'with contents'); 12 | new Post('Another post', 'with contents'); 13 | return Post.all().length.should_be(2); 14 | }); 15 | return it('should return a specific post', function() { 16 | var post; 17 | Post.purge(); 18 | post = new Post('Elephant Stampede', 'Contents'); 19 | return Post.get(post.slug).should_be(post); 20 | }); 21 | }); 22 | 23 | }).call(this); 24 | -------------------------------------------------------------------------------- /4/listings/4.8.coffee: -------------------------------------------------------------------------------- 1 | class Views #1 2 | constructor: -> #2 3 | @pages = {} #2 4 | increment: (key) -> #3 5 | @pages[key] ?= 0 #3 6 | @pages[key] = @pages[key] + 1 #3 7 | total: () -> #4 8 | sum = 0 #4 9 | for own url, count of @pages #4 10 | sum = sum + count #4 11 | sum #4 12 | 13 | businessViews = new Views #5 14 | personalViews = new Views #5 15 | 16 | exports.Views = Views -------------------------------------------------------------------------------- /8/listings/8.4.coffee: -------------------------------------------------------------------------------- 1 | 2 | doctype = (variant) -> 3 | switch variant 4 | when 5 5 | "" 6 | 7 | markup = (wrapper) -> 8 | (attributes..., descendents) -> 9 | attributesMarkup = if attributes.length is 1 10 | ' ' + ("#{name}='#{value}'" for name, value of attributes[0]).join ' ' 11 | else 12 | '' 13 | "<#{wrapper}#{attributesMarkup}>#{descendents() || ''}" 14 | 15 | html = markup 'html' 16 | body = markup 'body' 17 | ul = markup 'ul' 18 | li = markup 'li' 19 | 20 | assert = require 'assert' 21 | 22 | loggedIn = true 23 | 24 | markup = html -> 25 | body -> 26 | ul class: 'info', -> 27 | li -> 'Logged in' if loggedIn 28 | 29 | console.log markup 30 | assert markup is "" -------------------------------------------------------------------------------- /6/listings/6.3.coffee: -------------------------------------------------------------------------------- 1 | numberSold = 0 #A 2 | 3 | calculateNumberSold = (salePrice) -> 4 | numberSold = 50 + 20/10 * (200 - salePrice) 5 | 6 | calculateRevenue = (salePrice, callback) -> 7 | callback numberSold * salePrice 8 | 9 | revenueBetween = (start, finish) -> 10 | totals = [] #B 11 | for price in [start..finish] 12 | calculateNumberSold price 13 | addToTotals = (result) -> 14 | totals.push result 15 | calculateRevenue price, addToTotals 16 | totals 17 | 18 | revenueBetween 140, 145 19 | # [ 23800, 23688, 23572, 23452, 23328, 23200 ] 20 | #A shared state 21 | #B local state 22 | 23 | assert = require 'assert' 24 | assert.deepEqual (revenueBetween 140, 145), [ 23800, 23688, 23572, 23452, 23328, 23200 ] 25 | -------------------------------------------------------------------------------- /8/listings/8.5.coffee: -------------------------------------------------------------------------------- 1 | css = (raw) -> 2 | hyphenate = (property) -> 3 | dashThenUpperAsLower = (match, pre, upper) -> 4 | "#{pre}-#{upper.toLowerCase()}" 5 | property.replace /([a-z])([A-Z])/g, dashThenUpperAsLower 6 | 7 | 8 | output = (for selector, rules of raw 9 | rules = (for ruleName, ruleValue of rules 10 | "#{hyphenate ruleName}: #{ruleValue};" 11 | ).join '\n' 12 | """ 13 | #{selector} { 14 | #{rules} 15 | } 16 | """ 17 | ).join '\n' 18 | 19 | 20 | assert = require 'assert' 21 | 22 | emphasis = -> 23 | fontWeight: 'bold' 24 | 25 | raw = 26 | 'ul': 27 | emphasis() 28 | '.x': 29 | fontSize: '2em' 30 | 31 | console.log css(raw) 32 | 33 | assert css(raw) is ''' 34 | ul { 35 | font-weight: bold; 36 | } 37 | .x { 38 | font-size: 2em; 39 | }''' 40 | -------------------------------------------------------------------------------- /exercises/4.6.3.coffee: -------------------------------------------------------------------------------- 1 | cassette = 2 | title: "Awesome songs. To the max!" 3 | duration: "10:34" 4 | released: "1988" 5 | track1: "Safety Dance - Men Without Hats" 6 | track2: "Funkytown - Lipps, Inc" 7 | track3: "Electric Avenue - Eddy Grant" 8 | track4: "We built this city - Starship" 9 | 10 | # The music device was created from it: 11 | musicDevice = Object.create cassette 12 | 13 | # Creating another one from the first is the same: 14 | secondMusicDevice = Object.create musicDevice 15 | # Changes to either the original cassette or the music device will be visible on the second music device: 16 | cassette.track5 = "Hello - Lionel Richie" 17 | 18 | secondMusicDevice.track5 19 | # "Hello - Lionel Richie" 20 | 21 | musicDevice.track6 = "Mickey - Toni Basil" 22 | 23 | secondMusicDevice.track6 24 | # "Mickey - Toni Basil" 25 | -------------------------------------------------------------------------------- /12/listings/12.1.coffee: -------------------------------------------------------------------------------- 1 | │ #A 2 | ├── app #B 3 | │ ├── controllers #C 4 | │ │ ├── blog.coffee 5 | │ │ ├── controller.coffee 6 | │ │ ├── static.coffee 7 | │ ├── load.coffee 8 | │ ├── models #C 9 | │ │ ├── model.coffee 10 | │ │ ├── post.coffee 11 | │ ├── server.coffee 12 | │ └── views #C 13 | │ ├── list.coffee 14 | │ ├── post.coffee 15 | │ ├── view.coffee 16 | ├── content #D 17 | │ ├── my-trip-to-the-circus.txt 18 | │ └── my-trip-to-the-zoo.txt 19 | 20 | #A The root directory for the project 21 | #B The application directory 22 | #C This application has models, views and controllers 23 | #D The content directory, containing the your blog posts 24 | -------------------------------------------------------------------------------- /3/listings/3.4.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' #1 2 | http = require 'http' #1 3 | 4 | sourceFile = 'myfile' #2 5 | fileContents = 'File not read yet.' #2 6 | 7 | readSourceFile = -> #3 8 | fs.readFile sourceFile, 'utf-8', (error, data) -> #3 9 | if error #3 10 | console.log error #3 11 | else #3 12 | bulletin = data #3 13 | 14 | fs.watchFile sourceFile, readSourceFile #4 15 | 16 | server = http.createServer (request, response) -> #5 17 | response.end fileContents #5 18 | 19 | server.listen 8080, '127.0.0.1' #5 20 | -------------------------------------------------------------------------------- /6/listings/db.spec.coffee: -------------------------------------------------------------------------------- 1 | {describe, it} = require 'chromic' 2 | fs = require 'fs' 3 | 4 | {Db} = require './db' 5 | 6 | describe 'db', -> 7 | fs.writeFile = (_, __, cb) -> cb() 8 | fs.readFile = (_, __, cb) -> 9 | raw = ''' 10 | { 11 | "bob": { 12 | "x": 1, 13 | "y": 2 14 | } 15 | } 16 | ''' 17 | cb null, raw 18 | 19 | users = new Db './users.json' 20 | 21 | it 'gets', -> 22 | users.get 'bob', (error, data) -> 23 | data.shouldBe {"x": 1, "y": 2} 24 | 25 | it 'sets', -> 26 | users.set 'bob', 11, -> 27 | users.get 'bob', (error, data) -> 28 | data.shouldBe 11 29 | 30 | it 'suspends on write', -> 31 | fs.writeFile = (_, __, cb) -> 32 | setTimeout cb, 1000 33 | users.sync() 34 | users.get 'anything', (error, data) -> 35 | error.message.shouldBe 'Sync' -------------------------------------------------------------------------------- /8/the_wild_swans_at_coole.litcoffee: -------------------------------------------------------------------------------- 1 | W B Yeats 2 | The Wild Swans at Coole 3 | 4 | The trees are in their autumn beauty, 5 | 6 | trees = [{}, {}] 7 | for tree in trees 8 | tree.inAutumnBeauty = yes 9 | 10 | The woodland paths are dry, 11 | 12 | paths = [{}, {}, {}] 13 | for path in paths 14 | path.dry = yes 15 | 16 | Under the October twilight the water 17 | Mirrors a still sky; 18 | 19 | octoberTwilight = {} 20 | stillSky = {} 21 | water = 22 | placeUnder: -> 23 | 24 | water.placeUnder octoberTwilight 25 | water.mirrors = stillSky 26 | 27 | Upon the brimming water among the stones 28 | Are nine-and-fifty swans. 29 | 30 | water.brimming = true 31 | water.stones = [{}, {}, {}, {}] 32 | 33 | class Swan 34 | x: 3 35 | 36 | for n in [1..59] 37 | water.stones.push new Swan 38 | -------------------------------------------------------------------------------- /10/word_utils.spec.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | 3 | {addWord, removeWord} = require './word_utils' 4 | 5 | fact = (description, fn) -> 6 | try 7 | fn() 8 | console.log "#{description}: OK" 9 | catch e 10 | console.log "#{description}: \n#{e.stack}" 11 | 12 | fact "addWord adds a word", -> 13 | input = "ultra mega" 14 | expectedOutput = "ultra mega ok" 15 | actualOutput = addWord input, "ok" 16 | 17 | assert.equal expectedOutput, actualOutput 18 | 19 | fact "remove_word removes a word and surrounding whitespace", -> 20 | tests = [ 21 | initial: "ultra mega" 22 | replace: "mega" 23 | expected: "ultra" 24 | , 25 | initial: "ultra mega" 26 | replace: "meg" 27 | expected: "ultra mega" 28 | ] 29 | 30 | for test in tests 31 | assert.equal removeWord(test.initial, test.replace), test.expected 32 | 33 | -------------------------------------------------------------------------------- /exercises/2.6.5.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Suppose you have a string containing animal names. 3 | animals = 'baboons badgers antelopes cobras crocodiles' 4 | Write a program to output the following: 5 | ['A rumpus of baboons', 6 | 'A cete of badgers', 7 | 'A herd of antelopes', 8 | 'A quiver of cobras', 9 | 'A bask of crocodiles'] 10 | ### 11 | 12 | animals = 'baboons badgers antelopes cobras crocodiles' 13 | result = for animal in animals.split " " 14 | collective = switch animal 15 | when "antelopes" then "herd" 16 | when "baboons" then "rumpus" 17 | when "badgers" then "cete" 18 | when "cobras" then "quiver" 19 | when "crocodiles" then "bask" 20 | "A #{collective} of #{animal}" 21 | # ['A rumpus of baboons', 22 | # 'A cete of badgers', 23 | # 'A herd of antelopes', 24 | # 'A quiver of cobras', 25 | # 'A bask of crocodiles'] 26 | -------------------------------------------------------------------------------- /11/listings/11.3.coffee: -------------------------------------------------------------------------------- 1 | {EventEmitter} = require 'events' 2 | WebSocketServer = (require 'websocket').server #A 3 | 4 | seconds = (n) -> n*1000 5 | 6 | emitRandomNumbers = (emitter, event, interval) -> 7 | setInterval -> 8 | emitter.emit event, Math.floor Math.random()*100 9 | , interval 10 | 11 | source = new EventEmitter 12 | emitRandomNumbers source, 'update', seconds(4) 13 | 14 | attachSocketServer = (server) -> #B 15 | socketServer = new WebSocketServer httpServer: server #B 16 | socketServer.on 'request', (request) -> #B 17 | connection = request.accept 'graph', request.origin #B 18 | source.on 'update', (data) -> #B 19 | connection.sendUTF JSON.stringify data #B 20 | 21 | exports.attachSocketServer = attachSocketServer 22 | -------------------------------------------------------------------------------- /2/listings/2.3.coffee: -------------------------------------------------------------------------------- 1 | houseRoast = null 2 | 3 | hasMilk = (style) -> 4 | switch style.toLowerCase() 5 | when "latte", "cappucino" 6 | yes 7 | else 8 | no 9 | 10 | makeCoffee = (requestedStyle) -> 11 | style = requestedStyle || 'Espresso' 12 | console.log houseRoast 13 | if houseRoast? 14 | "#{houseRoast} #{style}" 15 | else 16 | style 17 | 18 | barista = (style) -> 19 | time = (new Date()).getHours() 20 | if hasMilk(style) and time > 12 then "No!" 21 | else 22 | coffee = makeCoffee style 23 | "Enjoy your #{coffee}!" 24 | 25 | 26 | ### 27 | Browser Scripting 28 | ### 29 | 30 | order = document.querySelector '#order' 31 | request = document.querySelector '#request' 32 | response = document.querySelector '#response' 33 | 34 | order.onsubmit = -> 35 | alert barista(request.value) 36 | response.innerHTML = barista(request.value) 37 | false 38 | -------------------------------------------------------------------------------- /2/listings/2.4.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | 3 | houseRoast = null 4 | 5 | hasMilk = (style) -> 6 | switch style.toLowerCase() 7 | when "latte", "cappucino" 8 | yes 9 | else 10 | no 11 | 12 | makeCoffee = (requestedStyle) -> 13 | style = requestedStyle || 'Espresso' 14 | if houseRoast? 15 | "#{houseRoast} #{style}" 16 | else 17 | style 18 | 19 | barista = (style) -> 20 | time = (new Date()).getHours() 21 | if hasMilk(style) and time > 12 then "No!" 22 | else 23 | coffee = makeCoffee style 24 | "Enjoy your #{coffee}!" 25 | 26 | main = -> 27 | requestedCoffee = process.argv[2] 28 | if !requestedCoffee? 29 | console.log 'You need to specify an order' 30 | else 31 | fs.readFile 'house_roast.txt', 'utf-8', (err, data) -> 32 | if data then houseRoast = data.replace /\n/, '' 33 | console.log barista(requestedCoffee) 34 | 35 | main() -------------------------------------------------------------------------------- /10/10.dependencies.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | 3 | fact = (description, fn) -> 4 | try 5 | fn() 6 | console.log "#{description}: OK" 7 | catch e 8 | console.log "#{description}: \n#{e.stack}" 9 | 10 | 11 | http_double = 12 | get: (options, callback) -> 13 | callback "x" 14 | @ 15 | on: (event, callback) -> 16 | 17 | fetch = (topic, callback, http) -> 18 | options = 19 | host: 'www.agtronscameras.com' 20 | port: 80 21 | path: "/data/#{topic}" 22 | 23 | http.get(options, (result) -> 24 | callback result 25 | ).on 'error', (e) -> 26 | console.log e 27 | 28 | parse = (data) -> 29 | "parsed x" 30 | 31 | fact "data is parsed correctly", -> 32 | parse (parsed) -> 33 | assert.equal parsed, "parsed x" 34 | 35 | fact "data is fetched correctly". -> 36 | fetch "a-topic", (result) -> 37 | assert.equal result, "x" 38 | , http_double 39 | 40 | -------------------------------------------------------------------------------- /2/listings/barista.coffee: -------------------------------------------------------------------------------- 1 | houseRoast = 'Yirgacheffe' 2 | 3 | hasMilk = (style) -> 4 | switch style.toLowerCase() 5 | when 'latte', 'cappucino', 'mocha' 6 | yes 7 | else 8 | no 9 | 10 | makeCoffee = (requestedStyle) -> 11 | style = requestedStyle || 'Espresso' 12 | console.log houseRoast 13 | if houseRoast? 14 | "#{houseRoast} #{style}" 15 | else 16 | style 17 | 18 | barista = (style) -> 19 | time = (new Date()).getHours() 20 | if hasMilk(style) and time > 12 then "No!" 21 | else 22 | coffee = makeCoffee style 23 | "Enjoy your #{coffee}!" 24 | 25 | 26 | ### 27 | Browser Scripting 28 | ### 29 | 30 | order = document.querySelector '#order' 31 | request = document.querySelector '#request' 32 | response = document.querySelector '#response' 33 | 34 | order.onsubmit = -> 35 | alert barista(request.value) 36 | response.innerHTML = barista(request.value) 37 | false 38 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/make_client.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | compiler = require 'coffee-script' 3 | 4 | compileClient = -> 5 | fs.mkdirSync "compiled/app/client" unless fs.existsSync "compiled/app/client" 6 | modules = fs.readFileSync "lib/modules.coffee", "utf-8" 7 | modules = compiler.compile modules, bare: true 8 | files = fs.readdirSync 'client' 9 | source = (for file in files when /\.coffee$/.test file 10 | module = file.replace /\.coffee/, '' 11 | fileSource = fs.readFileSync "client/#{file}", "utf-8" 12 | fs.writeFileSync "compiled/app/client/#{module}.js", compiler.compile fileSource 13 | """ 14 | defmodule({#{module}: function (require, exports) { 15 | #{compiler.compile(fileSource, bare: true)} 16 | }}); 17 | """ 18 | ).join '\n\n' 19 | 20 | out = modules + '\n\n' + source 21 | fs.writeFileSync 'compiled/app/client/application.js', out 22 | 23 | compileClient() -------------------------------------------------------------------------------- /10/listings/10.9: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee #A 2 | 3 | coffee = require 'coffee-script' 4 | fs = require 'fs' 5 | 6 | SPEC_PATH = './spec/' #B 7 | SRC_PATH = './src/' #B 8 | 9 | test = (file) -> 10 | fs.readFile SPEC_PATH + file, 'utf-8', (err, data) -> 11 | it = """ 12 | {fact, report} = require '../fact' 13 | assert = require 'assert' 14 | #{data} 15 | report '#{file}' 16 | """ 17 | coffee.run it, filename: SPEC_PATH + file #C 18 | 19 | spec = (file) -> 20 | if /#/.test file then false 21 | else /\.spec\.coffee$/.test file 22 | 23 | tests = -> 24 | fs.readdir SPEC_PATH, (err, files) -> 25 | for file in files 26 | test file if spec file 27 | 28 | fs.watch SPEC_PATH, (event, filename) -> #D 29 | tests() #D 30 | 31 | fs.watch SRC_PATH, (event, filename) -> #D 32 | tests() #D 33 | -------------------------------------------------------------------------------- /8/listings/8.2.coffee: -------------------------------------------------------------------------------- 1 | 2 | simplesmtp = require 'simplesmtp' 3 | 4 | class Email 5 | SMTP_PORT = 25 6 | SMTP_SERVER = 'coffeescriptinaction.com' 7 | constructor: ({@to, @from, @subject, @body}) -> 8 | send: -> 9 | client = simplesmtp.connect SMTP_PORT, SMTP_SERVER 10 | client.once 'idle', -> 11 | client.useEnvelope 12 | from: @from 13 | to: @to 14 | client.on 'message', -> 15 | client.write """ 16 | From: #{@from} 17 | To: #{@to} 18 | Subject: #{@subject} 19 | 20 | #{@body} 21 | 22 | 23 | """ 24 | client.end() 25 | 26 | 27 | scruffysEmail = new Email 28 | to: 'agtron@coffeescriptinaction.com' 29 | from: 'scruffy@coffeescriptinaction.com' 30 | subject: 'Hi Agtron!' 31 | body: ''' 32 | 33 | This is a test email. 34 | 35 | ''' 36 | 37 | scruffysEmail.send() 38 | 39 | assert = require 'assert' 40 | assert scruffysEmail.to is 'agtron@coffeescriptinaction.com' -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/client/main.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var http_request, init; 3 | 4 | http_request = function(url, callback) { 5 | var http; 6 | http = new XMLHttpRequest; 7 | http.open('POST', url, true); 8 | http.onreadystatechange = function() { 9 | if (http.readyState === 4) { 10 | return callback(http.responseText); 11 | } 12 | }; 13 | return http.send(); 14 | }; 15 | 16 | init = function() { 17 | var Comments, comment_form, comments, comments_node; 18 | comment_form = document.querySelector('#comment'); 19 | if (comment_form != null) { 20 | Comments = require('./comments').Comments; 21 | comments_node = document.querySelector('.comments'); 22 | comments = new Comments(window.location.href, comments_node, http_request); 23 | return comments.bind(comment_form, 'submit'); 24 | } 25 | }; 26 | 27 | exports.init = init; 28 | 29 | }).call(this); 30 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/load.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var Post, fs, load, 4 | __slice = [].slice; 5 | 6 | fs = require('fs'); 7 | 8 | Post = require('./models/post').Post; 9 | 10 | load = function(dir) { 11 | return fs.readdir(dir, function(err, files) { 12 | var file, _i, _len, _results; 13 | _results = []; 14 | for (_i = 0, _len = files.length; _i < _len; _i++) { 15 | file = files[_i]; 16 | if (/.*[.]txt$/.test(file)) { 17 | _results.push(fs.readFile("" + dir + "/" + file, 'utf-8', function(err, data) { 18 | var content, title, _ref; 19 | _ref = data.split('\n'), title = _ref[0], content = 2 <= _ref.length ? __slice.call(_ref, 1) : []; 20 | return new Post(title, content.join('\n')); 21 | })); 22 | } 23 | } 24 | return _results; 25 | }); 26 | }; 27 | 28 | exports.load = load; 29 | 30 | }).call(this); 31 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/spec/controllers/blog_spec.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Blog, Post, describe, http, it, _ref; 3 | 4 | http = require('http'); 5 | 6 | _ref = require('chromic'), describe = _ref.describe, it = _ref.it; 7 | 8 | Blog = require('../../app/controllers').Blog; 9 | 10 | Post = require('../../app/models').Post; 11 | 12 | describe('Blog controller', function() { 13 | var blog, response, server, setup; 14 | server = {}; 15 | blog = {}; 16 | response = {}; 17 | setup = function() { 18 | server = new http.Server; 19 | blog = new Blog(server); 20 | return response = (new http.ServerResponse({})).double; 21 | }; 22 | return it('should write headers and end response', function() { 23 | setup(); 24 | response.should_receive('writeHead'); 25 | response.should_receive('end'); 26 | return server.emit('request', { 27 | url: '/' 28 | }, response); 29 | }); 30 | }); 31 | 32 | }).call(this); 33 | -------------------------------------------------------------------------------- /13/listings/13.3.coffee: -------------------------------------------------------------------------------- 1 | class Formulaic 2 | "use strict" 3 | constructor: (@root, @selector, @http) -> 4 | @fields = {} 5 | @subscribers = [] 6 | @extractFields() 7 | 8 | bind: (field) -> 9 | Object.defineProperty @fields, field.name, 10 | set: (newValue) => 11 | field.value = newValue 12 | @sync() 13 | get: -> 14 | field.value 15 | enumerable: true 16 | 17 | updateField = => 18 | @fields[field.name] = field.value 19 | 20 | updateField() 21 | field.addEventListener 'input', updateField 22 | 23 | documentFields: -> 24 | element = @root.querySelector @selector 25 | element.getElementsByTagName 'input' 26 | 27 | extractFields: -> 28 | @bind field for field in @documentFields() 29 | 30 | sync: -> 31 | throw new Error 'No transport' unless @http? 32 | if @url? 33 | @http.post @url, JSON.stringify(@fields), (response) => 34 | @fields = JSON.parse response 35 | 36 | exports.Formulaic = Formulaic 37 | -------------------------------------------------------------------------------- /4/listings/4.9.spec: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | 3 | { server } = require './4.9' 4 | 5 | {it, stub, describe} = require 'chromic' 6 | 7 | businessHit = 8 | host: 'localhost', 9 | port: 8080, 10 | path: '/business/a', 11 | method: 'GET' 12 | 13 | personalHit = 14 | host: 'localhost', 15 | port: 8080, 16 | path: '/personal/a', 17 | method: 'GET' 18 | 19 | for hit in Array 7 20 | request = http.get businessHit 21 | request.on 'error', (e) -> console.log e 22 | 23 | http.get personalHit 24 | http.get personalHit 25 | 26 | indexHit = 27 | host: 'localhost', 28 | port: 8080, 29 | path: '/', 30 | method: 'GET' 31 | 32 | request = http.get indexHit, (response) -> 33 | result = '' 34 | response.on 'data', (data) -> 35 | result += data 36 | 37 | response.on 'end', -> 38 | describe 'listing 4.9', -> 39 | it 'should serve number of views', -> 40 | /Personal: 2/.test(result).shouldBe true 41 | /Business: 7/.test(result).shouldBe true 42 | server.close() 43 | -------------------------------------------------------------------------------- /13/listings/13.2.coffee: -------------------------------------------------------------------------------- 1 | 2 | class Formulaic 3 | constructor: (@root, @selector, @http) -> 4 | @subscribers = [] 5 | @fields = @extractFields() 6 | @startPolling() 7 | 8 | extractFields: -> 9 | element = @root.querySelector @selector 10 | fields = element.getElementsByTagName 'input' 11 | extracted = {} 12 | for field in fields 13 | extracted[field.name] = field.value 14 | extracted 15 | 16 | update: => 17 | for own key, value of @extractFields() 18 | if @fields[key] isnt value 19 | @fields[key] = value 20 | @notify() 21 | 22 | startPolling: -> 23 | setInterval @update, 100 24 | 25 | subscribe: (subscriber) -> 26 | @subscribers.push subscriber 27 | 28 | notify: -> 29 | subscriber() for subscriber in @subscribers 30 | 31 | sync: -> 32 | throw new Error 'No transport' unless @http? and @url? 33 | @http.post @url, JSON.stringify(@extractFields()), (response) -> 34 | @fields = JSON.parse response 35 | 36 | exports.Formulaic = Formulaic -------------------------------------------------------------------------------- /8/listings/8.3.coffee: -------------------------------------------------------------------------------- 1 | 2 | simplesmtp = require 'simplesmtp' 3 | 4 | class Email 5 | SMTP_PORT = 25 6 | SMTP_SERVER = 'coffeescriptinaction.com' 7 | constructor: (options) -> 8 | ['from', 'to', 'subject', 'body'].forEach (key) => 9 | @["_{key}"] = options?[key] 10 | @[key] = (newValue) -> 11 | @["_#{key}"] = newValue 12 | @ 13 | 14 | send: -> 15 | client = simplesmtp.connect SMTP_PORT, SMTP_SERVER 16 | client.once 'idle', -> 17 | client.useEnvelope 18 | from: @_from 19 | to: @_to 20 | client.on 'message', -> 21 | client.write """ 22 | From: "#{@_from}" 23 | To: #{@_to} 24 | Subject: #{@_subject} 25 | 26 | #{@_body} 27 | 28 | 29 | """ 30 | client.end() 31 | @ 32 | 33 | scruffysEmail = new Email() 34 | 35 | scruffysEmail 36 | .to('agtron@coffeescriptinaction.com') 37 | .from('scruffy@coffeescriptinaction.com') 38 | .subject('Hi Agtron!') 39 | .body ''' 40 | 41 | This is a test email. 42 | 43 | ''' 44 | 45 | scruffysEmail.send() 46 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/views/js.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var Js, View, fs, 4 | __hasProp = {}.hasOwnProperty, 5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 6 | 7 | View = require('./view').View; 8 | 9 | fs = require('fs'); 10 | 11 | Js = (function(_super) { 12 | var src; 13 | 14 | __extends(Js, _super); 15 | 16 | src = {}; 17 | 18 | function Js(file) { 19 | this.file = file; 20 | if (!src[this.file]) { 21 | src[this.file] = fs.readFileSync("" + __dirname + "/../client/" + this.file, 'utf-8'); 22 | } 23 | } 24 | 25 | Js.prototype.render = function() { 26 | return src[this.file]; 27 | }; 28 | 29 | return Js; 30 | 31 | })(View); 32 | 33 | exports.Js = Js; 34 | 35 | }).call(this); 36 | -------------------------------------------------------------------------------- /13/listings/13.1.coffee: -------------------------------------------------------------------------------- 1 | class Formulaic 2 | constructor: (@root, @selector, @http) -> 3 | @subscribers = [] 4 | @fields = @extractFields() 5 | @startPolling() 6 | 7 | extractFields: -> 8 | element = @root.querySelector @selector 9 | fields = element.getElementsByTagName 'input' 10 | extracted = {} 11 | for field in fields 12 | extracted[field.name] = field.value 13 | extracted 14 | 15 | startPolling: -> 16 | diff = => 17 | for own key, value of @extractFields() 18 | if @fields[key] isnt value 19 | @fields[key] = value 20 | @notify() 21 | setInterval diff, 100 22 | 23 | subscribe: (subscriber) -> 24 | @subscribers.push subscriber 25 | 26 | notify: -> 27 | subscriber() for subscriber in @subscribers 28 | 29 | 30 | 31 | # test 32 | element = 33 | getElementsByTagName: -> 34 | [{name: 'a', value: 1}, {name: 'b', value: 2}] 35 | 36 | root = 37 | querySelector: -> 38 | element 39 | 40 | http = {} 41 | 42 | f = new Formulaic root, '#x', http 43 | 44 | console.log JSON.stringify(f) -------------------------------------------------------------------------------- /2/listings/2.1.spec.coffee: -------------------------------------------------------------------------------- 1 | 2 | listing21 = require './2.1' 3 | {it, stub, describe} = require 'chromic' 4 | 5 | describe "listing 2.1", -> 6 | describe "hasMilk", -> 7 | it "should return true for a latte or cappucino", -> 8 | listing21.hasMilk("latte").shouldBe true 9 | 10 | describe "make_coffee", -> 11 | it "should return a given style", -> 12 | listing21.makeCoffee("Zorb").shouldBe "Zorb" 13 | 14 | it "should return the default style", -> 15 | listing21.makeCoffee().shouldBe "Espresso" 16 | 17 | it "should use the house roast", -> 18 | listing21.specifyHouseRoast "Yirgacheffe" 19 | listing21.makeCoffee().shouldBe "Yirgacheffe Espresso" 20 | 21 | describe "barista", -> 22 | it "should deny a milky coffee after midday", -> 23 | Date.stub("getHours") -> 15 24 | listing21.barista("latte").shouldBe "No!" 25 | 26 | it "should allow a milky coffee before midday", -> 27 | listing21.specifyHouseRoast "Yirgacheffe" 28 | Date.stub("getHours") -> 9 29 | listing21.barista("latte").shouldBe "Enjoy your Yirgacheffe latte!" 30 | 31 | -------------------------------------------------------------------------------- /7/listings/7.1.coffee: -------------------------------------------------------------------------------- 1 | find = (name) -> #A 2 | document.querySelector ".#{name}" #A 3 | 4 | color = (element, color) -> #B 5 | element.style.background = color #B 6 | 7 | insert = (teams...) -> #C 8 | root = document.querySelector '.teams' #C 9 | for team in teams #C 10 | element = document.createElement 'li' #C 11 | element.innerHTML = team #C 12 | element.className = team #C 13 | root.appendChild element #C 14 | 15 | highlight = (first, rest...) -> #D 16 | color find(first), 'gold' #D 17 | for name in rest #D 18 | color find(name), 'blue' #D 19 | 20 | initialize = (ranked) -> #E 21 | insert ranked... #E 22 | first = ranked.slice 0, 1 #E 23 | rest = ranked.slice 1 #E 24 | highlight first, rest... 25 | 26 | window.onload = -> #F 27 | initialize [ #F 28 | 'wolverines' #F 29 | 'wildcats' #F 30 | 'mongooses' #F 31 | ] #F 32 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/controllers/static.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var Controller, Static, fs, _ref, 4 | __hasProp = {}.hasOwnProperty, 5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 6 | 7 | Controller = require('../framework/lib').Controller; 8 | 9 | fs = require('fs'); 10 | 11 | Static = (function(_super) { 12 | __extends(Static, _super); 13 | 14 | /* 15 | fs.readFile "../client/#{file}", 'utf-8', (err, data) -> 16 | if err then throw err 17 | @reponse.writeHead 200, 'Content-Type': 'text/html' 18 | data 19 | */ 20 | 21 | 22 | function Static() { 23 | _ref = Static.__super__.constructor.apply(this, arguments); 24 | return _ref; 25 | } 26 | 27 | return Static; 28 | 29 | })(Controller); 30 | 31 | exports.Static = Static; 32 | 33 | }).call(this); 34 | -------------------------------------------------------------------------------- /10/listings/10.4.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | 3 | class Tracking 4 | constructor: (@options, @http) -> #A 5 | @pages = [] #A 6 | start: (callback) -> #B 7 | @server = @http.createServer @controller #B 8 | @server.listen @options.port, callback #B 9 | stop: -> #C 10 | @server.close() #C 11 | controller: (request, response) => #D 12 | @increment request.url #D 13 | response.writeHead 200, 'Content-Type': 'text/html' #D 14 | response.write '' #D 15 | response.end() #D 16 | increment: (key) -> #E 17 | @pages[key] ?= 0 #E 18 | @pages[key] = @pages[key] + 1 #E 19 | total: -> #F 20 | sum = 0 #F 21 | for page, count of @pages #F 22 | sum = sum + count #F 23 | sum #F 24 | 25 | exports.Tracking = Tracking #G 26 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/views/post.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var Post, View, 4 | __hasProp = {}.hasOwnProperty, 5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 6 | 7 | View = require('./view').View; 8 | 9 | Post = (function(_super) { 10 | __extends(Post, _super); 11 | 12 | function Post(post) { 13 | this.post = post; 14 | } 15 | 16 | Post.prototype.render = function() { 17 | return this.wrap("

    " + this.post.title + "

    \n
    \n" + this.post.body + "\n
    \n
    \n" + this.post.comments + "\n
    \n
    \nEnter your comment here:\n\n\n
    "); 18 | }; 19 | 20 | return Post; 21 | 22 | })(View); 23 | 24 | exports.Post = Post; 25 | 26 | }).call(this); 27 | -------------------------------------------------------------------------------- /exercises/3.1.5.coffee: -------------------------------------------------------------------------------- 1 | # Write a version of countWords that ignores words shorter than three letters. 2 | 3 | countWords = (text) -> 4 | words = text.split /[\s,]+/ # match on space or comma 5 | (word for word in words when word.length > 3).length # evaluate to the length of the comprehension result 6 | 7 | console.log countWords 'There are nine words longer than three letters in this sentence.' 8 | # 9 9 | 10 | ### 11 | Write a function that creates a new space-delimited string of words containing only 12 | every second word in the original space-delimited string. For example, 13 | ### 14 | everyOtherWord = (text) -> 15 | words = text.split /[\s,]+/ 16 | takeOther = for word, index in words 17 | if index % 2 then '' 18 | else word 19 | takeOther.join(' ').replace /\s\s/gi, ' ' 20 | 21 | misquote = """we like only like think like when like we like are like confronted like with like a like problem""" 22 | # 'we only think when we are confronted with a problem' 23 | 24 | everyOtherWord 'first second third fourth fifth sixth seventh eighth ninth tenth' 25 | # first third fifth seventh ninth 26 | -------------------------------------------------------------------------------- /10/listings/10.1.coffee: -------------------------------------------------------------------------------- 1 | 2 | assert = require 'assert' 3 | 4 | {addWord, removeWord} = require './word_utils' #A 5 | 6 | fact = (description, fn) -> #B 7 | try #B 8 | fn() #B 9 | console.log "#{description}: OK" #B 10 | catch e #B 11 | console.error "#{description}: " #B 12 | throw e #B 13 | 14 | 15 | fact "addWord adds a word", -> 16 | input = "product special" 17 | expectedOutput = "product special popular" 18 | actualOutput = addWord input, "popular" 19 | 20 | assert.equal expectedOutput, actualOutput 21 | 22 | fact "removeWord removes a word and surrounding whitespace", -> 23 | tests = [ 24 | initial: "product special" 25 | replace: "special" 26 | expected: "product" 27 | , 28 | initial: "product special" 29 | replace: "spec" 30 | expected: "product special" 31 | ] 32 | 33 | for {initial, replace, expected} in tests 34 | assert.equal removeWord(initial, replace), expected 35 | -------------------------------------------------------------------------------- /4/listings/4.9.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | 3 | class Views 4 | constructor: -> 5 | @pages = {} 6 | increment: (key) -> 7 | @pages[key] ?= 0 8 | @pages[key] = @pages[key] + 1 9 | total: -> 10 | sum = 0 11 | for own url, count of @pages 12 | sum = sum + count 13 | sum 14 | 15 | businessViews = new Views 16 | personalViews = new Views 17 | 18 | server = http.createServer (request, response) -> 19 | renderHit = (against) -> 20 | against.increment request.url 21 | response.writeHead 200, 'Content-Type': 'text/html' 22 | response.end "recorded" 23 | 24 | if request.url is '/' 25 | response.writeHead 200, 'Content-Type': 'text/html' 26 | response.end """ 27 | Personal: #{personalViews.total()} 28 | Business: #{businessViews.total()} 29 | """ 30 | else if /\/business\/.*/.test request.url 31 | renderHit businessViews 32 | else if /\/personal\/.*/.test request.url 33 | renderHit personalViews 34 | else 35 | response.writeHead 404, 'Content-Type': 'text/html' 36 | response.end "404" 37 | 38 | 39 | server.listen 8080, '127.0.0.1' 40 | 41 | exports.server = server 42 | -------------------------------------------------------------------------------- /10/listings/10.2.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | 3 | fact = (description, fn) -> 4 | try 5 | fn() 6 | console.log "#{description}: OK" 7 | catch e 8 | console.log "#{description}: \n#{e.stack}" 9 | 10 | 11 | http = #A 12 | get: (options, callback) -> #A 13 | callback "canned response" #A 14 | @ #A 15 | on: (event, callback) -> #A 16 | 17 | fetch = (topic, http, callback) -> 18 | options = 19 | host: 'www.agtronscameras.com' 20 | port: 80 21 | path: "/data/#{topic}" 22 | 23 | http.get(options, (result) -> 24 | callback result 25 | ).on 'error', (e) -> 26 | console.log e 27 | 28 | parse = (data) -> #B 29 | "parsed canned response" #B 30 | #B 31 | fact "data is parsed correctly", -> #B 32 | parsed = parse 'canned response' #B 33 | assert.equal parsed, "parsed canned response" #B 34 | 35 | fact "data is fetched correctly", -> 36 | fetch "a-topic", http, (result) -> 37 | assert.equal result, "canned response" 38 | 39 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/controllers/controller.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Controller 4 | routes = {} 5 | 6 | @route = (path, method) -> 7 | routes[path] = method 8 | 9 | constructor: (server) -> 10 | server.on 'request', (request, response) => 11 | url = require('url').parse(request.url) 12 | path = url.pathname 13 | if url.query then path += "?#{url.query}" 14 | handlers = [] 15 | for route, handler of routes 16 | if new RegExp("^#{route}$").test(path) 17 | handlers.push 18 | handler: handler 19 | matches: path.match(new RegExp("^#{route}$")) 20 | 21 | method = handlers[0]?.handler || 'default' 22 | if handlers[0]?.matches 23 | response.end @[method](request, response, handlers[0]?.matches.slice(1)...) 24 | else 25 | response.writeHead 404, 'Content-Type': 'text/html' 26 | response.end '404' 27 | 28 | render: (view, mime='text/html') -> 29 | @response.writeHead 200, 'Content-Type': mime 30 | @response.end view.render() 31 | 32 | default: (@request, @response) -> 33 | @render render: -> 'unknown' 34 | 35 | 36 | exports.Controller = Controller 37 | -------------------------------------------------------------------------------- /10/word_utils_v1.spec.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | {addWord, removeWord} = require './word_utils_v1' 3 | 4 | do removeWordShouldRemoveOneWord = -> 5 | tests = [ #A 6 | initial: "product special" #A 7 | replace: "special" #A 8 | expected: "product" #A 9 | , #A 10 | initial: "product special" #A 11 | replace: "spec" #A 12 | expected: "product special" #A 13 | ] 14 | 15 | for test in tests #B 16 | actual = removeWord(test.initial, test.replace) #B 17 | assert.equal actual, test.expected #B 18 | 19 | 20 | do addWordShouldAddOneWord = -> 21 | try #A 22 | input = "product special" #A 23 | expectedOutput = "product" #A 24 | actualOutput = addWord input, "ok" #A 25 | assert.equal expectedOutput, actualOutput #A 26 | console.log 'addWord passed' #A 27 | catch #A 28 | console.log 'addWord failed' 29 | -------------------------------------------------------------------------------- /2/listings/2.1.coffee: -------------------------------------------------------------------------------- 1 | houseRoast = null 2 | 3 | hasMilk = (style) -> #1 4 | switch style #1 5 | when "latte", "cappucino" #1 6 | yes #1 7 | else #1 8 | no #1 9 | 10 | makeCoffee = (requestedStyle) -> #2 11 | style = requestedStyle || 'Espresso' #2 12 | if houseRoast? #2 13 | "#{houseRoast} #{style}" #2 14 | else #2 15 | style #2 16 | 17 | barista = (style) -> #3 18 | time = (new Date()).getHours() #3 19 | if hasMilk(style) and time > 12 then "No!" #3 20 | else #3 21 | coffee = makeCoffee style #3 22 | "Enjoy your #{coffee}!" #3 23 | 24 | 25 | specifyHouseRoast = (roast) -> 26 | houseRoast = roast 27 | 28 | exports.hasMilk = hasMilk 29 | exports.makeCoffee = makeCoffee 30 | exports.barista = barista 31 | exports.specifyHouseRoast = specifyHouseRoast -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/app/controllers/blog.coffee: -------------------------------------------------------------------------------- 1 | {Controller} = require './controller' 2 | {Post} = require '../models' 3 | {views} = require '../views' 4 | 5 | fs = require 'fs' 6 | 7 | class Blog extends Controller 8 | 9 | @route '/', 'index' 10 | index: (@request, @response) => 11 | @posts = Post.all() 12 | @render views 'list', @posts 13 | 14 | @route '/([a-zA-Z0-9-]+)', 'show' 15 | show: (@request, @response, id) => 16 | @post = Post.get id 17 | if @post 18 | @render views 'post', @post 19 | else '' 20 | 21 | @route '/([a-zA-Z0-9-]+)[/]comments[?]insert=(.*)', 'insert_comment' 22 | insert_comment: (@request, @response, id, comment) => 23 | @post = Post.get id 24 | if @request.method is 'POST' 25 | @post.comments.push comment 26 | @render render: => JSON.stringify(@post.comments, 'text/json') 27 | 28 | 29 | @route '/([a-zA-Z0-9-]+)[/]comments', 'list_comments' 30 | list_comments: (@request, @response, id) => 31 | @post = Post.get id 32 | @render render: => JSON.stringify(@post.comments, 'text/json') 33 | 34 | @route '/(.*\.js)', 'js' 35 | js: (@request, @response, file) => 36 | @render views('js', file), 'application/javascript' 37 | 38 | exports.Blog = Blog 39 | -------------------------------------------------------------------------------- /13/listings/13.4.coffee: -------------------------------------------------------------------------------- 1 | class Formulaic 2 | "use strict" #A 3 | constructor: (@root, @selector, @http) -> 4 | @fields = {} 5 | @extractFields() 6 | 7 | bind: (field) -> 8 | Object.defineProperty @fields, field.name, 9 | set: (newValue) => 10 | field.value = newValue 11 | @sync() 12 | get: -> 13 | field.value 14 | enumerable: true 15 | configurable: true 16 | 17 | updateField = => 18 | @fields[field.name] = field.value 19 | 20 | updateField() 21 | field.addEventListener 'input', updateField 22 | 23 | disable: -> 24 | for key, value of @fields 25 | Object.defineProperty @fields, key, { value } 26 | for field in @documentFields() 27 | field.disabled = true 28 | 29 | Object.freeze @fields 30 | 31 | documentFields: -> 32 | element = @root.querySelector @selector 33 | element.getElementsByTagName 'input' 34 | 35 | extractFields: -> 36 | @bind field for field in @documentFields() 37 | 38 | sync: -> 39 | throw new Error 'No transport' unless @http? 40 | if @url? 41 | @http.post @url, JSON.stringify(@fields), (response) => 42 | @fields = JSON.parse response 43 | 44 | exports.Formulaic = Formulaic 45 | -------------------------------------------------------------------------------- /8/listings/8.7.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | coffee = require 'coffee-script' 3 | 4 | evalScruffyCoffeeFile = (fileName) -> 5 | fs.readFile fileName, 'utf-8', (error, scruffyCode) -> 6 | return if error 7 | tokens = coffee.tokens scruffyCode 8 | 9 | i = 0 10 | while token = tokens[i] 11 | isLambda = token[0] is 'IDENTIFIER' and /^λ[a-zA-Z]+$/.test token[1] 12 | if isLambda and tokens[i + 1][0] is '.' 13 | paramStart = ['PARAM_START', '(', {}] 14 | param = ['IDENTIFIER', token[1].replace(/λ/, ''), {}] 15 | paramEnd = ['PARAM_END', ')', {}] 16 | arrow = ['->', '->', {}] 17 | indent = ['INDENT', 2, generated: true] 18 | tokens.splice i, 2, paramStart, param, paramEnd, arrow, indent 19 | j = i 20 | while tokens[j][0] isnt 'TERMINATOR' 21 | j++ 22 | outdent = ['OUTDENT', 2, generated: true] 23 | tokens.splice j, 0, outdent 24 | i = i + 3 25 | continue 26 | i++ 27 | 28 | nodes = coffee.nodes tokens 29 | javaScript = nodes.compile() 30 | eval javaScript 31 | 32 | fileName = process.argv[2] 33 | unless fileName 34 | console.log 'No file specified' 35 | process.exit() 36 | 37 | evalScruffyCoffeeFile fileName 38 | -------------------------------------------------------------------------------- /2/listings/2.2.coffee: -------------------------------------------------------------------------------- 1 | hasMilk = (style) -> 2 | switch style 3 | when "latte", "cappucino" 4 | yes 5 | else 6 | no 7 | 8 | makeCoffee = -> 9 | style || 'Espresso' 10 | 11 | 12 | barista = (style) -> #2 13 | now = new Date() #3 14 | time = now.getHours() #3 15 | if hasMilk(style) and time > 12 #3 16 | "No!" #4 17 | else #3 18 | coffee = makeCoffee style #5 19 | "Enjoy your #{coffee}!" #5 20 | 21 | 22 | 23 | 24 | barista "latte" 25 | 26 | ### JavaScript (see also 2.2.clean.js) 27 | 28 | var hasMilk = function (style) { #1 29 | switch (style) { 30 | case "latte": 31 | case "cappucino": 32 | return true; 33 | default: 34 | return false; 35 | } 36 | }; 37 | 38 | var makeCoffee = function (style) { 39 | return style || 'Espresso'; 40 | }; 41 | 42 | var barista = function (style) { 43 | var now = new Date(); 44 | var time = now.getHours(); 45 | var coffee; 46 | if (hasMilk(style) && time > 12) { 47 | return "No"; 48 | } else { 49 | coffee = makeCoffee(style); 50 | return "Enjoy your "+coffee+"!"; 51 | } 52 | }; 53 | 54 | barista("latte"); 55 | ### 56 | 57 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/views/list.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var List, View, 4 | __hasProp = {}.hasOwnProperty, 5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 6 | 7 | View = require('./view').View; 8 | 9 | List = (function(_super) { 10 | __extends(List, _super); 11 | 12 | function List(posts) { 13 | this.posts = posts; 14 | } 15 | 16 | List.prototype.render = function() { 17 | var all, post; 18 | all = ((function() { 19 | var _i, _len, _ref, _results; 20 | _ref = this.posts; 21 | _results = []; 22 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 23 | post = _ref[_i]; 24 | _results.push("
  • " + post.title + "
  • "); 25 | } 26 | return _results; 27 | }).call(this)).join(''); 28 | return this.wrap(""); 29 | }; 30 | 31 | return List; 32 | 33 | })(View); 34 | 35 | exports.List = List; 36 | 37 | }).call(this); 38 | -------------------------------------------------------------------------------- /exercises/3.4.4.coffee: -------------------------------------------------------------------------------- 1 | # Use accumulate to create a sumFractions function that will sum fractions 2 | # supplied as strings and return a fraction as a string. 3 | 4 | accumulate = (initial, items, accumulator) -> 5 | total = initial 6 | for item in items 7 | total = accumulator total, item 8 | total 9 | 10 | sumFractions = (fractions) -> 11 | accumulator = (lhs, rhs) -> 12 | if lhs is '0/0' 13 | rhs 14 | else if rhs is '0/0' 15 | lhs 16 | else 17 | lhsSplit = lhs.split /\//gi 18 | rhsSplit = rhs.split /\//gi 19 | lhsNumer = 1*lhsSplit[0] 20 | lhsDenom = 1*lhsSplit[1] 21 | rhsNumer = 1*rhsSplit[0] 22 | rhsDenom = 1*rhsSplit[1] 23 | if lhsDenom isnt rhsDenom 24 | commonDenom = lhsDenom*rhsDenom 25 | else 26 | commonDenom = lhsDenom 27 | 28 | sumNumer = lhsNumer*(commonDenom/lhsDenom) + rhsNumer*(commonDenom/rhsDenom) 29 | "#{sumNumer}/#{commonDenom}" 30 | 31 | accumulate '0/0', fractions, accumulator 32 | 33 | 34 | console.log sumFractions ['2/6', '1/4'] 35 | # '14/24' 36 | 37 | sumFractions ['4/2', '5/2', '1/3'] 38 | # 29/6 39 | 40 | 41 | keep = (arr, cond) -> 42 | item for item in arr when cond item 43 | 44 | greaterThan3 = (n) -> n > 3 45 | keep [1,2,3,4], greaterThan3 46 | 47 | console.log keep [1,2,3,4], greaterThan3 48 | -------------------------------------------------------------------------------- /3/listings/3.6.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | http = require 'http' 3 | 4 | makeMostRecent = (file1, file2) -> #A 5 | mostRecent = 'Nothing read yet.' #A 6 | #A 7 | sourceFileWatcher = (fileName) -> #A 8 | sourceFileReader = -> #A 9 | fs.readFile fileName, (error, data) -> #A 10 | mostRecent = data #A 11 | fs.watch fileName, sourceFileReader #A 12 | #A 13 | sourceFileWatcher file1 #A 14 | sourceFileWatcher file2 #A 15 | #A 16 | getMostRecent = -> #A 17 | mostRecent #A 18 | 19 | 20 | makeServer = -> #B 21 | mostRecent = makeMostRecent 'file1.txt', 'file2.txt' #B 22 | #B 23 | server = http.createServer (request, response) -> #B 24 | response.write mostRecent() #B 25 | response.end() #B 26 | #B 27 | server.listen '8080', '127.0.0.1' #B 28 | 29 | server = makeServer() #C 30 | -------------------------------------------------------------------------------- /12/listings/12.14.coffee: -------------------------------------------------------------------------------- 1 | 2 | # This task belongs to a Cakefile. See the final Cakefile for the 3 | # complete tasks for the application. 4 | 5 | task 'build:client', 'build client side stuff with modules', -> 6 | compiler = require 'coffee-script' 7 | modules = fs.readFileSync "lib/modules.coffee", "utf-8" #A 8 | 9 | modules = compiler.compile modules, bare: true #B 10 | files = fs.readdirSync 'client' 11 | source = (for file in files when /\.coffee$/.test file 12 | module = file.replace /\.coffee/, '' 13 | fileSource = fs.readFileSync "client/#{file}", "utf-8" #C 14 | """ 15 | defmodule({#{module}: function (require, exports) { 16 | #{compiler.compile(fileSource, bare: true)} 17 | }}); 18 | """ #D 19 | ).join '\n\n' #E 20 | 21 | out = modules + '\n\n' + source 22 | fs.writeFileSync 'compiled/app/client/application.js' #F 23 | #A lib/modules.coffee contains the code from listing 13.13 24 | #B CoffeeScript is compiled bare. Meaning that there is no outer function wrapper 25 | #C As this is in the build where you don’t care about blocking - readFileSync is acceptable. 26 | #D The actual file output is the compiled CoffeeScript wrapped in the defmodule call. 27 | #E All of the modules are concatenated with a simple join 28 | #F Write the entire compiled application to application.js 29 | -------------------------------------------------------------------------------- /9/listings/with_events_tests.coffee: -------------------------------------------------------------------------------- 1 | {EventEmitter} = require 'events' 2 | 3 | withEvents = (emitter, event) -> 4 | pipeline = [] 5 | data = [] 6 | 7 | reset = -> pipeline = [] 8 | 9 | reset() 10 | 11 | run = -> 12 | result = data 13 | for processor in pipeline 14 | if processor.filter? 15 | result = result.filter processor.filter 16 | else if processor.map? 17 | result = result.map processor.map 18 | result 19 | 20 | emitter.on event, (datum) -> 21 | data.push datum 22 | 23 | filter: (filter) -> 24 | pipeline.push {filter} 25 | @ 26 | map: (map) -> 27 | pipeline.push {map} 28 | @ 29 | drain: (fn) -> #A 30 | emitter.on event, (datum) -> #A 31 | result = run() #A 32 | data = [] #A 33 | fn result #A 34 | evaluate: -> 35 | result = run() 36 | result 37 | reset: -> 38 | reset() 39 | 40 | 41 | assert = require 'assert' 42 | 43 | even = (number) -> number%2 is 0 44 | 45 | emitter = new EventEmitter 46 | evenNumberEvents = withEvents(emitter, 'number').filter(even) 47 | 48 | emitter.emit 'number', 2 49 | emitter.emit 'number', 5 50 | 51 | assert.deepEqual evenNumberEvents.evaluate(), [2] 52 | # [2] 53 | 54 | emitter.emit 'number', 4 55 | emitter.emit 'number', 3 56 | 57 | assert.deepEqual evenNumberEvents.evaluate(), [2, 4] 58 | # [2, 4] -------------------------------------------------------------------------------- /exercises/4.2.3-1.coffee: -------------------------------------------------------------------------------- 1 | phonebook = 2 | numbers: 3 | hannibal: '555-5551' 4 | darth: '555-5552' 5 | hal9000: 'disconnected' 6 | freddy: '555-5554' 7 | 'T-800': '555-5555' 8 | list: -> 9 | "#{name}: #{number}" for name, number of @numbers 10 | add: (name, number) -> 11 | if not (name of @numbers) 12 | @numbers[name] = number 13 | else 14 | "#{name} already exists" 15 | edit: (name, number) -> 16 | if name of @numbers 17 | @numbers[name] = number 18 | else 19 | "#{name} not found" 20 | get: (name) -> 21 | if name of @numbers 22 | "#{name}: #{@numbers[name]}" 23 | else 24 | "#{name} not found" 25 | 26 | console.log "Phonebook. Commands are add, get, edit, list, and exit." 27 | 28 | process.stdin.setEncoding 'utf8' 29 | stdin = process.openStdin() 30 | 31 | stdin.on 'data', (chunk) -> 32 | args = chunk.split(' ') 33 | command = args[0].trim() 34 | name = args[1].trim() if args[1] 35 | number = args[2].trim() if args[2] 36 | switch command 37 | when 'add' 38 | res = phonebook.add(name, number) if name and number 39 | console.log res 40 | when 'get' 41 | console.log phonebook.get(name) if name 42 | when 'edit' 43 | console.log phonebook.edit(name, number) if name and number 44 | when 'list' 45 | console.log phonebook.list() 46 | when 'exit' 47 | process.exit 1 48 | -------------------------------------------------------------------------------- /exercises/4.2.3.coffee: -------------------------------------------------------------------------------- 1 | phonebook = 2 | numbers: 3 | hannibal: '555-5551' 4 | darth: '555-5552' 5 | hal9000: 'disconnected' 6 | freddy: '555-5554' 7 | 'T-800': '555-5555' 8 | list: -> 9 | "#{name}: #{number}" for name, number of @numbers 10 | add: (name, number) -> 11 | if not (name of @numbers) 12 | @numbers[name] = number 13 | else 14 | "#{name} already exists" 15 | edit: (name, number) -> 16 | if name of @numbers 17 | @numbers[name] = number 18 | else 19 | "#{name} not found" 20 | get: (name) -> 21 | if name of @numbers 22 | "#{name}: #{@numbers[name]}" 23 | else 24 | "#{name} not found" 25 | 26 | console.log "Phonebook. Commands are add, get, edit, list, and exit." 27 | 28 | process.stdin.setEncoding 'utf8' 29 | stdin = process.openStdin() 30 | 31 | stdin.on 'data', (chunk) -> 32 | args = chunk.split(' ') 33 | command = args[0].trim() 34 | name = args[1].trim() if args[1] 35 | number = args[2].trim() if args[2] 36 | switch command 37 | when 'add' 38 | res = phonebook.add(name, number) if name and number 39 | console.log res 40 | when 'get' 41 | console.log phonebook.get(name) if name 42 | when 'edit' 43 | console.log phonebook.edit(name, number) if name and number 44 | when 'list' 45 | console.log phonebook.list() 46 | when 'exit' 47 | process.exit 1 48 | -------------------------------------------------------------------------------- /2/listings/barista.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var barista, hasMilk, houseRoast, makeCoffee, order, request, response; 4 | 5 | houseRoast = 'Yirgacheffe'; 6 | 7 | hasMilk = function(style) { 8 | switch (style.toLowerCase()) { 9 | case 'latte': 10 | case 'cappucino': 11 | case 'mocha': 12 | return true; 13 | default: 14 | return false; 15 | } 16 | }; 17 | 18 | makeCoffee = function(requestedStyle) { 19 | var style; 20 | style = requestedStyle || 'Espresso'; 21 | console.log(houseRoast); 22 | if (houseRoast != null) { 23 | return "" + houseRoast + " " + style; 24 | } else { 25 | return style; 26 | } 27 | }; 28 | 29 | barista = function(style) { 30 | var coffee, time; 31 | time = (new Date()).getHours(); 32 | if (hasMilk(style) && time > 12) { 33 | return "No!"; 34 | } else { 35 | coffee = makeCoffee(style); 36 | return "Enjoy your " + coffee + "!"; 37 | } 38 | }; 39 | 40 | /* 41 | Browser Scripting 42 | */ 43 | 44 | 45 | order = document.querySelector('#order'); 46 | 47 | request = document.querySelector('#request'); 48 | 49 | response = document.querySelector('#response'); 50 | 51 | order.onsubmit = function() { 52 | alert(barista(request.value)); 53 | response.innerHTML = barista(request.value); 54 | return false; 55 | }; 56 | 57 | }).call(this); 58 | -------------------------------------------------------------------------------- /7/listings/7.2.coffee: -------------------------------------------------------------------------------- 1 | 2 | makeCompetition = ({max, sort}) -> #A 3 | find = (name) -> 4 | document.querySelector ".#{name}" 5 | 6 | color = (element, color) -> 7 | element.style.background = color 8 | 9 | insert = (teams...) -> 10 | root = document.querySelector '.teams' 11 | for team in teams 12 | element = document.createElement 'li' 13 | element.innerHTML = "#{team.name} (#{team.points})" 14 | element.className = team.name 15 | root.appendChild element 16 | 17 | highlight = (first, rest...) -> 18 | color find(first.name), 'gold' 19 | for team in rest 20 | color find(team.name), 'blue' 21 | 22 | rank = (unranked) -> #B 23 | unranked.sort(sort).slice(0, max) #B 24 | 25 | 26 | initialize: (unranked) -> 27 | ranked = rank unranked 28 | insert ranked... 29 | first = ranked.slice(0, 1)[0] 30 | rest = ranked.slice 1 31 | highlight first, rest... 32 | 33 | { initialize } 34 | 35 | 36 | sortOnPoints = (a, b) -> #D 37 | a.points > b.points #D 38 | 39 | window.onload = -> 40 | competition = makeCompetition max: 5, sort: sortOnPoints #E 41 | competition.initialize [ 42 | { name: 'wolverines', points: 22 } 43 | { name: 'wildcats', points: 11 } 44 | { name: 'mongooses', points: 33 } 45 | { name: 'raccoons', points: 12 } 46 | { name: 'badgers', points: 19 } 47 | { name: 'baboons', points: 16 } 48 | ] 49 | -------------------------------------------------------------------------------- /8/listings/8.1.litcoffee: -------------------------------------------------------------------------------- 1 | ## Rot13 2 | A simple letter substitution cipher that replaces a letter 3 | with the letter 13 letters after it in the alphabet. 4 | 5 | charRot13 = (char) -> 6 | 7 | The built-in string utility for getting character codes can be used 8 | 9 | charCode = char.charCodeAt 0 10 | 11 | If the character is in the alphabet up to 'm' then 12 | add 13 to the character code 13 | 14 | charCodeRot13 = if charInRange char, 'a', 'm' 15 | charCode + 13 16 | 17 | If the character is after 'm' in the alphabet then 18 | subtract 13 from the character code 19 | 20 | else if charInRange char, 'n', 'z' 21 | charCode - 13 22 | else 23 | charCode 24 | 25 | Characters can be converted back using the built-in string method 26 | 27 | String.fromCharCode charCodeRot13 28 | 29 | A character is in a specific range regardless of whether 30 | it's uppercase or lowercase 31 | 32 | charInRange = (char, first, last) -> 33 | lowerCharCode = char.toLowerCase().charCodeAt(0) 34 | first.charCodeAt(0) <= lowerCharCode <= last.charCodeAt(0) 35 | 36 | 37 | Converting a string is done by converting all the characters 38 | and joining the result 39 | 40 | stringRot13 = (string) -> 41 | (charRot13 char for char in string).join '' 42 | 43 | 44 | assert = require 'assert' 45 | 46 | assert.equal stringRot13('Some Rot13 for you'), 'Fbzr Ebg13 sbe lbh' 47 | assert.equal stringRot13('Fbzr Ebg13 sbe lbh'), 'Some Rot13 for you' 48 | -------------------------------------------------------------------------------- /exercises/10.4.4.coffee: -------------------------------------------------------------------------------- 1 | # Suppose this is the Tracking class and http object: 2 | class Tracking 3 | constructor: (prefs, http) -> 4 | @http = http 5 | start: -> 6 | @http.listen() 7 | 8 | 9 | http = listen: -> 10 | 11 | # A potential double function follows: 12 | double = (original) -> 13 | mock = {} 14 | for key, value of original 15 | if value.call? 16 | do -> 17 | stub = -> 18 | stub.called = true 19 | mock[key] = stub 20 | mock 21 | 22 | 23 | # This double function returns a mock version of the original object. 24 | # It has all the same method names, but the methods themselves are just 25 | # empty functions that remember if they’ve been called or not. 26 | # 27 | # In other circumstances your test might call for a spy instead. 28 | # When you spy on an object, any method calls still occur on the original 29 | # object but are seen by the spy. A double function that returns a spy follows: 30 | 31 | double = (original) -> 32 | spy = Object.create original 33 | for key, value of original 34 | if value.call? 35 | do -> 36 | originalMethod = value 37 | spyMethod = (args...) -> 38 | spyMethod.called = true 39 | originalMethod args... 40 | spy[key] = spyMethod 41 | spy 42 | 43 | # Depending on the test framework you’re using, there may be both mocking and 44 | # spying libraries provided for you. The pros and cons of using mocks, spies, 45 | # and other testing techniques aren’t covered here. 46 | -------------------------------------------------------------------------------- /9/listings/9.1.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | http = require 'http' 3 | 4 | readFile = (file, strategy) -> 5 | fs.readFile file, 'utf-8', (error, response) -> 6 | throw error if error 7 | strategy response 8 | 9 | readFileAsArray = (file, delimiter, callback) -> 10 | asArray = (data) -> 11 | callback data.split(delimiter).slice(0,-1) 12 | readFile(file, asArray) 13 | 14 | compareOnLastName = (a,b) -> 15 | lastName = (s) -> 16 | s.split(/\s+/g)[1].replace /,/, ',' 17 | if !a or !b 18 | 1 19 | else if lastName(a) >= lastName(b) 20 | 1 21 | else 22 | -1 23 | 24 | sortedCompetitorsFromFile = (fileName, callback) -> 25 | newline = /\n/gi 26 | readFileAsArray fileName, newline, (array) -> 27 | callback array.sort(compareOnLastName) 28 | 29 | makeServer = -> 30 | responseData = '' 31 | server = http.createServer (request, response) -> 32 | response.writeHead 200, 'Content-Type': 'text/html' 33 | response.end JSON.stringify responseData 34 | server.listen 8888, '127.0.0.1' 35 | (data) -> 36 | responseData = data 37 | 38 | main = (fileName) -> 39 | server = makeServer() 40 | 41 | loadData = -> 42 | console.log 'Loading data' 43 | sortedCompetitorsFromFile fileName, (data) -> 44 | console.log 'Data loaded' 45 | server data 46 | loadData() 47 | fs.watchFile fileName, loadData 48 | 49 | if process.argv[2] 50 | main process.argv[2] 51 | console.log "Starting server on port 8888" 52 | else 53 | console.log "usage: coffee 9.1.coffee [file]" 54 | -------------------------------------------------------------------------------- /6/listings/6.6.coffee: -------------------------------------------------------------------------------- 1 | before = (decoration) -> 2 | (base) -> 3 | (params...) -> 4 | decoration.apply @, params 5 | base.apply @, params 6 | 7 | after = (decoration) -> 8 | (base) -> 9 | (params...) -> 10 | result = base.apply @, params 11 | decoration.apply @, params 12 | result 13 | 14 | around = (decoration) -> 15 | (base) -> 16 | (params...) -> 17 | result = undefined 18 | func = => result = base.apply @, params 19 | decoration.apply @, ([func].concat params) 20 | result 21 | 22 | ## Example 23 | class Robot 24 | withRunningEngine = around (action) -> 25 | @startEngine() 26 | action() 27 | @stopEngine() 28 | constructor: (@at = 0) -> 29 | position: -> 30 | @at 31 | move: (displacement) -> 32 | console.log 'move' 33 | @at += displacement 34 | startEngine: -> console.log 'start engine' 35 | stopEngine: -> console.log 'stop engine' 36 | forward: withRunningEngine -> 37 | @move 1 38 | reverse: withRunningEngine -> 39 | @move -1 40 | wasteFuel: withRunningEngine -> 41 | console.log 'Wasting fuel' 42 | 'Fuel wasted' 43 | 44 | 45 | bender = new Robot 3 46 | bender.forward() 47 | # start engine 48 | # move 49 | # stop engine 50 | 51 | bender.forward() 52 | # start engine 53 | # move 54 | # stop engine 55 | 56 | bender.reverse() 57 | # start engine 58 | # move 59 | # stop engine 60 | assert = require 'assert' 61 | assert.deepEqual bender.position(), 4 62 | # 4 63 | 64 | assert.equal bender.wasteFuel(), 'Fuel wasted' -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/spec/client/comments_spec.coffee: -------------------------------------------------------------------------------- 1 | {describe, it} = require 'chromic' 2 | 3 | {Comments} = require '../../app/client/comments' 4 | 5 | describe 'Comments', -> 6 | it 'should post a comment to the server', -> 7 | requested = false 8 | httpRequest = (url) -> requested = url 9 | comments = new Comments 'http://the-url', {}, httpRequest 10 | 11 | comment = 'Hey Agtron. Nice site.' 12 | comments.post comment 13 | 14 | requested.shouldBe "http://the-url/comments?insert=#{comment}" 15 | 16 | it 'should fetch the comments when constructed', -> 17 | requested = false 18 | httpRequest = (url) -> requested = url 19 | 20 | comments = new Comments 'http://the-url', {}, httpRequest 21 | 22 | requested.shouldBe "http://the-url/comments" 23 | 24 | it 'should bind to event on the element', -> 25 | comments = new Comments 'http://the-url', {}, -> 26 | 27 | element = 28 | querySelector: -> element 29 | value: 'A comment from Scruffy' 30 | 31 | comments.bind element, 'post' 32 | postReceived = false 33 | comments.post = (comment) -> postReceived = comment 34 | 35 | element.onpost() 36 | 37 | postReceived.shouldBe element.value 38 | 39 | it 'should render comments to the page as a list', -> 40 | out = innerHTML: (content) -> renderedContent = content 41 | 42 | comments = new Comments 'http://the-url', out, -> 43 | 44 | comments.render '["One", "Two", "Three"]' 45 | 46 | out.innerHTML.shouldBe "" 47 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/client/comments.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Comments, 3 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 4 | 5 | Comments = (function() { 6 | function Comments(url, out, http_request) { 7 | this.url = url; 8 | this.out = out; 9 | this.http_request = http_request; 10 | this.render = __bind(this.render, this); 11 | this.http_request("" + this.url + "/comments", this.render); 12 | } 13 | 14 | Comments.prototype.post = function(comment) { 15 | return this.http_request("" + this.url + "/comments?insert=" + comment, this.render); 16 | }; 17 | 18 | Comments.prototype.bind = function(element, event) { 19 | var comment, 20 | _this = this; 21 | comment = element.querySelector('textarea'); 22 | return element["on" + event] = function() { 23 | _this.post(comment.value); 24 | return false; 25 | }; 26 | }; 27 | 28 | Comments.prototype.render = function(data) { 29 | var comments, formatted, in_li; 30 | in_li = function(text) { 31 | return "
  • " + text + "
  • "; 32 | }; 33 | if (data !== '') { 34 | comments = JSON.parse(data); 35 | if (comments.map != null) { 36 | formatted = comments.map(in_li).join(''); 37 | return this.out.innerHTML = ""; 38 | } 39 | } 40 | }; 41 | 42 | return Comments; 43 | 44 | })(); 45 | 46 | exports.Comments = Comments; 47 | 48 | }).call(this); 49 | -------------------------------------------------------------------------------- /3/listings/3.5.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | 3 | split = (text) -> #A 4 | text.split /\W/g #A 5 | 6 | count = (text) -> #B 7 | parts = split text #B 8 | words = (word for word in parts when word.trim().length > 0) #B 9 | words.length #B 10 | 11 | countMany = (texts) -> #D 12 | sum = 0 #D 13 | for text in texts #D 14 | sum = sum + count text #D 15 | sum #D 16 | 17 | countWordsInFile = (fileName) -> #E 18 | stream = fs.createReadStream fileName #E 19 | stream.setEncoding 'ascii' #E 20 | wordCount = 0 #E 21 | stream.on 'data', (data) -> #E 22 | lines = data.split /\n/gm #E 23 | wordCount = wordCount + countMany lines #E 24 | stream.on 'close', () -> #E 25 | console.log "#{wordCount} words" #E 26 | 27 | file = process.argv[2] #F 28 | 29 | if file #G 30 | countWordsInFile file #G 31 | else #G 32 | console.log 'usage: coffee 3.5.coffee [file]' #G 33 | -------------------------------------------------------------------------------- /11/listings/11.6.coffee: -------------------------------------------------------------------------------- 1 | window.onload = -> 2 | status = document.querySelector '#status' 3 | graph = document.createElement 'canvas' 4 | graph.width = window.innerWidth 5 | graph.height = window.innerHeight 6 | status.appendChild graph 7 | context = graph.getContext '2d' 8 | 9 | render = (buffer) -> 10 | context.fillStyle = 'black' 11 | context.clearRect 0, 0, graph.width, graph.height 12 | context.fillRect 0, 0, graph.width, graph.height 13 | context.lineWidth = 5 14 | context.strokeStyle = '#5AB946' 15 | context.beginPath() 16 | prev = 0 17 | for y, x in buffer() 18 | unless y is prev 19 | context.lineTo 0 + x, 100 + y 20 | prev = y 21 | context.stroke() 22 | 23 | seconds = (n) -> 24 | 1000*n 25 | 26 | framesPerSecond = (n) -> 27 | (seconds 1)/n 28 | 29 | buffer = [] 30 | 31 | nextCallbackId = do -> 32 | callbackId = 0 33 | -> callbackId = callbackId + 1 34 | 35 | nextCallbackName = -> 36 | "callback#{nextCallbackId()}" 37 | 38 | fetch = (src, callback) -> 39 | head = document.querySelector 'head' 40 | script = document.createElement 'script' 41 | ajaxCallbackName = nextCallbackName() 42 | window[ajaxCallbackName] = (data) -> 43 | callback data 44 | script.src = src + "?callback=#{ajaxCallbackName}" 45 | head.appendChild script 46 | 47 | window.setInterval -> 48 | fetch '/feed.json', (json) -> 49 | render -> 50 | buffer.push (JSON.parse json).hits 51 | if buffer.length is graph.width then buffer.shift() 52 | buffer 53 | , framesPerSecond 30 54 | -------------------------------------------------------------------------------- /10/add_class.coffee: -------------------------------------------------------------------------------- 1 | 2 | {add_word, remove_word} = require './word_utils' 3 | 4 | {describe, it} = require 'chromic' 5 | 6 | #curry = curry '../6/curry' 7 | 8 | make_document = -> 9 | node = 10 | className: "" 11 | 12 | nodes = {} 13 | 14 | querySelector: (selector) -> 15 | unless nodes[selector]? 16 | nodes[selector] = Object.create node 17 | nodes[selector] 18 | 19 | # you will recognise this pattern. 20 | # These two functions are the same 21 | # except for the strategy they use. 22 | 23 | modify_class = (selector, modifier, new_class) -> 24 | element = document.querySelector selector 25 | element.className = modifier(element.className, new_class) 26 | 27 | modify_class = (seeker, selector, modifier, new_class) -> 28 | element = seeker(selector) 29 | element.className = modifier(element.className, new_class) 30 | 31 | add_class = (selector, new_class) -> 32 | modify_class(selector, add_word, new_class) 33 | 34 | remove_class = (selector, old_class) -> 35 | modify_class(selector, remove_word, old_class) 36 | 37 | # perhaps should use curry here instead? 38 | 39 | describe "adding and removing classes", -> 40 | document = make_document() 41 | 42 | assert_class = (selector, expected) -> 43 | document.querySelector(selector).className.should_be expected 44 | 45 | it "should add a class", -> 46 | add_class "#sel", "def" 47 | assert_class "#sel", "def" 48 | 49 | add_class "#sel", "jkl" 50 | assert_class "#sel", "def jkl" 51 | 52 | remove_class "#sel", "def" 53 | assert_class "#sel", "jkl" 54 | 55 | 56 | exports.add_class = add_class 57 | exports.remove_class = remove_class 58 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/models/post.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var Model, Post, 4 | __hasProp = {}.hasOwnProperty, 5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 6 | 7 | Model = require('./model').Model; 8 | 9 | Post = (function(_super) { 10 | var posts; 11 | 12 | __extends(Post, _super); 13 | 14 | posts = []; 15 | 16 | function Post(title, body) { 17 | this.title = title; 18 | this.body = body; 19 | if (!this.title) { 20 | throw 'requires title'; 21 | } 22 | this.comments = []; 23 | Post.__super__.constructor.apply(this, arguments); 24 | this.slug = this.dirify(this.title); 25 | posts.push(this); 26 | } 27 | 28 | Post.all = function() { 29 | return posts; 30 | }; 31 | 32 | Post.get = function(slug) { 33 | var post; 34 | return ((function() { 35 | var _i, _len, _results; 36 | _results = []; 37 | for (_i = 0, _len = posts.length; _i < _len; _i++) { 38 | post = posts[_i]; 39 | if (post.slug === slug) { 40 | _results.push(post); 41 | } 42 | } 43 | return _results; 44 | })())[0]; 45 | }; 46 | 47 | Post.purge = function() { 48 | return posts = []; 49 | }; 50 | 51 | return Post; 52 | 53 | })(Model); 54 | 55 | exports.Post = Post; 56 | 57 | }).call(this); 58 | -------------------------------------------------------------------------------- /7/listings/7.3.coffee: -------------------------------------------------------------------------------- 1 | 2 | makeCompetition = ({max, sort}) -> 3 | render = (team) -> 4 | """ 5 | 6 | #{team?.name||''} 7 | #{team?.points||''} 8 | #{team?.goals?.scored||''} 9 | #{team?.goals?.conceded||''} 10 | 11 | """ 12 | 13 | find = (name) -> 14 | document.querySelector ".#{name}" 15 | 16 | color = (element, color) -> 17 | element.style.background = color 18 | 19 | insert = (teams...) -> 20 | root = document.querySelector '.teams' 21 | for team in teams 22 | root.innerHTML += render team 23 | 24 | highlight = (first, rest...) -> 25 | color find(first.name), 'gold' 26 | for team in rest 27 | color find(team.name), 'blue' 28 | 29 | rank = (unranked) -> 30 | unranked.sort(sort).slice(0, max).reverse() 31 | 32 | 33 | initialize: (unranked) -> 34 | ranked = rank unranked 35 | insert ranked... 36 | first = ranked.slice(0, 1)[0] 37 | rest = ranked.slice 1 38 | highlight first, rest... 39 | 40 | sortOnPoints = (a, b) -> 41 | a.points > b.points 42 | 43 | window.onload = -> 44 | competition = makeCompetition(max:5, sort: sortOnPoints) 45 | competition.initialize [ 46 | name: 'wolverines' 47 | points: 56 48 | goals: 49 | scored: 26 50 | conceded: 8 51 | , 52 | name: 'wildcats' 53 | points: 53 54 | goals: 55 | scored: 32 56 | conceded: 19 57 | , 58 | name: 'mongooses' 59 | points: 34 60 | goals: 61 | scored: 9 62 | conceded: 9 63 | , 64 | name: 'racoons' # disqualified 65 | points: 0 66 | ] 67 | -------------------------------------------------------------------------------- /10/listings/10.3.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | 3 | {fact} = require './fact' #A 4 | {Tracking} = require './10.4' #B 5 | 6 | fact 'controller responds with 200 header and empty body', -> 7 | request = url: '/some/url' 8 | 9 | response = #C 10 | write: (body) -> #C 11 | @body = body #C 12 | writeHead: (status) -> #C 13 | @status = status #C 14 | end: -> #C 15 | @ended = true #C 16 | 17 | tracking = new Tracking 18 | for view in [1..10] 19 | tracking.controller request, response 20 | 21 | assert.equal response.status, 200 #D 22 | assert.equal response.body, '' #D 23 | assert.ok response.ended #D 24 | assert.equal tracking.pages['/some/url'], 10 #D 25 | 26 | 27 | fact 'increments once for each key', -> 28 | tracking = new Tracking #E 29 | tracking.increment 'a/page' for i in [1..100] #E 30 | tracking.increment 'another/page' #E 31 | 32 | assert.equal tracking.pages['a/page'], 100 #F 33 | assert.equal tracking.total(), 101 #F 34 | 35 | fact 'starts and stops server', -> 36 | http = #G 37 | createServer: -> #G 38 | @created = true #G 39 | listen: => #G 40 | @listening = true #G 41 | close: => #G 42 | @listening = false #G 43 | 44 | tracking = new Tracking {}, http #H 45 | tracking.start() #H 46 | 47 | assert.ok http.listening #H 48 | 49 | tracking.stop() #H 50 | assert.ok not http.listening #H 51 | -------------------------------------------------------------------------------- /8/listings/8.9.coffee: -------------------------------------------------------------------------------- 1 | 2 | # let with a rewriter 3 | 4 | fs = require 'fs' 5 | coffee = require 'coffee-script' 6 | 7 | evalScruffyCoffeeFile = (fileName) -> 8 | fs.readFile fileName, 'utf-8', (error, scruffyCode) -> 9 | letReplacedScruffyCode = scruffyCode.replace /\slet\s/g, ' $LET ' 10 | return if error 11 | tokens = coffee.tokens letReplacedScruffyCode 12 | 13 | i = 0 14 | consumingLet = false 15 | waitingForOutdent = 0 16 | while token = tokens[i] 17 | if token[0] is 'IDENTIFIER' and token[1] is '$LET' 18 | consumingLet = true 19 | doToken = ['UNARY', 'do', spaced: true] 20 | tokens.splice i, 1, doToken 21 | else if consumingLet 22 | if token[0] is 'CALL_START' 23 | paramStartToken = ['PARAM_START', '(', spaced: true] 24 | tokens[i + 1][2] = 0 25 | tokens.splice i, 1, paramStartToken 26 | if token[0] is 'CALL_END' 27 | paramEndToken = ['PARAM_END', ')', spaced: true] 28 | functionArrowToken = ['->', '->', spaced: true] 29 | indentToken = ['INDENT', 2, generated: true] 30 | tokens.splice i, 2, paramEndToken, functionArrowToken, indentToken 31 | consumingLet = false 32 | waitingForOutdent++ 33 | else if waitingForOutdent isnt 0 34 | if token[0] is 'OUTDENT' 35 | outdentToken = ['OUTDENT', 2, generated: true] 36 | tokens.splice i, 0, outdentToken 37 | waitingForOutdent-- 38 | i++ 39 | 40 | nodes = coffee.nodes tokens 41 | 42 | javaScript = nodes.compile() 43 | console.log javaScript 44 | eval javaScript 45 | 46 | fileName = process.argv[2] 47 | process.exit 'No file specified' unless fileName 48 | evalScruffyCoffeeFile fileName 49 | -------------------------------------------------------------------------------- /11/listings/11.2.coffee: -------------------------------------------------------------------------------- 1 | window.onload = -> #A 2 | status = document.querySelector '#status' 3 | 4 | 5 | render = (buffer) -> #B 6 | status.style.color = 'green' #B 7 | status.style.fontSize = '120px' #B 8 | status.innerHTML = buffer[buffer.length-1] #B 9 | 10 | nextCallbackId = do -> 11 | callbackId = 0 12 | -> callbackId = callbackId + 1 13 | 14 | nextCallbackName = -> 15 | "callback#{nextCallbackId()}" 16 | 17 | fetch = (src, callback) -> #C 18 | head = document.querySelector 'head' #C 19 | script = document.createElement 'script' #C 20 | ajaxCallbackName = nextCallbackName() #C 21 | window[ajaxCallbackName] = (data) -> #C 22 | callback data #C 23 | script.src = src + "?callback=#{ajaxCallbackName}" #C 24 | head.appendChild script #C 25 | 26 | seconds = (n) -> 27 | 1000*n 28 | 29 | framesPerSecond = (n) -> 30 | (seconds 1)/n 31 | 32 | makeUpdater = (buffer = []) -> #D 33 | bufferRenderer = (json) -> #D 34 | buffer.push (JSON.parse json).hits #D 35 | render buffer #D 36 | #D 37 | -> #D 38 | window.setInterval -> #D 39 | fetch '/feed.json', bufferRenderer #D 40 | , framesPerSecond 20 #D 41 | 42 | updater = makeUpdater() 43 | updater() 44 | -------------------------------------------------------------------------------- /8/listings/8.8.coffee: -------------------------------------------------------------------------------- 1 | 2 | fs = require 'fs' 3 | coffee = require 'coffee-script' 4 | 5 | capitalizeFirstLetter = (string) -> 6 | string.replace /^(.)/, (character) -> character.toUpperCase() 7 | 8 | generateTestMethod = (name) -> 9 | "test#{capitalizeFirstLetter name}: -> assert false" 10 | 11 | walkAst = (node) -> 12 | generated = "assert = require 'assert'" 13 | 14 | if node.body?.classBody 15 | className = node.variable.base.value 16 | methodTests = for expression in node.body.expressions 17 | if expression.base?.properties 18 | methodTestBodies = for objectProperties in expression.base.properties 19 | if objectProperties.value.body? 20 | generateTestMethod objectProperties.variable.base.value 21 | methodTestBodies.join '\n\n ' 22 | methodTestsAsText = methodTests.join('').replace /^\n/, '' 23 | generated += """ 24 | \n 25 | class Test#{className} 26 | #{methodTestsAsText} 27 | 28 | test = new Test#{className} 29 | for methodName of Test#{className}:: 30 | test[methodName]() 31 | """ 32 | 33 | 34 | expressions = node.expressions || [] 35 | if expressions.length isnt 0 36 | for expression in node.expressions 37 | generated = walkAst expression 38 | generated 39 | 40 | generateTestStubs = (source) -> 41 | nodes = coffee.nodes source 42 | walkAst nodes 43 | 44 | 45 | generateTestFile = (fileName, callback) -> 46 | fs.readFile fileName, 'utf-8', (err, source) -> 47 | if err then callback 'No such file' 48 | testFileName = fileName.replace '.coffee', '_test.coffee' 49 | generatedTests = generateTestStubs source 50 | fs.writeFile "#{testFileName}", generatedTests, callback "Generated #{testFileName}" 51 | 52 | fileName = process.argv[2] 53 | 54 | unless fileName 55 | console.log 'No file specified' 56 | process.exit() 57 | 58 | generateTestFile fileName, (report) -> 59 | console.log report -------------------------------------------------------------------------------- /11/listings/11.5.coffee: -------------------------------------------------------------------------------- 1 | window.onload = -> 2 | status = document.querySelector '#status' 3 | status.style.width = '640px' 4 | status.style.height = '480px' 5 | canvas = document.createElement 'canvas' 6 | canvas.width = '640' 7 | canvas.height = '480' 8 | status.appendChild canvas 9 | context = canvas.getContext '2d' 10 | 11 | drawTitle = (title) -> 12 | context.font = 'italic 20px sans-serif' 13 | context.fillText title 14 | 15 | drawGraph = (buffer) -> 16 | canvas.width = canvas.width #A 17 | context.fillStyle = 'black' 18 | context.clearRect 0, 0, 640, 480 19 | context.fillRect 0, 0, 640, 480 20 | context.lineWidth = 2 21 | context.strokeStyle = '#5AB946' 22 | context.beginPath() 23 | prev = 0 24 | for y, x in buffer 25 | unless y is prev 26 | context.lineTo 0 + x*10, 100 + y 27 | prev = y 28 | context.stroke() 29 | 30 | 31 | render = (buffer) -> 32 | drawGraph buffer 33 | drawTitle 'Server Dashboard' 34 | 35 | nextCallbackId = do -> 36 | callbackId = 0 37 | -> callbackId = callbackId + 1 38 | 39 | nextCallbackName = -> 40 | "callback#{nextCallbackId()}" 41 | 42 | fetch = (src, callback) -> 43 | head = document.querySelector 'head' 44 | script = document.createElement 'script' 45 | ajaxCallbackName = nextCallbackName() 46 | window[ajaxCallbackName] = (data) -> 47 | callback data 48 | script.src = src + "?callback=#{ajaxCallbackName}" 49 | head.appendChild script 50 | 51 | seconds = (n) -> 52 | 1000*n 53 | 54 | framesPerSecond = (n) -> 55 | (seconds 1)/n 56 | 57 | makeUpdater = (buffer = []) -> 58 | bufferRenderer = (json) -> 59 | buffer.push (JSON.parse json).hits 60 | if buffer.length is 22 then buffer.shift() 61 | render buffer 62 | 63 | -> 64 | window.setInterval -> 65 | fetch '/feed.json', bufferRenderer 66 | , framesPerSecond 1 67 | 68 | updater = makeUpdater() 69 | updater() 70 | -------------------------------------------------------------------------------- /4/listings/4.2.coffee: -------------------------------------------------------------------------------- 1 | phonebook = 2 | numbers: #A 3 | hannibal: '555-5551' #A 4 | darth: '555-5552' #A 5 | hal9000: 'disconnected' #A 6 | freddy: '555-5554' #A 7 | 'T-800': '555-5555' #A 8 | list: -> #B 9 | "#{name}: #{number}" for name, number of @numbers #B 10 | add: (name, number) -> #B 11 | if not (name of @numbers) #B 12 | @numbers[name] = number #B 13 | get: (name) -> #B 14 | if name of @numbers #B 15 | "#{name}: #{@numbers[name]}" #B 16 | else #B 17 | "#{name} not found" #B 18 | 19 | 20 | ### uncomment this to run from command line 21 | console.log "Phonebook. Commands are add get list and exit." 22 | 23 | process.stdin.setEncoding 'utf8' #C 24 | stdin = process.openStdin() #C 25 | 26 | stdin.on 'data', (chunk) -> #D 27 | args = chunk.split(' ') 28 | command = args[0].trim() #E 29 | name = args[1].trim() if args[1] #E 30 | number = args[2].trim() if args[2] #E 31 | switch command #F 32 | when 'add' #F 33 | res = phonebook.add(name, number) if name and number #F 34 | console.log res #F 35 | when 'get' #F 36 | console.log phonebook.get(name) if name #F 37 | when 'list' #F 38 | console.log phonebook.list() 39 | when 'exit' 40 | process.exit 1 41 | ### 42 | 43 | exports.phonebook = phonebook -------------------------------------------------------------------------------- /13/listings/13.5.coffee: -------------------------------------------------------------------------------- 1 | throw new Error 'Proxy required' unless Proxy? 2 | 3 | require 'harmony-reflect' 4 | 5 | class Formulaic 6 | 7 | constructor: (@root, @selector, @http, @url) -> 8 | @source = @root.querySelector @selector 9 | @handler = 10 | get: (target, property) -> 11 | target[property]?.value 12 | set: (target, property, value) => 13 | if @valid property then @sync() 14 | @fields = new Proxy @source, @handler 15 | 16 | valid: (property) -> 17 | property isnt '' 18 | 19 | addField: (field, value) -> 20 | throw new Error "Can't append to DOM" unless @source.appendChild? 21 | 22 | newField = @root.createElement 'input' 23 | newField.value = value 24 | @source.appendChild newField 25 | 26 | sync: -> 27 | throw new Error 'No HTTP specified' unless @http? and @url? 28 | 29 | @http.post @url, JSON.stringify(@source), (response) => #B 30 | for field, fieldResponse of JSON.parse response 31 | if field of @source 32 | @source[field].value = fieldResponse.value 33 | else 34 | @addField field, fieldResponse.value 35 | 36 | 37 | #toJSON: -> 38 | # JSON.stringify @fields 39 | # {'a': 'b'} 40 | # inline specs ftw 41 | 42 | # how to sync from form back to server? 43 | 44 | assert = require 'assert' 45 | 46 | obj = 47 | a: 48 | value: 'A' 49 | b: 50 | value: 'B' 51 | 52 | # console.log JSON.stringify(obj) 53 | 54 | root = 55 | createElement: () -> 56 | querySelector: -> obj 57 | 58 | fakeHttp = 59 | post: (url, json, callback) -> 60 | otherObj = 61 | a: 62 | value: 'changed A' 63 | b: 64 | value: 'changed B' 65 | c: 66 | value: 'C' 67 | 68 | callback JSON.stringify(otherObj) 69 | 70 | 71 | f = new Formulaic root, "#a", fakeHttp, 'www.agtron.co' 72 | 73 | # assert.equal f.fields.z, 'get' 74 | # f.fields.rr = 2 75 | # assert.equal f.fields.rr, 'get' 76 | 77 | # console.log f.fields.a 78 | 79 | assert.equal f.fields.a, 'A' 80 | 81 | assert.equal f.fields.d, null 82 | 83 | f.fields.t = 'k' -------------------------------------------------------------------------------- /7/listings/server.coffee: -------------------------------------------------------------------------------- 1 | 2 | http = require 'http' 3 | fs = require 'fs' 4 | coffee = require 'coffee-script' 5 | 6 | render = (res, head, body) -> 7 | res.writeHead 200, 'Content-Type': 'text/html' 8 | res.end """ 9 | 10 | 11 | 12 | 13 | Chapter 7 14 | #{head} 15 | 16 | 17 | #{body} 18 | 19 | 20 | """ 21 | 22 | listing = (id) -> 23 | markup = 24 | 1: """ 25 | """ 27 | 2: """ 28 | """ 30 | 3: """ 31 | 32 | 33 | 34 | 35 | 36 | 37 |
    TeamPointsScoredConceded
    """ 38 | 4: """ 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    TeamPointsScoredConceded
    """ 46 | script = 47 | 1: "" 48 | 2: "" 49 | 3: "" 50 | 4: "" 51 | 52 | head: script[id], body: markup[id] 53 | 54 | routes = {} 55 | 56 | for n in [1..6] 57 | do -> 58 | listing_number = n 59 | routes["/#{listing_number}"] = (res) -> 60 | render res, listing(listing_number).head, listing(listing_number).body 61 | routes["/#{listing_number}.js"] = (res) -> 62 | script res, listing_number 63 | 64 | 65 | server = http.createServer (req, res) -> 66 | handler = routes[req.url] or (res) -> res.end 'nothing here' 67 | handler res 68 | 69 | script = (res, listing) -> 70 | res.writeHead 200, 'Content-Type': 'application/javascript' 71 | fs.readFile "7.#{listing}.coffee", 'utf-8', (e, source) -> 72 | if e then res.end "/* #{e} */" 73 | else res.end coffee.compile source 74 | 75 | 76 | server.listen 8080, '127.0.0.1' 77 | -------------------------------------------------------------------------------- /12/listings/12.12.coffee: -------------------------------------------------------------------------------- 1 | 2 | # This needs to be in a Cakefile. 3 | # See the final Cakefile for a complete listing 4 | # of all the tasks for the application 5 | 6 | compile (directory) = -> #A 7 | coffee = spawn 'coffee', ['-c', '-o', "compiled/#{directory}", directory] #A 8 | #A 9 | coffee.on 'exit', (code) -> #A 10 | console.log 'Build complete' #A 11 | 12 | clean = (path, callback) -> #B 13 | exec "rm -rf #{path}", -> callback?() #B 14 | 15 | forAllSpecsIn = (dir, fn) -> #C 16 | execFile 'find', [ dir ], (err, stdout, stderr) -> #C 17 | fileList = stdout.split '\n' #C 18 | for file in fileList #C 19 | fn file if /_spec.js$/.test file #C 20 | 21 | runSpecs = (folder) -> #D 22 | forAllSpecsIn folder, (file) -> #D 23 | require "./#{file}" #D 24 | 25 | task 'build', 'Compile the application', -> #E 26 | clean 'compiled', -> #E 27 | compile 'app', -> #E 28 | 'Build complete' #E 29 | 30 | task 'test' , 'Run the tests', -> #F 31 | clean 'compiled', -> #F 32 | compile 'app', -> #F 33 | compile 'spec', -> #F 34 | runSpecs 'compiled', -> #F 35 | console.log 'Tests complete' #F 36 | #A Compile function that takes a directory from which it should compile files 37 | #B A clean function that deletes a directory 38 | #C Invoke a function for all the _spec.js files in a directory 39 | #D Run the specs by passing a function that requires a test to forAllSpecsIn 40 | #E The build task cleans then compiles 41 | #F The test task cleans and compiles the app, then cleans and compiles the tests, then runs the tests 42 | -------------------------------------------------------------------------------- /9/listings/9.1.timed.dsd.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | http = require 'http' 3 | 4 | readFile = (file, strategy) -> 5 | fs.readFile file, 'utf-8', (error, response) -> 6 | throw error if error 7 | strategy response 8 | 9 | readFileAsArray = (file, delimiter, callback) -> 10 | asArray = (data) -> 11 | callback data.split(delimiter).slice(0,-1) 12 | readFile(file, asArray) 13 | 14 | lastName = (s) -> 15 | s.split(/\s+/g)[1].replace /,/, ',' 16 | 17 | decorateSortUndecorate = (array, sort_rule) -> 18 | decorate = (array) -> 19 | {original: item, sortOn: sortRule item} for item in array 20 | 21 | undecorate = (array) -> 22 | item.original for item in array 23 | 24 | comparator = (left, right) -> 25 | if left.sortOn > right.sortOn 26 | 1 27 | else 28 | -1 29 | 30 | decorated = decorate array 31 | sorted = decorated.sort comparator 32 | undecorate sorted 33 | 34 | 35 | sortedCompetitorsFromFile = (fileName, callback) -> 36 | newline = /\n/gi 37 | readFileAsArray fileName, newline, (array) -> 38 | callback decorateSortUndecorate(array, lastName) 39 | 40 | makeServer = -> 41 | responseData = '' 42 | server = http.createServer (request, response) -> 43 | response.writeHead 200, 'Content-Type': 'text/html' 44 | response.end JSON.stringify responseData 45 | server.listen 8888, '127.0.0.1' 46 | (data) -> 47 | responseData = data 48 | 49 | main = (fileName) -> 50 | server = makeServer() 51 | 52 | loadData = -> 53 | start = new Date() 54 | console.log 'Loading data' 55 | sortedCompetitorsFromFile fileName, (data) -> 56 | elapsed = new Date() - start 57 | console.log "Data loaded in #{elapsed/1000} seconds" 58 | server data 59 | 60 | 61 | loadData() 62 | fs.watchFile fileName, loadData 63 | 64 | 65 | start = new Date() 66 | setInterval -> 67 | console.log "Clock tick at #{(new Date()-start)/1000} seconds" 68 | , 1000 69 | 70 | 71 | if process.argv[2] 72 | main process.argv[2] 73 | console.log "Starting server on port 8888" 74 | else 75 | console.log "usage: coffee 9.1.coffee [file]" 76 | 77 | -------------------------------------------------------------------------------- /exercises/7.2.5.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | 3 | # Given an array of numbers such as [1,2,3,4,5,6], 4 | # write a function that uses destructuring and a comprehension 5 | # to reverse each subsequent pair of numbers in the array so that, 6 | # for example, [1,2,3,4,5] becomes [2,1,4,3,6,5] and 7 | # [1,2,1,2,1,2] becomes [2,1,2,1,2,1]. 8 | 9 | swapPairs = (array) -> 10 | for index in array by 2 11 | [first, second] = array[index-1..index] 12 | [second, first] 13 | 14 | # Almost! 15 | swapPairs([3,4,3,4,3,4]) 16 | # [ [ 4, 3 ], [ 4, 3 ], [ 4, 3 ] ] 17 | 18 | swapPairs([1,2,3,4,5,6]) 19 | # [ [ 2, 1 ], [ 4, 3 ], [ 6, 5 ] ] 20 | 21 | swapPairs = (array) -> 22 | reversedPairs = for index in array by 2 23 | [first, second] = array[index-1..index] 24 | [second, first] 25 | [].concat reversedPairs... 26 | 27 | swapPairs([3,4,3,4,3,4]) 28 | # [ 4, 3, 4, 3, 4, 3 ] 29 | 30 | swapPairs([1,2,3,4,5,6]) 31 | # [ 2, 1, 4, 3, 6, 5 ] 32 | 33 | assert.deepEqual swapPairs([1,2,3,4,5,6]), [2,1,4,3,6,5] 34 | assert.deepEqual swapPairs([1,2,1,2,1,2]), [2,1,2,1,2,1] 35 | 36 | # Suppose you have received some JSON representing a phone directory 37 | 38 | phoneDirectory = { 39 | "A": [ 40 | {"name": "Fred", "phone": "555 1111"}, 41 | {"name": "Bill", "phone": "555 1112"} 42 | ] 43 | "B": [ 44 | {"name": "Sam", "phone": "555 1113"} 45 | ] 46 | } 47 | 48 | # Equivalent to this Coffeescript 49 | 50 | phoneDirectory = 51 | A: [ 52 | name: 'Abe' 53 | phone: '555 1110' 54 | , 55 | name: 'Andy' 56 | phone: '555 1111' 57 | , 58 | name: 'Alice' 59 | phone: '555 1112' 60 | ] 61 | B: [ 62 | name: 'Bam' 63 | phone: '555 1113' 64 | ] 65 | 66 | # Write a function that produces the last phone number for a given letter found in the phone directory. 67 | 68 | lastNumberForLetter = (letter, directory) -> 69 | [..., lastForLetter] = directory[letter] 70 | {phone} = lastForLetter 71 | phone 72 | 73 | 74 | assert.equal lastNumberForLetter('A', phoneDirectory), '555 1112' 75 | assert.equal lastNumberForLetter('B', phoneDirectory), '555 1113' 76 | -------------------------------------------------------------------------------- /11/listings/11.4.coffee: -------------------------------------------------------------------------------- 1 | window.onload = -> 2 | status = document.querySelector '#status' 3 | 4 | ensureBars = (number) -> #A 5 | unless (document.querySelectorAll '.bar').length >= number #A 6 | for n in [0..number] #A 7 | bar = document.createElement 'div' #A 8 | bar.className = 'bar' #A 9 | bar.style.width = '60px' #A 10 | bar.style.position = 'absolute' #A 11 | bar.style.bottom = '0' #A 12 | bar.style.background = 'green' #A 13 | bar.style.color = 'white' #A 14 | bar.style.left = "#{60*n}px" #A 15 | status.appendChild bar #A 16 | 17 | render = (buffer) -> 18 | ensureBars 20 19 | bars = document.querySelectorAll '.bar' 20 | for bar, index in bars 21 | bar.style.height = "#{buffer[index]}px" 22 | bar.innerHTML = buffer[index] || 0 23 | 24 | nextCallbackId = do -> 25 | callbackId = 0 26 | -> callbackId = callbackId + 1 27 | 28 | nextCallbackName = -> 29 | "callback#{nextCallbackId()}" 30 | 31 | fetch = (src, callback) -> 32 | head = document.querySelector 'head' 33 | script = document.createElement 'script' 34 | ajaxCallbackName = nextCallbackName() 35 | window[ajaxCallbackName] = (data) -> 36 | callback data 37 | script.src = src + "?callback=#{ajaxCallbackName}" 38 | head.appendChild script 39 | 40 | seconds = (n) -> 41 | 1000*n 42 | 43 | framesPerSecond = (n) -> 44 | (seconds 1)/n 45 | 46 | makeUpdater = (buffer = []) -> 47 | bufferRenderer = (json) -> 48 | buffer.push (JSON.parse json).hits 49 | if buffer.length is 22 then buffer.shift() 50 | render buffer 51 | 52 | -> 53 | window.setInterval -> 54 | fetch '/feed.json', bufferRenderer 55 | , framesPerSecond 1 56 | 57 | updater = makeUpdater() 58 | updater() 59 | -------------------------------------------------------------------------------- /5/listings/5.8.coffee: -------------------------------------------------------------------------------- 1 | 2 | http = (method, src, callback) -> 3 | handler = -> 4 | if @readyState is 4 and @status is 200 5 | unless @responseText is null 6 | callback JSON.parse @responseText 7 | 8 | client = new XMLHttpRequest 9 | client.onreadystatechange = handler 10 | client.open method, src 11 | client.send() 12 | 13 | get = (src, callback) -> 14 | http "GET", src, callback 15 | 16 | post = (src, callback) -> 17 | http "POST", src, callback 18 | 19 | 20 | class Product 21 | constructor: (name, info) -> 22 | @name = name 23 | @info = info 24 | @view = document.createElement 'div' 25 | @view.className = "product #{@category}" 26 | document.querySelector('.page').appendChild @view 27 | @view.onclick = => 28 | @purchase() 29 | @render() 30 | render: -> 31 | @view.innerHTML = @template() 32 | purchase: -> 33 | if @info.stock > 0 34 | post "/json/purchase/#{@category}/#{@name}", (res) => 35 | if res.status is "success" 36 | @info = res.update 37 | @render() 38 | template: => 39 | """ 40 |

    #{@name}

    41 |
    42 |
    Stock
    43 |
    #{@info.stock}
    44 |
    Specials?
    45 |
    #{@specials.join(',') || 'No'}
    46 |
    47 | """ 48 | 49 | 50 | class Camera extends Product 51 | category: 'camera' 52 | megapixels: -> @info.megapixels || "Unknown" 53 | 54 | class Skateboard extends Product 55 | category: 'skateboard' 56 | length: -> @info.length || "Unknown" 57 | 58 | class Shop 59 | constructor: -> 60 | unless Product::specials? #A 61 | Product::specials = [] #A 62 | @view = document.createElement 'div' 63 | @render() 64 | get '/json/list', (data) -> 65 | for own category of data 66 | for own name, info of data[category] 67 | if info.special? #B 68 | Product::specials.push info.special #B 69 | switch category 70 | when 'camera' 71 | new Camera name, info 72 | when 'skateboard' 73 | new Skateboard name, info 74 | render: -> 75 | @view.innerHTML = "" 76 | 77 | 78 | shop = new Shop 79 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/Cakefile: -------------------------------------------------------------------------------- 1 | 2 | fs = require 'fs' 3 | {exec, execFile} = require 'child_process' 4 | 5 | buildUtilities = require './build_utilities' #A 6 | 7 | { 8 | clean, 9 | compile, 10 | copy, 11 | createArtifact, 12 | runSpecs, 13 | runApp 14 | } = buildUtilities.fromDir './' 15 | 16 | VERSION = fs.readFileSync('./VERSION', 'utf-8') #B 17 | 18 | task 'clean', 'delete existing build', -> #C 19 | execFile "npm", ["install"], -> #C 20 | clean "compiled" #C 21 | 22 | task 'build', 'run the build', -> #D 23 | clean 'compiled', -> #D 24 | compile 'app', -> #D 25 | copy 'content', 'compiled', -> #D 26 | createArtifact 'compiled', VERSION, -> #D 27 | console.log 'Build complete' #D 28 | 29 | task 'test' , 'run the tests', -> #E 30 | clean 'compiled', -> #E 31 | compile 'app', -> #E 32 | compile 'spec', -> #E 33 | runSpecs 'compiled', -> #E 34 | console.log 'Tests complete' #E 35 | 36 | task "development:start", "start on development", -> #F 37 | runApp 'development' 38 | 39 | SERVER = require('./app/config').production.host 40 | 41 | deploy = -> 42 | console.log "Deploy..." 43 | tarOptions = ["-cvf", "artifact.#{VERSION}.tar", "compiled"] 44 | execFile "tar", tarOptions, (err, data) -> 45 | console.log '1. Created artifact' 46 | execFile 'scp', [ 47 | "artifact.#{VERSION}.tar", 48 | "#{SERVER}:~/." 49 | ], (err, data) -> 50 | console.log '2. Uploaded artifact' 51 | exec """ 52 | ssh #{SERVER} 'cd ~/; 53 | rm -rf compiled; 54 | tar -xvf artifact.#{VERSION}.tar; 55 | cd ~/compiled; 56 | NODE_ENV=production nohup node app/server.js &' & 57 | """, (err, data) -> 58 | console.log '3. Started server' 59 | console.log 'Done' 60 | 61 | task "production:deploy", "deploys the app to production", -> 62 | clean 'compiled', -> 63 | compile 'app', -> 64 | copy 'content', 'compiled', -> 65 | createArtifact 'compiled', VERSION, -> 66 | deploy() -------------------------------------------------------------------------------- /6/listings/db.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | 3 | ## This Db only persists to disk once 4 | ## per minute. You probably don't want 5 | ## to use it for important data 6 | oneMinute = -> 60000 7 | 8 | class Db 9 | noop = (args..., callback) -> 10 | callback new Error 'Sync' 11 | 12 | constructor: (file) -> 13 | set = (key, value, callback) => 14 | if !key 15 | callback? new Error 'No key' 16 | else 17 | @data[key] = value 18 | callback? null, @data[key] 19 | 20 | get = (key, callback) => 21 | if !key 22 | @all callback 23 | else if @data[key] 24 | callback null, @data[key] 25 | else 26 | callback new Error 'Not found' 27 | 28 | decr = (key, callback) -> 29 | num = parseInt @data[key], 10 30 | if !(isNaN num) 31 | num = num - 1 32 | @set key, num 33 | callback null, num 34 | else 35 | callback new Error 'Failed to decrement' 36 | 37 | all = (callback) => 38 | callback null, @data 39 | 40 | exit = (callback) => 41 | @sync() 42 | clearTimeout @syncTimeout 43 | 44 | @ready = => 45 | @get = get 46 | @set = set 47 | @decr = decr 48 | @all = all 49 | @exit = exit 50 | @onreadyCallback?() 51 | @onreadyCallback = null 52 | 53 | @onready = (callback) -> 54 | @onreadyCallback = callback 55 | 56 | @suspended = => 57 | @get = noop 58 | @set = noop 59 | @decr = noop 60 | @all = noop 61 | @exit = noop 62 | 63 | @sync = (callback) => 64 | @suspended() 65 | console.log "About to write file #{file}" 66 | fs.writeFile file, (JSON.stringify @data), => 67 | console.log "Wrote #{JSON.stringify(@data)}" 68 | callback?() 69 | @ready() 70 | 71 | fs.readFile file, 'utf8', (error, data) => 72 | console.log "Read file #{file}" 73 | @data = (JSON.parse data) || Object.create null 74 | console.log "Read #{JSON.stringify(@data)}" 75 | @syncTimeout = setTimeout @sync, oneMinute() 76 | @ready() 77 | 78 | 79 | users = new Db './users.json' 80 | products = new Db './products.json' 81 | stock = new Db './stock.json' 82 | 83 | exports.Db = Db 84 | exports.users = users 85 | exports.products = products 86 | exports.stock = stock -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/app/controllers/controller.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var Controller, 4 | __slice = [].slice; 5 | 6 | Controller = (function() { 7 | var routes; 8 | 9 | routes = {}; 10 | 11 | Controller.route = function(path, method) { 12 | return routes[path] = method; 13 | }; 14 | 15 | function Controller(server) { 16 | var _this = this; 17 | server.on('request', function(request, response) { 18 | var handler, handlers, method, path, route, url, _ref, _ref1, _ref2; 19 | url = require('url').parse(request.url); 20 | path = url.pathname; 21 | if (url.query) { 22 | path += "?" + url.query; 23 | } 24 | handlers = []; 25 | for (route in routes) { 26 | handler = routes[route]; 27 | if (new RegExp("^" + route + "$").test(path)) { 28 | handlers.push({ 29 | handler: handler, 30 | matches: path.match(new RegExp("^" + route + "$")) 31 | }); 32 | } 33 | } 34 | method = ((_ref = handlers[0]) != null ? _ref.handler : void 0) || 'default'; 35 | if ((_ref1 = handlers[0]) != null ? _ref1.matches : void 0) { 36 | return response.end(_this[method].apply(_this, [request, response].concat(__slice.call((_ref2 = handlers[0]) != null ? _ref2.matches.slice(1) : void 0)))); 37 | } else { 38 | response.writeHead(404, { 39 | 'Content-Type': 'text/html' 40 | }); 41 | return response.end('404'); 42 | } 43 | }); 44 | } 45 | 46 | Controller.prototype.render = function(view, mime) { 47 | if (mime == null) { 48 | mime = 'text/html'; 49 | } 50 | this.response.writeHead(200, { 51 | 'Content-Type': mime 52 | }); 53 | return this.response.end(view.render()); 54 | }; 55 | 56 | Controller.prototype["default"] = function(request, response) { 57 | this.request = request; 58 | this.response = response; 59 | return this.render({ 60 | render: function() { 61 | return 'unknown'; 62 | } 63 | }); 64 | }; 65 | 66 | return Controller; 67 | 68 | })(); 69 | 70 | exports.Controller = Controller; 71 | 72 | }).call(this); 73 | -------------------------------------------------------------------------------- /5/listings/5.2.coffee: -------------------------------------------------------------------------------- 1 | http = (method, src, callback) -> #A 2 | handler = -> #A 3 | if @readyState is 4 and @status is 200 #A 4 | unless @responseText is null #A 5 | callback JSON.parse @responseText #A 6 | #A 7 | client = new XMLHttpRequest #A 8 | client.onreadystatechange = handler #A 9 | client.open method, src #A 10 | client.send() #A 11 | 12 | get = (src, callback) -> #B 13 | http "GET", src, callback #B 14 | 15 | post = (src, callback) -> #C 16 | http "POST", src, callback #C 17 | 18 | class Camera #D 19 | constructor: (name, info) -> #D 20 | @name = name #D 21 | @info = info #D 22 | @view = document.createElement 'div' #D 23 | @view.className = "camera" #D 24 | document.querySelector('.page').appendChild @view #D 25 | @view.onclick = => #D 26 | @purchase() #D 27 | @render() #D 28 | 29 | render: -> #D 30 | @view.innerHTML = """ 31 | #{@name} 32 |
    33 | (#{@info.stock} stock) 34 | """ #D 35 | 36 | purchase: -> #D 37 | if @info.stock > 0 #D 38 | post "/json/purchase/camera/#{@name}", (res) => #D 39 | if res.status is "success" #D 40 | @info = res.update #D 41 | @render() #D 42 | 43 | class Shop #E 44 | constructor: -> #E 45 | get '/json/list/camera', (data) -> #E 46 | for own name, info of data #E 47 | new Camera name, info #E 48 | 49 | shop = new Shop 50 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/compiled/spec/client/comments_spec.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Comments, describe, it, _ref; 3 | 4 | _ref = require('chromic'), describe = _ref.describe, it = _ref.it; 5 | 6 | Comments = require('../../app/client/comments').Comments; 7 | 8 | describe('Comments', function() { 9 | it('should post a comment to the server', function() { 10 | var comment, comments, http_request, requested; 11 | requested = false; 12 | http_request = function(url) { 13 | return requested = url; 14 | }; 15 | comments = new Comments('http://the-url', {}, http_request); 16 | comment = 'Hey Agtron. Nice site.'; 17 | comments.post(comment); 18 | return requested.should_be("http://the-url/comments?insert=" + comment); 19 | }); 20 | it('should fetch the comments when constructed', function() { 21 | var comments, http_request, requested; 22 | requested = false; 23 | http_request = function(url) { 24 | return requested = url; 25 | }; 26 | comments = new Comments('http://the-url', {}, http_request); 27 | return requested.should_be("http://the-url/comments"); 28 | }); 29 | it('should bind to event on the element', function() { 30 | var comments, element, post_received; 31 | comments = new Comments('http://the-url', {}, function() {}); 32 | element = { 33 | querySelector: function() { 34 | return element; 35 | }, 36 | value: 'A comment from Scruffy' 37 | }; 38 | comments.bind(element, 'post'); 39 | post_received = false; 40 | comments.post = function(comment) { 41 | return post_received = comment; 42 | }; 43 | element.onpost(); 44 | return post_received.should_be(element.value); 45 | }); 46 | return it('should render comments to the page as a list', function() { 47 | var comments, out; 48 | out = { 49 | innerHTML: function(content) { 50 | var rendered_content; 51 | return rendered_content = content; 52 | } 53 | }; 54 | comments = new Comments('http://the-url', out, function() {}); 55 | comments.render('["One", "Two", "Three"]'); 56 | return out.innerHTML.should_be(""); 57 | }); 58 | }); 59 | 60 | }).call(this); 61 | -------------------------------------------------------------------------------- /5/listings/5.5.coffee: -------------------------------------------------------------------------------- 1 | http = (method, src, callback) -> 2 | handler = -> 3 | if @readyState is 4 and @status is 200 4 | unless @responseText is null 5 | callback JSON.parse @responseText 6 | 7 | client = new XMLHttpRequest 8 | client.onreadystatechange = handler 9 | client.open method, src 10 | client.send() 11 | 12 | get = (src, callback) -> 13 | http "GET", src, callback 14 | 15 | post = (src, callback) -> 16 | http "POST", src, callback 17 | 18 | 19 | 20 | class Gallery #A 21 | constructor: (@photos) -> #A 22 | render: -> #A 23 | images = for photo in @photos #A 24 | "
  • sample photo
  • " #A 25 | "" #A 26 | 27 | 28 | class Product 29 | constructor: (name, info) -> #B 30 | @name = name #B 31 | @info = info #B 32 | @view = document.createElement 'div' #B 33 | @view.className = 'product' #B 34 | document.querySelector('.page').appendChild @view #B 35 | @render() #B 36 | render: -> 37 | @view.innerHTML = "#{@name}: #{@info.stock}" 38 | 39 | 40 | class Camera extends Product 41 | constructor: (name, info) -> #C 42 | @gallery = new Gallery info.gallery #C 43 | super name, info #C 44 | @view.className += ' camera' #C 45 | render: -> 46 | @view.innerHTML = """ 47 | #{@name} (#{@info.stock}) 48 | #{@gallery.render()} 49 | """ 50 | 51 | 52 | class Shop 53 | constructor: -> 54 | @view = document.createElement 'div' 55 | document.querySelector('.page').appendChild @view 56 | document.querySelector('.page').className += ' l55' 57 | @render() 58 | get '/json/list', (data) -> 59 | for own category of data 60 | for own name, info of data[category] 61 | switch category 62 | when 'camera' 63 | new Camera name, info 64 | else 65 | new Product name, info 66 | 67 | render: () -> 68 | @view.innerHTML = "" 69 | 70 | window.onload = -> 71 | shop = new Shop 72 | -------------------------------------------------------------------------------- /11/listings/11.7.coffee: -------------------------------------------------------------------------------- 1 | 2 | Cézanne = do -> 3 | 4 | seconds = (n) -> n*1000 5 | framesPerSecond = 30 6 | tickInterval = seconds(1)/framesPerSecond 7 | 8 | circlePrototype = 9 | radius: (radius) -> 10 | @radius = radius 11 | this 12 | color: (hex) -> 13 | @hex = hex 14 | this 15 | position: (x, y) -> 16 | @x = x 17 | @y = y 18 | @context.beginPath() 19 | @context.fillStyle = @color 20 | @context.arc @x, @y, @radius, (Math.PI/180)*360, 0, true 21 | @context.closePath() 22 | @context.fill() 23 | this 24 | animatePosition: (x, y, duration) -> 25 | @frames ?= [] 26 | frameCount = Math.ceil seconds(duration)/tickInterval 27 | for n in [1..frameCount] 28 | if n is frameCount 29 | do => 30 | frame = n 31 | @frames.unshift => 32 | @position x, y 33 | else 34 | do => 35 | frame = n 36 | @frames.unshift => 37 | @position x/frameCount*frame, y/frameCount*frame 38 | 39 | scenePrototype = 40 | clear: -> 41 | @canvas.width = @width 42 | size: (width, height) -> 43 | @width = width 44 | @height = height 45 | @canvas.width = width 46 | @canvas.height = height 47 | this 48 | addElement: (element) -> 49 | @elements ?= [] 50 | @elements.push element 51 | element.context = @context 52 | startClock: -> 53 | clockTick = => 54 | @clear() 55 | for element in @elements 56 | frame = element.frames.pop() 57 | frame?() 58 | @clockInterval = window.setInterval clockTick, tickInterval 59 | 60 | createCircle: -> 61 | circle = Object.create circlePrototype 62 | @addElement circle 63 | circle 64 | 65 | RawUmber: '#826644' 66 | Viridian: '#40826d' 67 | 68 | createScene: (selector) -> 69 | scene = Object.create scenePrototype 70 | node = document.querySelector selector 71 | scene.canvas = document.createElement 'canvas' 72 | scene.context = scene.canvas.getContext '2d' 73 | node.appendChild scene.canvas 74 | scene.startClock() 75 | scene 76 | 77 | window.onload = -> 78 | scene = Cézanne 79 | .createScene('#status') 80 | .size(400, 400) 81 | 82 | circle = scene 83 | .createCircle() 84 | .radius(10) 85 | .color(Cézanne.RawUmber) 86 | .position(20, 20) 87 | 88 | circle.animatePosition 360, 360, 2 89 | -------------------------------------------------------------------------------- /9/listings/9.1.timed.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | http = require 'http' 3 | 4 | readFile = (file, strategy) -> 5 | fs.readFile file, 'utf-8', (error, response) -> 6 | throw error if error 7 | strategy response 8 | 9 | readFileAsArray = (file, delimiter, callback) -> #A 10 | asArray = (data) -> #A 11 | callback data.split(delimiter).slice(0,-1) #A 12 | readFile(file, asArray) #A 13 | 14 | compareOnLastName = (a,b) -> #B 15 | lastName = (s) -> #B 16 | s.split(/\s+/g)[1].replace /,/, ',' #B 17 | if !a or !b #B 18 | 1 #B 19 | else if lastName(a) >= lastName(b) #B 20 | 1 #B 21 | else #B 22 | -1 #B 23 | 24 | sortedCompetitorsFromFile = (fileName, callback) -> #C 25 | newline = /\n/gi #C 26 | readFileAsArray fileName, newline, (array) -> #C 27 | callback array.sort(compareOnLastName) #C 28 | 29 | makeServer = -> #D 30 | responseData = '' #D 31 | server = http.createServer (request, response) -> #D 32 | response.writeHead 200, 'Content-Type': 'text/html' #D 33 | response.end JSON.stringify responseData #D 34 | server.listen 8888, '127.0.0.1' #D 35 | (data) -> #D 36 | responseData = data #D 37 | 38 | main = (fileName) -> 39 | server = makeServer() 40 | 41 | loadData = -> 42 | start = new Date() 43 | console.log 'Loading data' 44 | sortedCompetitorsFromFile fileName, (data) -> 45 | elapsed = new Date() - start 46 | console.log "Data loaded in #{elapsed/1000} seconds" 47 | server data 48 | 49 | loadData() #E 50 | fs.watchFile fileName, loadData #E 51 | 52 | if process.argv[2] #F 53 | main process.argv[2] #F 54 | console.log "Starting server on port 8888" #F 55 | else #F 56 | console.log "usage: coffee 9.1.coffee [file]" #F 57 | -------------------------------------------------------------------------------- /7/listings/7.5.coffee: -------------------------------------------------------------------------------- 1 | 2 | http = require 'http' 3 | fs = require 'fs' 4 | coffee = require 'coffee-script' 5 | 6 | render = (res, head, body) -> 7 | res.writeHead 200, 'Content-Type': 'text/html' 8 | res.end """ 9 | 10 | 11 | 12 | 13 | Chapter 7 14 | 19 | #{head} 20 | 21 | 22 | #{body} 23 | 24 | 25 | """ 26 | 27 | listing = (id) -> 28 | markup = 29 | 1: """ 30 | """ 32 | 2: """ 33 | """ 35 | 3: """ 36 | 37 | 38 | 39 | 40 | 41 | 42 |
    TeamPointsScoredConceded
    """ 43 | 4: """ 44 | 45 | 46 | 47 | 48 | 49 | 50 |
    TeamPointsScoredConceded
    """ 51 | script = 52 | 1: "" 53 | 2: "" 54 | 3: "" 55 | 4: "" 56 | 57 | head: script[id], body: markup[id] 58 | 59 | routes = {} 60 | 61 | for n in [1..6] 62 | do -> 63 | listingNumber = n 64 | routes["/#{listingNumber}"] = (res) -> 65 | render res, listing(listingNumber).head, listing(listingNumber).body 66 | routes["/#{listingNumber}.js"] = (res) -> 67 | script res, listingNumber 68 | 69 | 70 | server = http.createServer (req, res) -> 71 | handler = routes[req.url] or (res) -> 72 | render res, '', ''' 73 | 79 | ''' 80 | handler res 81 | 82 | script = (res, listing) -> 83 | res.writeHead 200, 'Content-Type': 'application/javascript' 84 | fs.readFile "7.#{listing}.coffee", 'utf-8', (e, source) -> 85 | if e then res.end "/* #{e} */" 86 | else res.end coffee.compile source 87 | 88 | 89 | server.listen 8080, '127.0.0.1' 90 | -------------------------------------------------------------------------------- /5/listings/5.3.coffee: -------------------------------------------------------------------------------- 1 | 2 | http = (method, src, callback) -> #A 3 | handler = -> 4 | if @readyState is 4 and @status is 200 5 | unless @responseText is null 6 | callback JSON.parse @responseText 7 | 8 | client = new XMLHttpRequest 9 | client.onreadystatechange = handler 10 | client.open method, src 11 | client.send() 12 | 13 | get = (src, callback) -> #B 14 | http "GET", src, callback 15 | 16 | post = (src, callback) -> #B 17 | http "POST", src, callback 18 | 19 | 20 | class Product 21 | constructor: (name, info) -> #C 22 | @name = name #C 23 | @info = info #C 24 | @view = document.createElement 'div' #C 25 | @view.className = "product" #C 26 | document.body.appendChild @view #C 27 | @view.onclick = => #C 28 | @purchase() #C 29 | @render() #C 30 | render: -> 31 | renderInfo = (key,val) -> 32 | "
    #{key}: #{val}
    " 33 | displayInfo = (renderInfo(key, val) for own key, val of @info) #D 34 | @view.innerHTML = "#{@name} #{displayInfo.join ''}" #D 35 | purchase: -> #D 36 | if @info.stock > 0 #D 37 | post "/json/purchase/#{@purchase_category}/#{@name}", (res) => #D 38 | if res.status is "success" #D 39 | @info = res.update #D 40 | @render() #D 41 | 42 | class Camera extends Product 43 | purchaseCategory: 'camera' 44 | megapixels: -> @info.megapixels || "Unknown" 45 | 46 | 47 | class Skateboard extends Product 48 | purchaseCategory: 'skateboard' 49 | length: -> @info.length || "Unknown" 50 | 51 | 52 | class Shop #E 53 | constructor: -> #E 54 | get '/json/list', (data) -> #E 55 | for own category of data #E 56 | for own name, info of data[category] #E 57 | switch category #E 58 | when 'camera' #E 59 | new Camera name, info #E 60 | when 'skateboard' #E 61 | new Skateboard name, info #E 62 | 63 | shop = new Shop 64 | -------------------------------------------------------------------------------- /5/5.spec.coffee: -------------------------------------------------------------------------------- 1 | chapter5 = require './5' 2 | 3 | fs = require 'fs' 4 | {it, stub, describe} = require 'chromic' 5 | 6 | jsdom = require 'jsdom' 7 | 8 | 9 | evalWithDom = (html, executer) -> 10 | jsdom.env 11 | html: html 12 | done: (errors, window) -> 13 | try 14 | executer window 15 | catch e 16 | console.log e 17 | 18 | describe "chapter 5", -> 19 | 20 | data = JSON.parse '''{ 21 | "Fuji-X100": { 22 | "description": "a camera", 23 | "stock":5 24 | }, 25 | "Leica-X1": { 26 | "description": "a camera", 27 | "stock":6 28 | } 29 | }''' 30 | 31 | it "should demonstrate property access from JSON", -> 32 | data["Fuji-X100"].stock.shouldBe 5 33 | 34 | it "should demonstrate stringifying in a for comprehension", -> 35 | res = for own name, info of data 36 | "#{name}: #{info.description} (#{info.stock} in stock)" 37 | res.shouldContain "Fuji-X100: a camera (5 in stock)" 38 | 39 | it "should demonstrate rendering to HTML", -> 40 | res = for own name, info of data 41 | "
  • #{name}: #{info.description} (#{info.stock} in stock)
  • " 42 | res.shouldContain "
  • Fuji-X100: a camera (5 in stock)
  • " 43 | 44 | it "should demonstrate a way to use that to create an event handler for each", -> 45 | for own name, info of data 46 | called = false 47 | evalWithDom "", (dom) -> 48 | li = dom.document.createElement "li" 49 | li.innerHTML = "#{name}: #{info.description} (#{info.stock} in stock)" 50 | li.onclick = -> called = true 51 | dom.document.body.appendChild li 52 | #called = dom.document.getElementsByTagName("li")[0] 53 | 54 | #called.shouldBe true 55 | 56 | it "should demonstrate a camera class", -> 57 | class Camera 58 | constructor: (name, info) -> 59 | @name = name 60 | @info = info 61 | render: -> 62 | "#{@name}: #{@info.description} (#{@info.stock} in stock)" 63 | 64 | leicaX1 = new Camera "Leica-X1", { 65 | description: "An awesome camera", stock: 5 66 | } 67 | 68 | leicaX1.render().shouldBe "Leica-X1: An awesome camera (5 in stock)" 69 | 70 | it "should demonstrate extends", -> 71 | class Product 72 | class Camera extends Product 73 | constructor: -> 74 | super 75 | 76 | it "should demonstrate extending a built in", -> 77 | Date::daysFromToday = -> 78 | millisecondsInDay = 86400000 79 | today = Date.now() 80 | diff = @ - today 81 | Math.floor diff/millisecondsInDay 82 | 83 | christmas = new Date "December 25, 2012 00:00" 84 | 85 | Date.now = -> christmas 86 | 87 | christmas.daysFromToday().shouldBe 0 88 | -------------------------------------------------------------------------------- /9/listings/9.3.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | {EventEmitter} = require 'events' 3 | 4 | ONE_SECOND = 1000 5 | 6 | lastName = (s) -> 7 | try 8 | s.split(/\s+/g)[1].replace /,/, ',' 9 | catch e 10 | '' 11 | 12 | undecorate = (array) -> 13 | item.original for item in array 14 | 15 | class CompetitorsEmitter extends EventEmitter 16 | 17 | validCompetitor = (string) -> #A 18 | /^[0-9]+:\s[a-zA-Z],\s[a-zA-Z]\n/.test string #A 19 | 20 | lines = (data) -> #B 21 | chunk = data.split /\n/ #B 22 | first = chunk[0] #B 23 | last = chunk[chunk.length-1] #B 24 | {chunk, first, last} #B 25 | 26 | insertionSort = (array, items) -> #C 27 | insertAt = 0 #C 28 | for item in items #C 29 | toInsert = original: item, sortOn: lastName(item) #C 30 | for existing in array #C 31 | if toInsert.lastName > existing.lastName #C 32 | insertAt++ #C 33 | array.splice insertAt, 0, toInsert #C 34 | 35 | constructor: (source) -> #D 36 | @competitors = [] #D 37 | stream = fs.createReadStream source, {flags: 'r', encoding: 'utf-8'} #D 38 | stream.on 'data', (data) => #D 39 | {chunk, first, last} = lines data #D 40 | if not validCompetitor last #D 41 | @remainder = last #D 42 | chunk.pop() #D 43 | if not validCompetitor first #D 44 | chunk[0] = @remainder + first #D 45 | insertionSort @competitors, chunk #D 46 | @emit 'data', @competitors #D 47 | 48 | 49 | path = require 'path' 50 | if !fs.existsSync 'competitors.15000.txt' 51 | console.error 'Error: File competitors.15000.txt not found' 52 | process.exit() 53 | 54 | competitors = new CompetitorsEmitter 'competitors.15000.txt' 55 | competitors.on 'data', (competitors) -> 56 | console.log "There are #{competitors.length} competitors" 57 | 58 | start = new Date() 59 | setInterval -> 60 | now = new Date() 61 | console.log "Tick at #{(now - start)/ONE_SECOND}" 62 | , ONE_SECOND/10 63 | -------------------------------------------------------------------------------- /11/listings/handlers.coffee: -------------------------------------------------------------------------------- 1 | 2 | fs = require 'fs' 3 | url = require 'url' 4 | coffee = require 'coffee-script' 5 | 6 | sections = ["11.2", "11.4", "11.5", "11.6", "11.7"] 7 | 8 | render = (body, section) -> 9 | """ 10 | 11 | 12 | 13 | 14 | The graph! 15 | 18 | 19 | 20 | 21 |
    22 | 23 | 24 | """ 25 | 26 | x = 0 27 | 28 | increment = (callback) -> 29 | x = x + 1 30 | callback() 31 | 32 | setInterval -> 33 | x = Math.floor Math.random()*100 34 | , 1000 35 | 36 | ok = (response, type='text/plain') -> 37 | response.writeHead 200, 'Content-Type': type + '; charset=utf-8' 38 | 39 | fail = (response, type='text/plain') -> 40 | response.writeHead 500, 'Content-Type': type + '; charset=utf8' 41 | 42 | js = (request, response) -> 43 | file = (url.parse(request.url).path.split /\//)[1] 44 | coffeeFile = file.replace '.js', '.coffee' 45 | fs.readFile "./#{coffeeFile}", 'utf8', (error, data) -> 46 | if error 47 | fail response 48 | response.end "/* #{error.toString()} */" 49 | else 50 | ok response 51 | response.end (coffee.compile data) 52 | 53 | views = (request, response) -> 54 | query = (url.parse request.url).query 55 | if query 56 | params = (url.parse request.url).query.split '&' 57 | sectionQuery = (params.filter (param) -> /^section=/.test params)[0] 58 | section = sectionQuery.replace 'section=', '' 59 | ok response, 'text/html' 60 | response.end render 'main view', section 61 | else 62 | ok response, 'text/html' 63 | sectionsMarkup = sections.map (section) -> 64 | "
  • #{section}
  • " 65 | response.end "" 66 | 67 | incr = (request, response) -> 68 | complete = -> 69 | ok response 70 | response.end 'incremented to ' + x 71 | increment complete 72 | 73 | callbackName = (s) -> 74 | result = /callback=([a-z0-9_]+)/gi.exec s 75 | if result and result.length is 2 76 | result[1] 77 | else 78 | null 79 | 80 | feed = (request, response) -> 81 | {query} = url.parse request.url 82 | callback = callbackName query 83 | ok response 84 | if callback 85 | response.end """ 86 | #{callback}('{\"hits\":#{x}}'); 87 | """ 88 | else 89 | response.end "{\"hits\":#{x}}" 90 | 91 | exports.incr = incr 92 | exports.views = views 93 | sections.forEach (section) -> 94 | exports["#{section}.js"] = js 95 | exports['feed.json'] = feed 96 | -------------------------------------------------------------------------------- /6/listings/6.4.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | db = (require './db').stock 3 | 4 | stock = 30 #A 5 | serverOne = http.createServer (req, res) -> #A 6 | response = switch req.url #A 7 | when '/purchase' #A 8 | res.writeHead 200, 'Content-Type': 'text/plain;charset=utf8' #A 9 | if stock > 0 #A 10 | stock = stock-1 #A 11 | "Purchased! There are #{stock} left." #A 12 | else #A 13 | 'Sorry! no stock left!' #A 14 | else #A 15 | res.writeHead 404, 'Content-Type': 'text/plain;charset=utf8' #A 16 | 'Go to /purchase' #A 17 | res.end response #A 18 | 19 | 20 | 21 | serverTwo = http.createServer (req, res) -> #B 22 | purchase = (callback) -> #B 23 | db.decr 'stock', (error, response) -> #B 24 | if error #B 25 | callback 0 #B 26 | else #B 27 | callback response #B 28 | 29 | render = (stock) -> #B 30 | res.writeHead 200, 'Content-Type': 'text/plain;charset=utf8' #B 31 | response = if stock > 0 #B 32 | "Purchased! There are #{stock} left." #B 33 | else #B 34 | 'Sorry! no stock left' #B 35 | res.end response #B 36 | 37 | switch req.url #B 38 | when '/purchase' #B 39 | purchase render #B 40 | else #B 41 | res.writeHead 404, 'Content-Type': 'text/plain;charset=utf8' #B 42 | res.end 'Go to /purchase' #B 43 | 44 | serverOne.listen 9091, '127.0.0.1' 45 | serverTwo.listen 9092, '127.0.0.1' 46 | 47 | #A Your program keeping state 48 | #B The database keeping state 49 | -------------------------------------------------------------------------------- /5/listings/5.12.coffee: -------------------------------------------------------------------------------- 1 | server = 2 | http: (method, src, callback) -> 3 | handler = -> 4 | if @readyState is 4 and @status is 200 5 | unless @responseText is null 6 | callback JSON.parse @responseText 7 | 8 | client = new XMLHttpRequest 9 | client.onreadystatechange = handler 10 | client.open method, src 11 | client.send() 12 | 13 | get: (src, callback) -> 14 | @http "GET", src, callback 15 | 16 | post: (src, callback) -> 17 | @http "POST", src, callback 18 | 19 | class View #A 20 | @:: = null #A 21 | @include = (to, className) => #A 22 | for key, val of @ #A 23 | to::[key] = val #A 24 | @handler = (event, fn) -> #A 25 | @node[event] = fn #A 26 | @update = -> #A 27 | unless @node? #A 28 | @node = document.createElement 'div' #A 29 | @node.className = @constructor.name.toLowerCase() #A 30 | document.querySelector('.page').appendChild @node #A 31 | @node.innerHTML = @template() #A 32 | 33 | 34 | class Product 35 | View.include @ #B 36 | products = [] 37 | @find = (query) -> 38 | (product for product in products when product.name is query) 39 | constructor: (@name, @info) -> 40 | products.push @ 41 | @template = => #C 42 | """ 43 | #{@name} 44 | """ 45 | @update() 46 | @handler "onclick", @purchase 47 | purchase: => 48 | if @info.stock > 0 49 | server.post "/json/purchase/#{@category}/#{@name}", (res) => 50 | if res.status is "success" 51 | @info = res.update 52 | @update() 53 | 54 | class Camera extends Product 55 | category: 'camera' 56 | megapixels: -> @info.megapixels || "Unknown" 57 | 58 | 59 | class Skateboard extends Product 60 | category: 'skateboard' 61 | length: -> @info.length || "Unknown" 62 | 63 | 64 | class Shop 65 | View.include @ #D 66 | constructor: -> 67 | @template = -> #E 68 | "

    News: #{@breakingNews}

    " 69 | 70 | server.get '/json/news', (news) => 71 | @breakingNews = news.breaking 72 | @update() 73 | 74 | server.get '/json/list', (data) -> 75 | for own category of data 76 | for own name, info of data[category] 77 | switch category 78 | when 'camera' 79 | new Camera name, info 80 | when 'skateboard' 81 | new Skateboard name, info 82 | 83 | 84 | shop = new Shop 85 | -------------------------------------------------------------------------------- /exercises/5.8.1/client.coffee: -------------------------------------------------------------------------------- 1 | server = 2 | http: (method, src, callback) -> 3 | handler = -> 4 | if @readyState is 4 and @status is 200 5 | unless @responseText is null 6 | callback JSON.parse @responseText 7 | 8 | client = new XMLHttpRequest 9 | client.onreadystatechange = handler 10 | client.open method, src 11 | client.send() 12 | 13 | get: (src, callback) -> 14 | @http "GET", src, callback 15 | 16 | post: (src, callback) -> 17 | @http "POST", src, callback 18 | 19 | class View #A 20 | @:: = null #A 21 | @include = (to, className) => #A 22 | for key, val of @ #A 23 | to::[key] = val #A 24 | @handler = (event, fn) -> #A 25 | @node[event] = fn #A 26 | @update = -> #A 27 | unless @node? #A 28 | @node = document.createElement 'div' #A 29 | @node.className = @constructor.name.toLowerCase() #A 30 | document.querySelector('.page').appendChild @node #A 31 | @node.innerHTML = @template() #A 32 | 33 | 34 | class Product 35 | View.include @ #B 36 | products = [] 37 | @find = (query) -> 38 | (product for product in products when product.name is query) 39 | constructor: (@name, @info) -> 40 | products.push @ 41 | @template = => #C 42 | """ 43 | #{@name} 44 | """ 45 | @update() 46 | @handler "onclick", @purchase 47 | purchase: => 48 | if @info.stock > 0 49 | server.post "/json/purchase/#{@category}/#{@name}", (res) => 50 | if res.status is "success" 51 | @info = res.update 52 | @update() 53 | 54 | class Camera extends Product 55 | category: 'camera' 56 | megapixels: -> @info.megapixels || "Unknown" 57 | 58 | 59 | class Skateboard extends Product 60 | category: 'skateboard' 61 | length: -> @info.length || "Unknown" 62 | 63 | 64 | class Shop 65 | View.include @ #D 66 | constructor: -> 67 | @template = -> #E 68 | "

    News: #{@breakingNews}

    " 69 | 70 | server.get '/json/news', (news) => 71 | @breakingNews = news.breaking 72 | @update() 73 | 74 | server.get '/json/list', (data) -> 75 | for own category of data 76 | for own name, info of data[category] 77 | switch category 78 | when 'camera' 79 | new Camera name, info 80 | when 'skateboard' 81 | new Skateboard name, info 82 | 83 | 84 | shop = new Shop 85 | -------------------------------------------------------------------------------- /12/listings/blog.agtron.co/build_utilities.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | {spawn, exec, execFile, fork} = require 'child_process' #A 3 | 4 | clientCompiled = false 5 | 6 | forAllSpecsIn = (dir, fn) -> #B 7 | execFile 'find', [ dir ], (err, stdout, stderr) -> #B 8 | fileList = stdout.split '\n' #B 9 | for file in fileList #B 10 | fn file if /_spec.js$/.test file #B 11 | 12 | compileClient = (callback) -> 13 | return callback() if clientCompiled #C 14 | clientCompiled = true #C 15 | compiler = require 'coffee-script' #C 16 | modules = fs.readFileSync "lib/modules.coffee", "utf-8" #C 17 | modules = compiler.compile modules, bare: true #C 18 | files = fs.readdirSync 'client' #C 19 | 20 | fs.mkdirSync "compiled/app/client" 21 | source = (for file in files when /\.coffee$/.test file #C 22 | module = file.replace /\.coffee/, '' #C 23 | fileSource = fs.readFileSync "client/#{file}", "utf-8" #C 24 | fs.writeFileSync "compiled/app/client/#{module}.js", compiler.compile fileSource 25 | """ 26 | defmodule({#{module}: function (require, exports) { 27 | #{compiler.compile(fileSource, bare: true)} 28 | }}); 29 | """ #C 30 | ).join '\n\n' #C 31 | 32 | out = modules + '\n\n' + source #C 33 | fs.writeFileSync 'compiled/app/client/application.js', out #C 34 | 35 | callback?() 36 | 37 | exports.fromDir = (root) -> 38 | 39 | return unless root 40 | 41 | compile = (path, callback) -> 42 | coffee = spawn 'coffee', ['-c', '-o', "#{root}compiled/#{path}", path] 43 | 44 | coffee.on 'exit', (code, s) -> 45 | if code is 0 then compileClient callback 46 | else console.log 'error compiling' 47 | 48 | coffee.on 'message', (data) -> 49 | console.log data 50 | 51 | createArtifact = (path, version, callback) -> #D 52 | execFile "tar", ["-cvf", "artifact.#{version}.tar", path], (e, d) -> #D 53 | callback?() #D 54 | 55 | runSpecs = (folder) -> 56 | forAllSpecsIn "#{root}#{folder}", (file) -> 57 | require "./#{file}" 58 | 59 | clean = (path, callback) -> 60 | exec "rm -r #{root}#{path}", (err) -> callback?() 61 | 62 | copy = (src, dst, callback) -> 63 | exec "cp -R #{root}#{src} #{root}#{dst}/.", -> 64 | callback?() 65 | 66 | runApp = (env) -> 67 | exec 'NODE_ENV=#{env} nohup node compiled/app/server.js &', -> 68 | console.log "Running..." 69 | 70 | {clean, compile, copy, createArtifact, runSpecs, runApp} 71 | -------------------------------------------------------------------------------- /3/listings/3.7.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | http = require 'http' 3 | coffee = require 'coffee-script' #1 4 | 5 | attendees = 0 #2 6 | friends = 0 #2 7 | 8 | split = (text) -> 9 | text.split /,/g 10 | 11 | accumulate = (initial, numbers, accumulator) -> 12 | total = initial or 0 13 | for number in numbers 14 | total = accumulator total, number 15 | total 16 | 17 | sum = (accum, current) -> accum + current 18 | 19 | attendeesCounter = (data) -> #3 20 | attendees = data.split(/,/).length #3 21 | 22 | friendsCounter = (data) -> #4 23 | numbers = (parseInt(string, 0) for string in split data) #4 24 | friends = accumulate(0, numbers, sum) #4 25 | 26 | readFile = (file, strategy) -> #5 27 | fs.readFile file, 'utf-8', (error, response) -> #5 28 | throw error if error #5 29 | strategy response #5 30 | 31 | countUsingFile = (file, strategy) -> #6 32 | readFile file, strategy #6 33 | fs.watch file, (-> readFile file, strategy) #6 34 | 35 | init = -> 36 | countUsingFile 'partygoers.txt', attendeesCounter #7 37 | countUsingFile 'friends.txt', friendsCounter #7 38 | 39 | server = http.createServer (request, response) -> #8 40 | switch request.url #8 41 | when '/' #8 42 | response.writeHead 200, 'Content-Type': 'text/html' #8 43 | response.end view #8 44 | when '/count' #8 45 | response.writeHead 200, 'Content-Type': 'text/plain' #8 46 | response.end "#{attendees + friends}" #8 47 | 48 | server.listen 8080, '127.0.0.1' #9 49 | console.log 'Now running at http://127.0.0.1:8080' 50 | 51 | #10 52 | clientScript = coffee.compile ''' 53 | get = (path, callback) -> 54 | req = new XMLHttpRequest() 55 | req.onload = (e) -> callback req.responseText 56 | req.open 'get', path 57 | req.send() 58 | 59 | showAttendees = -> 60 | out = document.querySelector '#how-many-attendees' 61 | get '/count', (response) -> 62 | out.innerHTML = "#{response} attendees!" 63 | 64 | showAttendees() 65 | setInterval showAttendees, 1000 66 | ''' 67 | 68 | #11 69 | view = """ 70 | 71 | How many people are coming? 72 | 73 |
    74 | 77 | 78 | 79 | """ 80 | 81 | init() 82 | 83 | ### 84 | exports.attendeesCounter = attendeesCounter 85 | exports.friendsCounter = friendsCounter 86 | exports.readFile = readFile 87 | exports.countUsingFile = countUsingFile 88 | ### 89 | -------------------------------------------------------------------------------- /6/listings/6.2.coffee: -------------------------------------------------------------------------------- 1 | # Important: Do not write like this 2 | 3 | http = require 'http' 4 | url = require 'url' 5 | 6 | {users, products} = require './db' 7 | 8 | server = http.createServer (req, res) -> 9 | path = url.parse(req.url).path 10 | parts = path.split /\// 11 | switch parts[1] 12 | when 'profit' 13 | res.writeHead 200, 'Content-Type': 'text/plain;charset=utf-8' 14 | if parts[2] and /^[0-9]+$/gi.test parts[2] 15 | price = parts[2] 16 | profit = (50+(20/10)*(200-price))* #A 17 | price-(140+(100*(50+((20/10)*(200-price))))) #A 18 | res.end (JSON.stringify { profit: profit parts[2] }) 19 | else 20 | res.end JSON.stringify { profit: 0 } 21 | when 'user' 22 | res.writeHead 200, 'Content-Type': 'text/plain;charset=utf-8' 23 | if req.method is "GET" 24 | if parts[2] and /^[a-z]+$/gi.test parts[2] 25 | users.get parts[2], (error, user) -> 26 | res.end JSON.stringify user, 'utf8' 27 | else 28 | users.all (error, users) -> 29 | res.end JSON.stringify users, 'utf8' 30 | else if parts[2] and req.method is "POST" 31 | user = parts[2] 32 | requestBody = '' 33 | req.on 'data', (chunk) -> 34 | requestBody += chunk.toString() 35 | req.on 'end', -> 36 | pairs = requestBody.split /&/g 37 | decodedRequestBody = for pair in pairs 38 | o = {} 39 | splitPair = pair.split /\=/g 40 | o[splitPair[0]] = splitPair[1] 41 | o 42 | users.set user, decodedRequestBody, -> 43 | res.end 'success', 'utf8' 44 | else 45 | res.writeHead 404, 'Content-Type': 'text/plain;charset=utf-8' 46 | res.end '404' 47 | when 'product' 48 | res.writeHead 200, 'Content-Type': 'text/plain;charset=utf-8' 49 | if req.method is "GET" 50 | products.get parts[2], (product) -> 51 | res.end JSON.stringify product, 'utf8' 52 | else if parts[2] and req.method is "POST" 53 | product = parts[2] 54 | requestBody = '' 55 | req.on 'data', (chunk) -> 56 | requestBody += chunk.toString() 57 | req.on 'end', -> 58 | pairs = requestBody.split /&/g 59 | decodedRequestBody = for pair in pairs 60 | o = {} 61 | splitPair = pair.split /\=/g 62 | o[splitPair[0]] = splitPair[1] 63 | o 64 | product.set user, decodedRequestBody, -> 65 | res.end 'success', 'utf8' 66 | requestBody = '' 67 | req.on 'data', (chunk) -> 68 | requestBody += chunk.toString() 69 | req.on 'end', -> 70 | decodedRequestBody = requestBody 71 | res.end decodedRequestBody, 'utf8' 72 | else 73 | res.writeHead 404, 'Content-Type': 'text/plain;charset=utf-8' 74 | res.end '404' 75 | else 76 | res.writeHead 200, 'Content-Type': 'text/html;charset=utf-8' 77 | res.end 'The API' 78 | 79 | server.listen 8080, '127.0.0.1' 80 | 81 | # Important: Do not write programs like this. 82 | #A Part of the program you already addressed 83 | -------------------------------------------------------------------------------- /9/listings/9.5.coffee: -------------------------------------------------------------------------------- 1 | withEvents = (emitter, event) -> 2 | pipeline = [] 3 | data = [] 4 | 5 | reset = -> pipeline = [] 6 | 7 | run = -> 8 | result = data 9 | for processor in pipeline 10 | if processor.filter? 11 | result = result.filter processor.filter 12 | else if processor.map? 13 | result = result.map processor.map 14 | result 15 | 16 | emitter.on event, (datum) -> 17 | data.push datum 18 | 19 | filter: (filter) -> 20 | pipeline.push {filter} 21 | @ 22 | map: (map) -> 23 | pipeline.push {map} 24 | @ 25 | drain: (fn) -> #A 26 | emitter.on event, (datum) -> #A 27 | result = run() #A 28 | data = [] #A 29 | fn result #A 30 | evaluate: -> 31 | result = run() 32 | reset() 33 | result 34 | 35 | 36 | UP = 38 #B 37 | DOWN = 40 #B 38 | Q = 81 #B 39 | A = 65 #B 40 | 41 | doc = #C 42 | on: (event, fn) -> #C 43 | old = document["on#{event}"] || -> #C 44 | document["on#{event}"] = (e) -> #C 45 | old e #C 46 | fn e #C 47 | 48 | class Paddle 49 | constructor: (@top=0, @left=0) -> 50 | @render() 51 | 52 | move: (displacement) -> #D 53 | @top += displacement*5 #D 54 | @paddle.style.top = @top + 'px' #D 55 | 56 | render: -> #E 57 | @paddle = document.createElement 'div' #E 58 | @paddle.className = 'paddle' #E 59 | @paddle.style.backgroundColor = 'black' #E 60 | @paddle.style.position = 'absolute' #E 61 | @paddle.style.top = "#{@top}px" #E 62 | @paddle.style.left = "#{@left}px" #E 63 | @paddle.style.width = '20px' #E 64 | @paddle.style.height = '100px' #E 65 | document.querySelector('#pong').appendChild @paddle #E 66 | 67 | displacement = ([up,down]) -> 68 | (event) -> 69 | switch event.keyCode 70 | when up then -1 71 | when down then 1 72 | else 0 73 | 74 | move = (paddle) -> 75 | (moves) -> 76 | for displacement in moves 77 | paddle.move displacement 78 | 79 | keys = (expected) -> 80 | (pressed) -> 81 | pressed.keyCode in expected 82 | 83 | lhs = 0 84 | rhs = document.body.offsetWidth 85 | 86 | paddle1 = new Paddle 0,lhs #F 87 | paddle1.keys = [Q,A] #F 88 | 89 | paddle2 = new Paddle 0, rhs - 20 #F 90 | paddle2.keys = [UP,DOWN] #F 91 | 92 | withEvents(doc, 'keydown') #G 93 | .filter(keys paddle1.keys) #G 94 | .map(displacement paddle1.keys) #G 95 | .drain(move paddle1) #G 96 | 97 | withEvents(doc, 'keydown') #G 98 | .filter(keys paddle2.keys) #G 99 | .map(displacement paddle2.keys) #G 100 | .drain(move paddle2) #G 101 | --------------------------------------------------------------------------------