├── .gitignore ├── README.md ├── assets ├── dashboard.css └── view_file.coffee ├── command_line.coffee ├── cs_js_source_mapping.coffee ├── dashboard.coffee ├── examples ├── bot.coffee ├── chat.coffee ├── client.coffee ├── coffeekup.coffee ├── game_of_life.coffee ├── hanoi.coffee ├── knight.coffee ├── lru.coffee ├── nodes.coffee ├── rosetta_crawl.coffee ├── spine.coffee └── underscore.coffee ├── file_utils.coffee ├── list_files.coffee ├── render_file_list.coffee ├── side_by_side.coffee ├── static_website.coffee └── test ├── test.coffee └── test.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

CS/JS Code Browser

2 | 3 | This project lets you see CS and JS code side by side, with lines matched up. 4 | 5 | If you would like a tour of the tool, there is a screencast w/audio: 6 | 7 | http://www.youtube.com/watch?v=dEze_TaORJs&feature=youtu.be (running time 8:54) 8 | 9 | Or, just jump in! 10 | 11 |

Instructions

12 | 13 | 1. Download: git clone git://github.com/showell/CoffeeScriptLineMatcher.git 14 | 1. Find a directory that has .coffee and .js files in it. 15 | 1. (There's an examples directory in this repo; just run "find . -name '\*.coffee' | xargs coffee -c" to get js files.) 16 | 1. Launch the web server, supplying the directory and port number as command line parameters: "node dashboard.js . 3000" 17 | 1. View your CS and JS code in the browser. 18 | 19 |

Example Usage

20 | 21 | ``` 22 | /tmp > git clone git://github.com/showell/CoffeeScriptLineMatcher.git 23 | Cloning into CoffeeScriptLineMatcher... 24 | [snip...] 25 | 26 | /tmp > cd CoffeeScriptLineMatcher/ 27 | 28 | /tmp/CoffeeScriptLineMatcher > find . -name '*.coffee' | xargs coffee -c 29 | 30 | /tmp/CoffeeScriptLineMatcher > node dashboard.js . 3000 31 | Server running at http://localhost:3000/ 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /assets/dashboard.css: -------------------------------------------------------------------------------- 1 | pre { 2 | font-size: 13px; 3 | padding: 4px; 4 | } 5 | 6 | .numbers { 7 | color: blue; 8 | } 9 | 10 | .code { 11 | overflow: auto; 12 | } 13 | 14 | p { 15 | width: 600px; 16 | } 17 | 18 | .view_files { 19 | width: 350px; 20 | } 21 | 22 | th { 23 | text-align: left; 24 | } -------------------------------------------------------------------------------- /assets/view_file.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | do -> 3 | $win = $ window 4 | setPre = -> 5 | $('td').css width: 50 6 | $('.code').css width: ($win.width()-185) * 0.50 7 | $win.resize setPre 8 | setPre() 9 | 10 | window.onfocus = -> 11 | f = -> 12 | $.getJSON "timestamps?FILE=#{CS_FN}", (data) -> 13 | if data.cs == FINGERPRINT.cs and data.js == FINGERPRINT.js 14 | # do nothing if files didn't change 15 | else 16 | # reload the page 17 | location.reload true 18 | 19 | # In some editors, like TextMate, files get saved when you remove 20 | # focus from the editor, so we give a couple seconds for the save 21 | # to happen and for coffee -wc to wake up. 22 | setTimeout f, 2000 23 | 24 | set_up_key_mappings = -> 25 | digits = '' 26 | 27 | go_to_js_line = (digits) -> 28 | id = "js_#{digits}" 29 | elem = $ "a##{id}" 30 | if elem.length == 1 31 | elem.focus() 32 | true 33 | else 34 | alert "JS line number #{digits} does not exist" 35 | false 36 | 37 | document.onkeypress = (e) -> 38 | keyunicode = e.charCode or e.keyCode 39 | c = String.fromCharCode(keyunicode) 40 | if '0' <= c <= '9' 41 | digits += c 42 | found = go_to_js_line digits 43 | if not found then digits = '' 44 | true 45 | else if c is ' ' 46 | digits = '' 47 | false 48 | else 49 | false 50 | 51 | set_up_key_mappings() -------------------------------------------------------------------------------- /command_line.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | {source_line_mappings} = require './cs_js_source_mapping' 3 | 4 | file_lines = (fn) -> 5 | fs.readFileSync(fn).toString().split '\n' 6 | 7 | list = (cs_lines, js_lines) -> 8 | matches = source_line_mappings cs_lines, js_lines 9 | 10 | snippet = (lines, start, end, prefix) -> 11 | for ln in [start...end] 12 | line = lines[ln] 13 | console.log "#{prefix}:#{ln+1} #{line}" 14 | null 15 | 16 | s_start = 0 17 | d_start = 0 18 | for match in matches 19 | [s_end, d_end] = match 20 | snippet cs_lines, s_start, s_end, 'cs' 21 | console.log '-----' 22 | snippet js_lines, d_start, d_end, 'js' 23 | console.log '==============================================================' 24 | s_start = s_end 25 | d_start = d_end 26 | 27 | do -> 28 | [ignore, ignore, cs_file, js_file] = process.argv 29 | cs_lines = file_lines(cs_file) 30 | 31 | if js_file == '-' 32 | data = '' 33 | stdin = process.openStdin() 34 | stdin.on 'data', (buffer) -> 35 | data += buffer.toString() if buffer 36 | stdin.on 'end', -> 37 | js_lines = data.split '\n' 38 | list cs_lines, js_lines 39 | else 40 | js_lines = file_lines(js_file) 41 | list cs_lines, js_lines -------------------------------------------------------------------------------- /cs_js_source_mapping.coffee: -------------------------------------------------------------------------------- 1 | # This module attempts to find source line mappings between CS code 2 | # and JS code. Yes, this an enormous hack. :) 3 | 4 | get_line_matcher = (line) -> 5 | # return a function that returns true iff a JS 6 | # line is likely generated from a CS line 7 | line = line.split('# ')[0].trim() 8 | return null if line == '' 9 | 10 | # simple if statements 11 | if line.match /^if \S+$/ 12 | expr = line[3...].replace /@/g, 'this.' 13 | return (line) -> 14 | line.trim().indexOf("if \(#{expr}\)") == 0 15 | 16 | # do statements 17 | if line.match /^do .*->$/ 18 | return (line) -> 19 | line.match /\(function\(.*\) {/ 20 | 21 | # requires 22 | if line.indexOf(" = require") > 0 23 | matches = line.match /["'].*?["']/g 24 | if matches 25 | s = matches[0] 26 | return (line) -> 27 | line.indexOf("= require(#{s})") > 0 28 | 29 | # classes 30 | matches = line.match /^class ([@A-Za-z0-9_\.\[\]]+)/g 31 | if matches 32 | s = matches[0] 33 | s = s.replace "class ", "" 34 | return (line) -> 35 | ~line.indexOf(s + " =") 36 | 37 | # assignments 38 | matches = line.match /^([\$@A-Za-z0-9_\.\[\]]+)\s+(=|\+=)/g 39 | if matches 40 | [lhs, op] = matches[0].split /\s+/ 41 | if lhs.length > 2 42 | lhs = lhs.replace '@', '.' 43 | return (line) -> 44 | ~line.indexOf(lhs + " " + op) 45 | 46 | # objects 47 | matches = line.match /^@?([A-Za-z0-9_]+\s*: )/g 48 | if matches and matches.indexOf('{') == -1 49 | lhs = matches[0].replace '@', '' 50 | lhs = lhs.trim() 51 | lhs = lhs[0...lhs.length-1].trim() 52 | return null if lhs in ['constructor', 'class'] 53 | return (line) -> 54 | line.trim().indexOf(lhs+':') == 0 or line.trim().indexOf(lhs+' =') > 0 55 | 56 | # multiple simple args 57 | matches = line.match /\(\S+, .*?\) ->/g 58 | if matches 59 | s = matches[0] 60 | s = s.replace "->", "{" 61 | return (line) -> line.indexOf(s) > 0 62 | 63 | # strings | regexes 64 | matches = line.match /"[^"]+?"|'[^']+?'|\/[^\/]+?\//g 65 | if matches 66 | for str in matches 67 | if str.length >= 5 68 | return (line) -> line.indexOf(str) >= 0 69 | 70 | # try 71 | if line.match /^try$/ 72 | return (line) -> line.trim() == 'try {' 73 | 74 | # catch 75 | if line.match /^catch /g 76 | catch_var = line.split(' ')[1] 77 | return (line) -> line.trim().indexOf("} catch (#{catch_var}) {") == 0 78 | 79 | null 80 | 81 | 82 | is_comment_line = (line) -> 83 | line = line.trim() 84 | return line == '' or line[0] == '#' 85 | 86 | exports.source_line_mappings = (coffee_lines, js_lines) -> 87 | # Return an array of source line mappings, where each mapping 88 | # is an array with these elements: 89 | # CS line number (zero-based) 90 | # JS line number (zero-based) 91 | # 92 | # Not every CS line gets a mapping, but ideally enough lines get 93 | # mapped to help out downstream tools. 94 | curr_cs_line = 0 95 | curr_js_line = 0 96 | matches = [] 97 | 98 | find_js_match = (line_matcher) -> 99 | for k in [curr_js_line...js_lines.length] 100 | return k if line_matcher js_lines[k] 101 | null 102 | 103 | create_match_for_prior_comment_lines = (cs_line, js_line) -> 104 | # Work backward from cs_line to get all comments and blank lines... 105 | first_comment_line = cs_line 106 | while curr_cs_line <= first_comment_line-1 and is_comment_line coffee_lines[first_comment_line-1] 107 | first_comment_line -= 1 108 | if first_comment_line < cs_line 109 | matches.push [first_comment_line, js_line] 110 | 111 | for line, cs_line in coffee_lines 112 | line_matcher = get_line_matcher line 113 | if line_matcher 114 | js_line = find_js_match(line_matcher) 115 | if js_line? and curr_js_line < js_line 116 | create_match_for_prior_comment_lines cs_line, js_line 117 | matches.push [cs_line, js_line] 118 | curr_cs_line = cs_line 119 | curr_js_line = js_line 120 | matches.push [coffee_lines.length, js_lines.length] 121 | matches 122 | 123 | -------------------------------------------------------------------------------- /dashboard.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | fs = require 'fs' 3 | url = require 'url' 4 | 5 | {side_by_side} = require './side_by_side' 6 | {source_line_mappings} = require './cs_js_source_mapping' 7 | {list_files} = require './list_files' 8 | file_utils = require './file_utils' 9 | 10 | DIR = null # will be cmd-line arg 11 | GIT_REPO = "https://github.com/showell/CoffeeScriptLineMatcher" 12 | JQUERY_CDN = """ 13 | 14 | """ 15 | COFFEE_FILE_REGEX = /\.(coffee|cof)$/ 16 | 17 | relative_path = (fn) -> file_utils.relative_path DIR, fn 18 | get_files = (regex) -> file_utils.get_files DIR, regex 19 | 20 | worst_match = (matches) -> 21 | # debugging code 22 | last = 0 23 | max = 0 24 | worst = null 25 | for match in matches 26 | [cs, js] = match 27 | if cs - last > max 28 | max = cs - last 29 | worst = cs - max + 1 30 | last = cs 31 | "The longest CS section starts at cs:#{worst} (#{max} lines)." 32 | 33 | timestamps = (cs_fn, cb) -> 34 | # Return timestamps of our files. Mostly used by AJAX calls to avoid 35 | # unnecessary page refreshes. 36 | cs_files = get_files COFFEE_FILE_REGEX 37 | ts = (fn) -> fs.statSync(fn).mtime.toISOString() 38 | js_files = get_files /\.js$/ 39 | js_fn = file_utils.js_file_for cs_fn, js_files 40 | cb get_fingerprint cs_fn, js_fn 41 | 42 | get_fingerprint = (cs_fn, js_fn) -> 43 | ts = (fn) -> fs.statSync(fn).mtime.toISOString() 44 | data = 45 | cs: ts cs_fn 46 | if js_fn 47 | data.js = ts js_fn 48 | data 49 | 50 | view_file = (cs_fn, cb) -> 51 | html = """ 52 | 53 | #{relative_path cs_fn} 54 | 55 | #{JQUERY_CDN} 56 | 57 | 58 |

#{relative_path cs_fn}

59 | View files (#{DIR}) 60 |
61 | About 62 |
63 | """ 64 | 65 | cs_files = get_files COFFEE_FILE_REGEX 66 | js_files = get_files /\.js$/ 67 | throw "illegal file #{cs_fn}" unless cs_fn in cs_files 68 | js_fn = file_utils.js_file_for cs_fn, js_files 69 | 70 | add_metadata = -> 71 | finger_print = get_fingerprint cs_fn, js_fn 72 | finger_print = JSON.stringify finger_print, null, " " 73 | html += """ 74 | 78 | """ 79 | 80 | if js_fn is null 81 | html += "No current JS file was found for #{cs_fn}" 82 | add_metadata() 83 | return cb html 84 | 85 | html += "JS file: #{relative_path js_fn}
" 86 | coffee_lines = file_utils.file_lines(cs_fn) 87 | js_lines = file_utils.file_lines(js_fn) 88 | matches = source_line_mappings coffee_lines, js_lines 89 | html += worst_match(matches) 90 | html += side_by_side matches, coffee_lines, js_lines 91 | add_metadata() 92 | 93 | cb html 94 | 95 | about = (cb) -> 96 | cb """ 97 | 98 | About CoffeeScriptLineMatcher 99 | 100 | 101 |

About

102 | View files 103 | 104 |

105 | GIT Repository: CoffeeScriptLineMatcher. 106 |

107 |

108 | This tool lets you view CS and JS code side by side. 109 |

110 |

111 | The algorithm for matching up CS lines to JS lines is 112 | independent of the compiler itself. I've tested the 113 | algorithm on several CS examples, but unorthodox coding 114 | styles will likely confuse the algorithm. (Long term, 115 | CS itself will have line number support, so this tool 116 | can eventually be patched to use native mappings.) 117 |

118 | """ 119 | 120 | 121 | run_dashboard = (port) -> 122 | server = http.createServer (req, res) -> 123 | serve_page = (html) -> 124 | res.writeHeader 200, 'Content-Type': 'text/html' 125 | res.write html 126 | res.end() 127 | 128 | serve_css = (fn) -> 129 | res.writeHeader 200, 'Content-Type': 'text/css' 130 | res.write fs.readFileSync fn 131 | res.end() 132 | 133 | serve_js = (fn) -> 134 | res.writeHeader 200, 'Content-Type': 'text/javascript' 135 | res.write fs.readFileSync fn 136 | res.end() 137 | 138 | serve_json = (data) -> 139 | res.writeHeader 200, 'Content-Type': 'text/json' 140 | res.write JSON.stringify data, null, ' ' 141 | res.end() 142 | 143 | parts = url.parse(req.url, true) 144 | 145 | # console.log "Serving #{parts.pathname} #{JSON.stringify parts.query}" 146 | try 147 | if parts.pathname == '/view' 148 | view_file parts.query.FILE, serve_page 149 | else if parts.pathname == '/timestamps' 150 | timestamps parts.query.FILE, serve_json 151 | else if parts.pathname == '/about' 152 | about serve_page 153 | else if parts.pathname == '/dashboard.css' 154 | serve_css './assets/dashboard.css' 155 | else if parts.pathname == '/view_file.js' 156 | serve_js './assets/view_file.js' 157 | else if parts.pathname == '/' 158 | list_files DIR, get_files, COFFEE_FILE_REGEX, serve_page 159 | else if parts.pathname == '/favicon.ico' 160 | # Patches welcome here, but favicon.ico is kind of pointless 161 | # in a localhost dev tool. Sending the 404 does nothing of 162 | # substance, so this is really just a placeholder. 163 | res.writeHeader 404 164 | res.end(); 165 | else 166 | res.end() 167 | catch e 168 | # Right now our code is mostly synchronous, but this won't 169 | # catch async exceptions, so it's just a band-aid for now. 170 | serve_page "Exception: #{e}" 171 | 172 | server.listen port 173 | console.log "Server running at http://localhost:#{port}/" 174 | 175 | do -> 176 | [ignore, ignore, DIR, port] = process.argv 177 | unless port? and port.match /^\d+/ 178 | console.warn "You must supply a directory and port number." 179 | return 180 | run_dashboard(port) 181 | -------------------------------------------------------------------------------- /examples/bot.coffee: -------------------------------------------------------------------------------- 1 | # NOTE! This code is borrowed from https://github.com/thejh/nodebot, and it 2 | # used here only for the example of line number mapping. 3 | 4 | 5 | coffee = require 'coffee-script' 6 | coco = require 'coco' 7 | config = JSON.parse require('fs').readFileSync __dirname+'/config.json', 'utf8' 8 | https = require 'https' 9 | npm = require 'npm' 10 | Irc = require 'irc-js' 11 | cradle = require 'cradle' 12 | {GitHubApi} = require 'github' 13 | request = require 'request' 14 | gitHubApi = new GitHubApi() 15 | githubIssueApi = gitHubApi.getIssueApi() 16 | githubObjectApi = gitHubApi.getObjectApi() 17 | githubCommitApi = gitHubApi.getCommitApi() 18 | Search = require 'complex-search' 19 | querystring = require 'querystring' 20 | 21 | BASIC_AUTH_DATA = "Basic #{new Buffer(config.github.auth).toString 'base64'}" 22 | BOTSAFE = /^[-_a-zA-Z0-9]+$/ 23 | NICKNAME_REGEX = /^[a-zA-Z0-9_][.a-zA-Z0-9_+-]+$/ 24 | 25 | docKeywords = {} 26 | lastDocsUpdate = 0 27 | docsFetching = null 28 | updateDocs = (cb) -> 29 | return cb(docKeywords) if (new Date().getTime() - lastDocsUpdate) < 1000*60*5 30 | return docsFetching.push cb if docsFetching? 31 | docsFetching = [cb] 32 | request { 33 | uri: "http://nodejs.org/docs/latest/api/all.html" 34 | }, (error, response, body) -> 35 | body.split('

36 | sectionID = sectionHTML.split('"')[1] 37 | sectionTitle = sectionHTML.split('>')[1].split('<')[0] 38 | section = id: sectionID, title: sectionTitle 39 | sectionHTML.split(/[^a-zA-Z0-9_]/).forEach (word) -> 40 | word = word.toLowerCase() 41 | return if word.length is 0 42 | docKeywords[word] = [] if not docKeywords.hasOwnProperty(word) 43 | docKeywords[word].push section if docKeywords[word].indexOf(section) is -1 44 | lastDocsUpdate = new Date().getTime() 45 | callback(docKeywords) for callback in docsFetching 46 | docsFetching = null 47 | 48 | npmData = {} 49 | lastNpmUpdate = 0 50 | lastNpmUpdateLocaltime = 0 51 | npmFetching = null 52 | updateNpm = (cb) -> 53 | return cb(npmData) if (new Date().getTime() - lastNpmUpdateLocaltime) < 1000*60*5 54 | return npmFetching.push cb if npmFetching? 55 | npmFetching = [cb] 56 | request { 57 | uri: "http://registry.npmjs.org/-/all/since?startkey=#{lastNpmUpdate}" 58 | }, (error, response, body) -> 59 | npmData[key] = value for key, value of JSON.parse body 60 | lastNpmUpdate = Date.parse response.headers.date 61 | lastNpmUpdateLocaltime = new Date().getTime() 62 | callback(npmData) for callback in npmFetching 63 | npmFetching = null 64 | 65 | github = 66 | # description: string 67 | # public: boolean 68 | # files: {string: content: string} 69 | postGist: (description, public, files, callback) -> 70 | request { 71 | uri: 'https://api.github.com/gists' 72 | method: 'POST' 73 | headers: 74 | 'Authorization': BASIC_AUTH_DATA 75 | body: JSON.stringify {description, public, files} 76 | }, (error, response, body) -> 77 | if error 78 | return callback error 79 | try 80 | responseJson = JSON.parse body.toString() 81 | catch e 82 | return callback e 83 | return callback "didn't get a gist back" if not responseJson.id? 84 | callback null, responseJson.id 85 | 86 | getGist: (id, callback) -> 87 | if not id? or not /^[0-9a-f]+$/.exec id 88 | return callback 'invalid id' 89 | request { 90 | uri: "https://api.github.com/gists/#{id}" 91 | }, (error, response, body) -> 92 | if error 93 | return callback error 94 | try 95 | responseJson = JSON.parse body.toString() 96 | catch e 97 | return callback e 98 | if not responseJson.id? 99 | console.log body.toString() 100 | return callback "didn't get a gist back" 101 | callback null, responseJson 102 | 103 | database = new (cradle.Connection)( 104 | 'https://thejh.cloudant.com' 105 | 443 106 | auth: 107 | username: config.couch.user 108 | password: config.couch.pass 109 | ).database 'ircbot' 110 | 111 | npmDatabase = new (cradle.Connection)( 112 | '' 113 | 80 114 | ).database 'registry' 115 | 116 | MYNICK = 'jhbot' 117 | 118 | npmLoaded = false 119 | npm.load {}, (err) -> 120 | throw err if err? 121 | npmLoaded = true 122 | 123 | 124 | irc = new Irc { 125 | server: 'irc.freenode.net' 126 | nick: MYNICK 127 | flood_protection: true 128 | user: { 129 | username: 'jhbot' 130 | realname: 'TheJHs Bot' 131 | } 132 | } 133 | 134 | process.on 'uncaughtException', (err) -> 135 | console.log err.stack||err 136 | try 137 | irc.privmsg 'thejh', "EXCEPTION: "+err 138 | catch e 139 | 140 | _cachedIssueList = null 141 | _cachedIssueListUpdated = 0 142 | 143 | lastCsGist = null 144 | 145 | min = (a, b) -> if a if a>b then a else b 147 | contains = (arr, el) -> -1 isnt arr.indexOf el 148 | arrayMax = (arr) -> 149 | n = -1/0 150 | n = val for val in arr when val > n 151 | n 152 | zeropad = (num, maxnum) -> 153 | padlen = (maxnum+"").length - (num+"").length 154 | ("0" for [0...padlen]).join('') + num 155 | spacepadEnd = (str, paddedLength) -> 156 | padlen = paddedLength - str.length 157 | str + (" " for [0...padlen]).join('') 158 | 159 | getIssueList = (cb) -> 160 | # time in ms 161 | time = new Date().getTime() 162 | # assume five minutes fresh cache 163 | if _cachedIssueList? and time-_cachedIssueListUpdated < 1000*60*5 164 | return cb null, _cachedIssueList 165 | githubIssueApi.getList 'joyent', 'node', 'open', (err, issues) -> 166 | unless err? 167 | _cachedIssueList = issues 168 | _cachedIssueListUpdated = time 169 | console.log "fetched issue list in #{new Date().getTime()-time}ms" 170 | cb err, issues 171 | 172 | wpheaders = 173 | 'User-Agent': 'jhbot by Jann Horn, contact: jannhorn@googlemail.com' 174 | 175 | commands = 176 | remember: (message, [name, value...], reply) -> 177 | if value.length is 0 178 | return reply "you need to specify name and definition", error: true 179 | value = value.join ' ' 180 | unless BOTSAFE.exec name 181 | return reply "that name doesn't match #{BOTSAFE}" 182 | unless BOTSAFE.exec value[0] 183 | return reply "that value starts with a non-alphanumeric character, I don't want to store bot commands", error: true 184 | docid = "definitions:#{name}" 185 | database.get docid, (err, oldData) -> 186 | savedCb = (err, doc) -> 187 | reply if err? 188 | console.error err 189 | "something went wrong" 190 | else 191 | "saved definition of '#{name}'" 192 | if err? 193 | database.save docid, {data: value}, savedCb 194 | else 195 | database.save docid, oldData._rev, {data: value}, savedCb 196 | 197 | git: 198 | context: (message, [project, file, line], reply) -> 199 | SAFE_STRING_REGEX = /^[a-zA-Z0-9_-]+$/ 200 | if not line? 201 | return reply "you must specify project, file and line", error: true 202 | if not /^[0-9]+$/.exec line 203 | return reply "line must be numeric", error: true 204 | # we start from 0, humans start from 1 205 | line -= 1 206 | [user, project] = project.split '/' 207 | if not SAFE_STRING_REGEX.exec user 208 | return reply "that user name looks weird", error: true 209 | if not SAFE_STRING_REGEX.exec project 210 | return reply "that project name looks weird", error: true 211 | githubCommitApi.getFileCommits user, project, 'master', file, (err, commits) -> 212 | if err? or commits.length is 0 213 | return reply "error, getFileCommits() failed, are you sure that the data is correct?", error: true 214 | githubObjectApi.showBlob user, project, commits[0].tree, file, (err, fileData) -> 215 | if err? 216 | return reply "error, showBlob() failed", error: true 217 | fileData = fileData.data 218 | fileLines = fileData.split '\n' 219 | if line >= fileLines.length 220 | return reply "that file only has #{fileLines.length} lines", error: true 221 | console.log "base line #{line}" 222 | fromLine = max 0, line-1 223 | toLine = min fileLines.length-1, line+1 224 | console.log "extracting lines #{fromLine}...#{toLine}" 225 | showedLines = for lineData, i in fileLines.slice fromLine, toLine+1 226 | "#{zeropad(i+fromLine+1, toLine+1)} #{lineData}" 227 | console.log "ME HAZ TEH BLOB! #{showedLines.length} shown (out of #{fileLines.length})" 228 | for lineData in showedLines 229 | reply lineData 230 | 231 | issue: 232 | search: (message, keywords, reply) -> 233 | MAX_ISSUES_COUNT = 3 234 | if keywords.length is 0 235 | return reply "please specify at least one keyword", error: true 236 | keywords = (keyword.toLowerCase() for keyword in keywords) 237 | getIssueList (err, issues) -> 238 | if err? 239 | return reply "something went wrong", error: true 240 | foundIssues = for issue in issues 241 | continue unless (do -> 242 | issueTitle = issue.title.toLowerCase() 243 | for keyword in keywords 244 | if -1 is issueTitle.indexOf keyword 245 | return false 246 | true 247 | ) 248 | issue 249 | 250 | secondHitIssues = for issue in issues 251 | continue if -1 isnt foundIssues.indexOf issue 252 | issuestr = (issue.title + " " + issue.body).toLowerCase() 253 | continue unless (do -> 254 | for keyword in keywords 255 | if -1 is issuestr.indexOf keyword 256 | return false 257 | true 258 | ) 259 | issue 260 | foundIssues = foundIssues.concat secondHitIssues 261 | 262 | foundIssueCount = foundIssues.length 263 | foundIssues = foundIssues.slice 0, MAX_ISSUES_COUNT 264 | if foundIssues.length is 0 265 | return reply "no issues found" 266 | reply "found issues: #{foundIssueCount}#{[if foundIssueCount > MAX_ISSUES_COUNT then ", showing the first #{MAX_ISSUES_COUNT}"]}" 267 | for {number, title} in foundIssues 268 | reply "Issue: https://github.com/joyent/node/issues/#{number} : #{title}" 269 | return 270 | mem: (message, [name, substitutions...], reply) -> 271 | if not name? 272 | return reply "you need to specify a name", error: true 273 | unless BOTSAFE.exec name 274 | return reply "that name doesn't match #{BOTSAFE}" 275 | database.get "definitions:#{name}", (err, doc) -> 276 | if err? 277 | reply "i don't know what a #{name} is", error: true 278 | else 279 | data = doc.data 280 | if substitutions? 281 | for subst in substitutions 282 | data = data.replace '$', subst 283 | reply data 284 | coco: 285 | compile: (message, code, reply) -> 286 | code = code.join " " 287 | try 288 | compiled = coco.compile code, bare: true 289 | compiled = compiled.replace /\n/g, ' ' 290 | compiled = compiled.replace /\s+/g, ' ' 291 | reply compiled 292 | catch e 293 | reply "failed: #{(e+'').split('\n')[0]}", error: true 294 | coffee: 295 | compile: (message, code, reply) -> 296 | code = code.join " " 297 | try 298 | compiled = coffee.compile code, bare: true 299 | compiled = compiled.replace /\n/g, ' ' 300 | compiled = compiled.replace /\s+/g, ' ' 301 | reply compiled 302 | catch e 303 | reply "failed: #{(e+'').split('\n')[0]}", error: true 304 | compilegist: (message, [gistid], reply) -> 305 | if not gistid? 306 | if lastCsGist? and message.params[0] is '#coffeescript' 307 | gistid = lastCsGist 308 | else 309 | return reply "please specify a gist by id or url", error: true 310 | gistid = gistid.split('/').pop() 311 | github.getGist gistid, (err, gist) -> 312 | if err? 313 | console.log err.stack or err 314 | return reply "couldn't fetch the gist", error: true 315 | coffeeFiles = {} 316 | for filename, {content} of gist.files 317 | coffeeFiles[filename] = content: try 318 | coffee.compile content, bare: true 319 | catch compileErr 320 | if compileErr.stack? 321 | compileErr.stack 322 | else 323 | compileErr+"" 324 | github.postGist "COMPILED"+gist.description, gist.public, coffeeFiles, (err, id) -> 325 | if err? 326 | console.log err.stack or err 327 | return reply "couldn't publish the gist", error: true 328 | reply "https://gist.github.com/#{id}" 329 | admin: 330 | join: (message, [channel], reply) -> 331 | if isChannel channel 332 | irc.join channel 333 | reply 'ok' 334 | say: (message, [target, what...]) -> 335 | irc.privmsg target, what.join ' ' 336 | testAccountLookup: (message, [nick], reply) -> 337 | getNicksAccount nick, (account) -> 338 | reply "account name of #{nick} is #{account}" 339 | eval: (message, code, reply) -> 340 | code = code.join ' ' 341 | reply eval code 342 | docs: 343 | search: (message, keywords, reply) -> 344 | NAMESLIMIT = 3 345 | if keywords.length is 0 346 | return reply "you must specify at least one keyword", error: true 347 | updateDocs (docKeywords) -> 348 | try 349 | search = new Search keywords.join(' ').toLowerCase(), (results) -> 350 | return reply "no results" if results.length is 0 351 | if results.length > NAMESLIMIT 352 | truncated = true 353 | results = results.slice 0, NAMESLIMIT 354 | reply "truncated list:" if truncated 355 | for result in results 356 | reply "section \"#{result.title}\": http://nodejs.org/docs/latest/api/all.html##{result.id}" 357 | return 358 | for keyword in search.keywords 359 | search.provideKeywordData keyword, docKeywords[keyword] or [] 360 | catch err 361 | if err.stack? 362 | console.log err.stack 363 | return reply "internal error", error: true 364 | else 365 | return reply "error: #{err}", error: true 366 | npm: 367 | owner: (message, [package], reply) -> 368 | if not package? 369 | return reply "package name missing", error: true 370 | npm.commands.owner ['ls', package], (err, owners) -> 371 | if err? 372 | reply "error", error: true 373 | else 374 | if not owners?.length 375 | msg = "admin party!" 376 | else 377 | msg = "owners: " + ("#{o.name} <#{o.email}>" for o in owners).join ', ' 378 | reply msg 379 | info: (message, [package], reply) -> 380 | if not package? 381 | return reply "package name missing", error: true 382 | updateNpm (npmData) -> 383 | if not npmData[package]? 384 | return reply "couldn't find that package" 385 | package = npmData[package] 386 | reply "#{package.name} by #{package.author.name}, version #{package['dist-tags'].latest}: #{package.description}" 387 | prop: (message, [package, propertyName], reply) -> 388 | if not package 389 | return reply "no package name specified", error: true 390 | if not propertyName 391 | return reply "no property specified", error: true 392 | updateNpm (npmData) -> 393 | if not npmData.hasOwnProperty package 394 | return reply "package not found", error: true 395 | if not npmData[package].hasOwnProperty propertyName 396 | return reply "property is not defined", error: true 397 | propertyValue = npmData[package][propertyName] 398 | if typeof propertyValue is 'object' 399 | propertyValue = JSON.stringify propertyValue 400 | reply "#{npmData[package].name} has #{propertyName} #{propertyValue}" 401 | search: (message, keywords, reply) -> 402 | NAMESLIMIT = 20 403 | if keywords.length == 0 404 | return reply "you must specify at least one keyword", error: true 405 | updateNpm (npmData) -> 406 | try 407 | search = new Search keywords.join(' '), (results) -> 408 | return reply "no results" if results.length is 0 409 | if results.length > NAMESLIMIT 410 | truncated = true 411 | results = results.slice 0, NAMESLIMIT 412 | if results.length > 5 and isChannel message.params[0] 413 | reply "packages (short format#{[if truncated then ', truncated']}): #{results.join ', '}" 414 | else 415 | reply "truncated list:" if truncated 416 | for result in results 417 | reply "package #{result}: #{npmData[result].description or ''}" 418 | return 419 | for keyword in search.keywords 420 | ids = for id, entry of npmData 421 | continue unless ( 422 | entry.keywords? and contains entry.keywords, keyword 423 | ) or ( 424 | contains entry.name, keyword 425 | ) or ( 426 | entry.description? and contains entry.description, keyword 427 | ) 428 | id 429 | search.provideKeywordData keyword, ids 430 | #do (keyword) -> 431 | # npmDatabase.view 'app/search', startkey: keyword, endkey: keyword+'ZZZZZZZZ', (err, rows) -> 432 | # if err? 433 | # console.log err.stack||err 434 | # return reply "internal error", error: true 435 | # for row in rows 436 | # searchResultModules[row.id] = row 437 | # search.provideKeywordData keyword, (id for {id} in rows) 438 | catch err 439 | if err.stack? 440 | console.log err.stack 441 | return reply "internal error", error: true 442 | else 443 | return reply "error: #{err}", error: true 444 | translate: (message, [srclang, dstlang, word], reply) -> 445 | return (reply "that srclang (#{srclang}) isnt supported", error: true) if srclang isnt 'de' and srclang isnt 'en' and srclang isnt 'fi' 446 | reqopts = 447 | uri: "http://#{srclang}.wiktionary.org/w/index.php?action=raw&title=#{encodeURIComponent word}" 448 | headers: wpheaders 449 | request reqopts, (err, response, body) -> 450 | console.log "TRANSLATE ERR <<<"+err+">>>" if err 451 | return (reply 'error in "translate"', error: true) if err or not body 452 | hits = [] 453 | translateRegex = if srclang is 'fi' 454 | /\*({{)([^}]+)}}: \[\[([^\]]+)]/g 455 | else 456 | /{{(Ü|t|t\+|t-)\|([^|]+)\|([^}]+)}}/g 457 | body.replace translateRegex, (_, _, curDst, translation) -> 458 | return unless curDst is dstlang 459 | return if -1 isnt translation.indexOf '\n' 460 | return if -1 isnt translation.indexOf '\r' 461 | hits.push translation 462 | hits = hits.join ', ' 463 | hits = '' if hits.length is 0 464 | hits = (hits.slice 0, 400) + ' ' if hits.length > 400 465 | reply "translations #{srclang}->#{dstlang}: #{hits}" 466 | help: (message, [botname]) -> 467 | if not botname 468 | if message.params[0] isnt '#nodejitsu' 469 | reply message.person.nick, "For help with this bot (a minute of one line per 2 seconds), type `!help jhbot`." 470 | return 471 | return if botname isnt 'jhbot' 472 | syntaxes = 473 | "remember": " " 474 | "git context": "/ " 475 | "mem": " [ [...]]" 476 | "coffee compile": "" 477 | "coffee compilegist": "" 478 | "admin join": "" 479 | "admin say": " " 480 | "npm owner": "" 481 | descriptions = 482 | "npm search": """ 483 | search for stuff on npm. you can use '&', '|', parens, keywords. default op is '&'. 484 | no operator precedence, just parens first. will show a maximum of 20 results, one per line. 485 | in channels, if there are more than 5 results, they will be printed in short format. 486 | """ 487 | "remember": "store a string, $ is a placeholder" 488 | "git context": "get three lines from the specified position in a file on github" 489 | "mem": "print a stored string, replace placeholders with given parameters" 490 | "coffee compile": "compile a given line of coffeescript" 491 | "coffee compilegist": "compile the given coffee-gist into another js-gist" 492 | "help": "print this help" 493 | lines = [] 494 | addHelp = (prefix, obj) -> 495 | for subkey, value of obj 496 | fullname = if prefix then "#{prefix} #{subkey}" else subkey 497 | switch typeof value 498 | when 'object' 499 | addHelp fullname, value 500 | when 'function' 501 | lines.push fullname 502 | addHelp null, commands 503 | longestLine = 2 + arrayMax (length for {length} in lines) 504 | outputLines = [] 505 | for line in lines 506 | syntax = syntaxes[line] 507 | description = descriptions[line] 508 | line += ' ' + syntax if syntax? 509 | outputLines.push 'command: '+line 510 | outputLines.push ' ' + descriptionLine for descriptionLine in description.split('\n') if description? 511 | reply message.person.nick, line for line in outputLines 512 | #time: (message, [location]) -> 513 | # if not location? 514 | # zone = 0 515 | # else 516 | # if /^[+-]?[0-9]+$/.exec location 517 | # zone = parseInt location, 10 518 | # else 519 | # return reply message, "unknown zone (try +/-5)" 520 | # reply message, new Date(new Date().getTime() + 1000*60*60*zone).toGMTString() 521 | 522 | isChannel = (chanOrNick) -> 523 | chanOrNick[0] == '#' 524 | 525 | isOwner = (person) -> 526 | {host} = person 527 | host is 'wikipedia/TheJH' 528 | 529 | reply = (do -> 530 | replyQueue = [] 531 | lastTime = 0 532 | WAIT_TIME = 2000 533 | 534 | doReply = (originalMessage, message) -> 535 | if typeof originalMessage is 'string' 536 | target = originalMessage 537 | else 538 | {person: {nick: senderNick}, params: [originalTarget]} = originalMessage 539 | target = if isChannel originalTarget 540 | originalTarget 541 | else 542 | senderNick 543 | irc.privmsg target, message 544 | lastTime = new Date().getTime() 545 | 546 | canReplyHandler = -> 547 | {originalMessage, message} = replyQueue.shift() 548 | doReply originalMessage, message 549 | if replyQueue.length > 0 550 | setTimeout canReplyHandler, WAIT_TIME 551 | 552 | (originalMessage, message) -> 553 | time = new Date().getTime() 554 | if replyQueue.length is 0 and time - lastTime > WAIT_TIME 555 | doReply originalMessage, message 556 | else 557 | if replyQueue.length is 0 558 | setTimeout canReplyHandler, WAIT_TIME - (time - lastTime) 559 | replyQueue.push {originalMessage, message} 560 | ) 561 | 562 | handleCommand = (message, commandParts) -> 563 | obj = commands 564 | i = 0 565 | if commandParts[0]?[0] is '@' 566 | answerTargetNick = commandParts[0].substring 1 567 | unless NICKNAME_REGEX.exec answerTargetNick 568 | return reply message, "That nick looks weird. I refuse." 569 | commandParts.shift() 570 | while typeof obj is 'object' 571 | if i is commandParts.length 572 | return 573 | nextPart = commandParts[i++] 574 | if nextPart is "admin" and not isOwner message.person 575 | console.log i 576 | return reply message, "you're not my admin" 577 | if nextPart is "admin" 578 | console.log "valid admin command from #{JSON.stringify message.person}" 579 | if obj.hasOwnProperty(nextPart) and not {}.hasOwnProperty(nextPart) 580 | obj = obj[nextPart] 581 | else 582 | return 583 | if typeof obj is 'function' 584 | obj message, commandParts.slice(i), (answer, options = {}) -> 585 | reply message, [if not options.error and answerTargetNick? then "#{answerTargetNick}, "] + answer 586 | 587 | autoLint = (original, nick, message) -> 588 | nick = " #{nick}" if not NICKNAME_REGEX.exec nick 589 | GIST_REGEX = /https:\/\/gist\.github\.com\/([0-9a-f]+)/ 590 | gist_match = GIST_REGEX.exec message 591 | lintWarn = (warning) -> 592 | reply original, "#{nick}, #{warning}" 593 | if gist_match 594 | lastCsGist = gist_id = gist_match[1] 595 | github.getGist gist_id, (err, gist) -> 596 | return if err? 597 | for filename, {content} of gist.files 598 | lines = content.split "\n" 599 | hasTabIndent = hasSpaceIndent = false 600 | levels = [0] 601 | indents = for line in lines 602 | [indent, contentStart] = line.split /[^\t\s]/ 603 | continue if not contentStart? 604 | hasSpaceIndent = true if -1 < indent.indexOf " " 605 | hasTabIndent = true if -1 < indent.indexOf "\t" 606 | indentLevel = indent.length 607 | lastIndentLevel = levels[levels.length-1] 608 | levels.push indentLevel if indentLevel > lastIndentLevel 609 | if indentLevel < lastIndentLevel 610 | newLevelIndex = levels.indexOf indentLevel 611 | if newLevelIndex is -1 612 | badOutdent = true 613 | break 614 | levels = levels.slice 0, newLevelIndex+1 615 | try 616 | coffee.compile content, bare: true 617 | valid_coffee = true 618 | catch compileErr 619 | if valid_coffee and hasTabIndent and hasSpaceIndent 620 | lintWarn "you're using both spaces and tabs for indentation. "+ 621 | "coffee treats one tab as one space, therefore the meaning of your code is messed up." 622 | if badOutdent 623 | lintWarn "you seem to have an outdent in your code that doesn't match the indents" 624 | 625 | genericWarnings = (original, nick, message, channel) -> 626 | nick = " #{nick}" if not NICKNAME_REGEX.exec nick 627 | GIST_REGEX = /https:\/\/gist\.github\.com\/([0-9a-f]+)/ 628 | gist_match = GIST_REGEX.exec message 629 | warn = (warning) -> reply original, "#{nick}, my almighty, artificially created brain says that #{warning.replace(/\n/g, ' ')}" 630 | if channel == '#node.js' and message.indexOf('graceful-fs') != -1 and message.indexOf('npm') != -1 631 | nick = " #{nick}" if not NICKNAME_REGEX.exec nick 632 | reply args, "#{nick}, if you have problems installing npm because of some 'graceful-fs not found' error, your node.js version is outdated." 633 | if gist_match 634 | github.getGist gist_match[1], (err, gist) -> 635 | return if err? 636 | for filename, {content} of gist.files 637 | if content.indexOf('info using npm@0.') != -1 638 | warn "that version of npm (0.x) is ancient. update npm with `curl http://npmjs.org/install.sh | sudo sh`." 639 | if content.indexOf("Error: Cannot find module 'graceful-fs'") != -1 and content.indexOf("fetching: http://registry.npmjs.org/") != -1 640 | warn "that version of nodejs is ancient, use 0.4.x or newer" 641 | if content.indexOf("ERR! TypeError: Cannot call method 'filter' of undefined") != -1 642 | warn "that version of npm doesn't cleanly self-update, use `curl http://npmjs.org/install.sh | sudo sh`" 643 | if content.indexOf("ERR! tar") != -1 and content.indexOf("Ignoring unknown extended header") != -1 644 | warn "your 'tar' program is broken/outdated, install a new one" 645 | if /npm info using node@v[0-9]+\.[0-9]+\.[0-9]+-pre/.test(content) 646 | warn """you're using a very unstable version of node (git master/HEAD). If you don't want to run into problems like this one, 647 | use a stable version (in x.y.z, y is an even number and there's no -pre at the end of the version). Of course, you should 648 | report problems anyway - after all, what's unstable now is supposed to become stable soon.""" 649 | 650 | irc.on 'privmsg', (args) -> 651 | BOTS = ['jhbot', 'v8bot', 'v8bot_', 'catbot', 'kohai'] 652 | {person: {nick, user, host}, params: [chanOrNick, message]} = args 653 | return if -1 isnt BOTS.indexOf nick 654 | if chanOrNick is '#coffeescript' 655 | autoLint args, nick, message 656 | genericWarnings args, nick, message, chanOrNick.toLowerCase() 657 | if message[0] isnt '!' and isChannel chanOrNick 658 | return 659 | if message[0] is '!' 660 | message = message.substring 1 661 | messageparts = message.split ' ' 662 | handleCommand args, messageparts 663 | 664 | nickInfoListeners = {} 665 | 666 | getNicksAccount = (usersNick, cb) -> 667 | if not nickInfoListeners[usersNick] 668 | nickInfoListeners[usersNick] = [] 669 | irc.privmsg 'NickServ', "info =#{usersNick}" 670 | nickInfoListeners[usersNick].push cb 671 | 672 | _handleNicksAccount = (nick, account) -> 673 | if nickInfoListeners[nick]? 674 | for listener in nickInfoListeners[nick] 675 | listener account 676 | delete nickInfoListeners[nick] 677 | 678 | # Information on \2TheJH\2 (account \2TheJH\2): 679 | NICKSERV_USERINFO_REGEX = /^Information on \x02([^\x02]*)\x02 \(account \x02([^\x02]*)\x02\)/ 680 | NICKSERV_HASNOUSER_REGEX = /^\x02=([^\x02]*)\x02 is not registered.$/ 681 | 682 | irc.on 'notice', (args) -> 683 | # server notices aren't interesting 684 | return if not args.person? 685 | {person: {nick, user, host}, params: [_, message]} = args 686 | if nick == 'NickServ' 687 | if 0 == message.indexOf 'You are now identified' 688 | console.log 'alright, were identified, go on' 689 | setTimeout (-> 690 | irc.join "##{chan}" for chan in ['node.js', 'coffeescript', 'nodejitsu', 'relief1', '#deutsch'] 691 | ), 10000 692 | userinfoMatch = NICKSERV_USERINFO_REGEX.exec message 693 | if userinfoMatch? 694 | console.log "userinfo match" 695 | _handleNicksAccount userinfoMatch[1], userinfoMatch[2] 696 | hasNoUserMatch = NICKSERV_HASNOUSER_REGEX.exec message 697 | if hasNoUserMatch? 698 | _handleNicksAccount hasNoUserMatch[1], null 699 | 700 | updateNpm (npmData) -> 701 | console.log "NPM ready with #{Object.keys(npmData).length} entries" 702 | 703 | irc.connect -> 704 | irc.privmsg 'NickServ', "IDENTIFY #{config.irc.user} #{config.irc.pass}" 705 | Sof = require './stackoverflow' 706 | 707 | new Sof (line) -> 708 | reply '#node.js', line -------------------------------------------------------------------------------- /examples/chat.coffee: -------------------------------------------------------------------------------- 1 | net = require("net") 2 | sys = require("sys") 3 | EventEmitter = require("events").EventEmitter 4 | 5 | isNicknameLegal = (nickname) -> 6 | return false unless nickname.replace(/[A-Za-z0-9]*/, "") is "" 7 | for used_nick of @chatters 8 | return false if used_nick is nickname 9 | true 10 | 11 | class ChatServer 12 | constructor: -> 13 | @chatters = {} 14 | @server = net.createServer @handleConnection 15 | @server.listen 1212, "localhost" 16 | 17 | handleConnection: (connection) => 18 | console.log "Incoming connection from " + connection.remoteAddress 19 | connection.setEncoding "utf8" 20 | chatter = new Chatter(connection, this) 21 | chatter.on "chat", @handleChat 22 | chatter.on "join", @handleJoin 23 | chatter.on "leave", @handleLeave 24 | 25 | handleChat: (chatter, message) => 26 | @sendToEveryChatterExcept chatter, chatter.nickname + ": " + message 27 | 28 | handleJoin: (chatter) => 29 | console.log chatter.nickname + " has joined the chat." 30 | @sendToEveryChatter chatter.nickname + " has joined the chat." 31 | @addChatter chatter 32 | 33 | handleLeave: (chatter) => 34 | console.log chatter.nickname + " has left the chat." 35 | @removeChatter chatter 36 | @sendToEveryChatter chatter.nickname + " has left the chat." 37 | 38 | addChatter: (chatter) => 39 | @chatters[chatter.nickname] = chatter 40 | 41 | removeChatter: (chatter) => 42 | delete @chatters[chatter.nickname] 43 | 44 | sendToEveryChatter: (data) => 45 | for nickname of @chatters 46 | @chatters[nickname].send data 47 | 48 | sendToEveryChatterExcept: (chatter, data) => 49 | for nickname of @chatters 50 | @chatters[nickname].send data unless nickname is chatter.nickname 51 | 52 | 53 | class Chatter extends EventEmitter 54 | constructor: (socket, server) -> 55 | EventEmitter.call this 56 | @socket = socket 57 | @server = server 58 | @nickname = "" 59 | @lineBuffer = new SocketLineBuffer(socket) 60 | @lineBuffer.on "line", @handleNickname 61 | @socket.on "close", @handleDisconnect 62 | @send "Welcome! What is your nickname?" 63 | 64 | handleNickname: (nickname) => 65 | if isNicknameLegal(nickname) 66 | @nickname = nickname 67 | @lineBuffer.removeAllListeners "line" 68 | @lineBuffer.on "line", @handleChat 69 | @send "Welcome to the chat, " + nickname + "!" 70 | @emit "join", this 71 | else 72 | @send "Sorry, but that nickname is not legal or is already in use!" 73 | @send "What is your nickname?" 74 | 75 | handleChat: (line) => 76 | @emit "chat", this, line 77 | 78 | handleDisconnect: => 79 | @emit "leave", this 80 | 81 | send: (data) => 82 | @socket.write data + "\r\n" 83 | 84 | 85 | class SocketLineBuffer extends EventEmitter 86 | constructor: (socket) -> 87 | EventEmitter.call this 88 | @socket = socket 89 | @buffer = "" 90 | @socket.on "data", @handleData 91 | 92 | handleData: (data) => 93 | console.log "Handling data", data 94 | i = 0 95 | 96 | while i < data.length 97 | char = data.charAt(i) 98 | @buffer += char 99 | if char is "\n" 100 | @buffer = @buffer.replace("\r\n", "") 101 | @buffer = @buffer.replace("\n", "") 102 | @emit "line", @buffer 103 | console.log "incoming line: #{@buffer}" 104 | @buffer = "" 105 | i++ 106 | 107 | server = new ChatServer() -------------------------------------------------------------------------------- /examples/client.coffee: -------------------------------------------------------------------------------- 1 | Canvas = (div, id, width=600, height=300) -> 2 | canvas_html = """ 3 | 4 | 5 | """ 6 | div.append canvas_html 7 | 8 | canvas = document.getElementById(id) 9 | ctx = canvas.getContext("2d") 10 | 11 | moveTo = (point) -> 12 | [x,y] = point 13 | ctx.moveTo Math.floor(x), Math.floor(y) 14 | 15 | lineTo = (point) -> 16 | [x,y] = point 17 | ctx.lineTo Math.floor(x), Math.floor(y) 18 | 19 | clear: -> 20 | canvas.width = width 21 | 22 | segment: (color, point1, point2) -> 23 | ctx.strokeStyle = color 24 | ctx.lineWidth = 2 25 | ctx.beginPath() 26 | moveTo point1 27 | lineTo point2 28 | ctx.stroke() 29 | ctx.closePath() 30 | 31 | draw_polygon: (color, point1, more_points...) -> 32 | ctx.fillStyle = color 33 | ctx.beginPath() 34 | moveTo point1 35 | for point in more_points 36 | lineTo point 37 | lineTo point1 38 | ctx.fill() 39 | ctx.closePath() 40 | 41 | outline_triangle: (color, point1, point2, point3) -> 42 | ctx.strokeStyle = color 43 | ctx.lineWidth = 1 44 | ctx.beginPath() 45 | moveTo point1 46 | lineTo point2 47 | lineTo point3 48 | lineTo point1 49 | ctx.stroke() 50 | ctx.closePath() 51 | 52 | Linkage = -> 53 | width = 700 54 | height = 450 55 | x_offset = 0 56 | y_distort = 0 57 | rescale = (point) -> 58 | [x, y] = point 59 | x += x_offset 60 | y *= y_distort 61 | [x * 20 + 100, height - y * 20 - 10] 62 | 63 | canvas = Canvas $("#linkage"), "linkage_canvas", width, height 64 | a = 17 65 | b = 5 66 | h = 0 67 | dh = 0.05 68 | dt = 2 69 | 70 | paused = false 71 | 72 | recording = true 73 | path1 = [] 74 | path2 = [] 75 | path3 = [] 76 | path4 = [] 77 | draw = -> 78 | if paused 79 | return 80 | 81 | canvas.clear() 82 | c = Math.sqrt a*a - b*b 83 | d = Math.sqrt c*c + h*h # distance from A to center of rhombus 84 | e = Math.sqrt b*b - h*h # distance from C to center of rhombus 85 | # rhomubs is DBCE, D is top point, A is bottom point 86 | 87 | pos_neg = 88 | if h > 0 89 | 1 90 | else 91 | -1 92 | i = pos_neg * h 93 | 94 | A = [0, 0] 95 | B = [0, d-i] 96 | C = [e, d] 97 | D = [0, d+i] 98 | E = [-e, d] 99 | Y = [a + c/2, c] 100 | cos = c / (d+i) 101 | sin = Math.sqrt(1 - cos*cos) * pos_neg 102 | 103 | rotate = (point) -> 104 | [x, y] = point 105 | [x*cos + y*sin, y*cos - x*sin] 106 | 107 | segment = (color, point1, point2) -> 108 | canvas.segment color, rescale(point1), rescale(point2) 109 | 110 | show = -> 111 | segment "pink", A, B 112 | segment "blue", E, D 113 | segment "blue", D, C 114 | segment "blue", C, B 115 | segment "blue", B, E 116 | segment "green", A, E 117 | segment "green", A, C 118 | 119 | A = rotate A 120 | B = rotate B 121 | C = rotate C 122 | D = rotate D 123 | E = rotate E 124 | 125 | x_offset = 12 126 | y_distort = 1 127 | if recording 128 | path1.push B 129 | path2.push D 130 | path3.push C 131 | path4.push E 132 | 133 | for path in [path1, path2, path3, path4] 134 | for i in [0...path.length-1] 135 | segment "pink", path[i], path[i+1] 136 | show() 137 | 138 | h += dh 139 | if h < -b or h > b 140 | recording = false if h < 0 141 | dh *= -1 142 | h += dh 143 | setTimeout(draw, dt) 144 | draw() 145 | button = $("#linkage_pause") 146 | button.click -> 147 | if paused 148 | paused = false 149 | draw() 150 | button.val "pause" 151 | else 152 | paused = true 153 | button.val "resume" 154 | 155 | PythagFolding = -> 156 | x_offset = 50 157 | height = 130 158 | rescale = (point) -> 159 | [x, y] = point 160 | [x * 10 + x_offset, height - y * 10 - 10] 161 | 162 | canvas = Canvas $("#pythag_fold1"), "pythag_fold1_canvas", 400, height 163 | 164 | a = 8.5 165 | b = 11 166 | 167 | A = [0,0] 168 | B = [0,b] 169 | C = [a,b] 170 | D = [a,0] 171 | 172 | draw_poly = (color, points...) -> 173 | scaled_points = (rescale point for point in points) 174 | canvas.draw_polygon color, scaled_points... 175 | 176 | segment = (color, point1, point2) -> 177 | canvas.segment color, rescale(point1), rescale(point2) 178 | 179 | draw_poly "lightblue", A, B, C, D 180 | 181 | x_offset += 120 182 | 183 | D = [a * (a*a - b*b) / (a*a + b*b), a * (2*a*b) / (a*a + b*b)] 184 | draw_poly "lightblue", A, B, C 185 | draw_poly "red", A, D, C 186 | segment "black", A, C 187 | 188 | x_offset += 100 189 | 190 | D = [a, 0] 191 | draw_poly "lightblue", A, B, C 192 | draw_poly "lightblue", A, C, D 193 | segment "blue", A, C 194 | 195 | x_offset = 50 196 | canvas = Canvas $("#pythag_fold2"), "pythag_fold2_canvas", 800, height 197 | draw_poly "lightblue", A, B, C 198 | draw_poly "lightblue", A, C, D 199 | segment "blue", A, C 200 | 201 | x_offset += 100 202 | E = [0, a] 203 | F = [a, a] 204 | draw_poly "lightblue", A, B, C, F 205 | segment "blue", A, C 206 | draw_poly "red", A, E, F 207 | segment "black", A, F 208 | segment "pink", E, F 209 | 210 | x_offset += 100 211 | G = [a, 2*a-b] 212 | H = [0, 2*a-b] 213 | K = [2*a-b, 2*a-b] 214 | draw_poly "lightblue", A, E, F 215 | draw_poly "red", E, F, G, H 216 | segment "black", E, F 217 | segment "pink", H, K 218 | 219 | x_offset += 100 220 | L = [2*a-b, a] 221 | draw_poly "lightblue", A, E, F 222 | draw_poly "lightblue", E, F, K, H 223 | draw_poly "red", L, K, F 224 | segment "pink", H, K 225 | segment "pink", L, K 226 | segment "black", K, F 227 | 228 | x_offset += 100 229 | I = [3*a-2*b, a] 230 | draw_poly "lightblue", A, H, K 231 | draw_poly "lightblue", E, L, K, H 232 | draw_poly "red", L, K, I 233 | segment "pink", H, K 234 | segment "pink", L, K 235 | segment "pink", K, I 236 | segment "black", K, L 237 | 238 | x_offset += 100 239 | J = [3*a-2*b, 2*a-b] 240 | draw_poly "lightblue", A, H, K 241 | draw_poly "lightblue", E, I, K, H 242 | draw_poly "red", J, K, I 243 | segment "pink", H, K 244 | segment "pink", I, J 245 | segment "black", I, K 246 | 247 | x_offset += 100 248 | M = [2*a-b, b] 249 | N = [a, 2*a-b] 250 | O = [a, 3*a-2*b] 251 | draw_poly "lightblue", A, B, C, D 252 | segment "blue", A, C 253 | segment "blue", A, F 254 | segment "blue", E, F 255 | segment "green", M, L 256 | segment "blue", L, K 257 | segment "green", K, N 258 | segment "blue", I, K 259 | segment "green", K, O 260 | segment "green", I, M 261 | segment "green", M, F 262 | 263 | x_offset = 50 264 | canvas = Canvas $("#pythag_fold3"), "pythag_fold3_canvas", 600, height 265 | 266 | for i in [1..2] 267 | draw_poly "lightgreen", A, B, C 268 | draw_poly "yellow", A, C, D 269 | segment "blue", A, C 270 | x_offset += 100 271 | 272 | draw_poly "black", A, B, M, K, N, D 273 | draw_poly "cyan", M, C, N, K 274 | segment "blue", E, F 275 | 276 | blue = "#AAAADD" 277 | for i in [1..2] 278 | x_offset += 100 279 | D = [a, 0] 280 | P = [a * a / b, a] 281 | draw_poly blue, A, P, F, D 282 | draw_poly "lightblue", P, F, C 283 | draw_poly "red", A, E, P 284 | draw_poly "pink", E, B, C, P 285 | 286 | PythagProof = -> 287 | canvas = Canvas $("#pythag_proof"), "pythag_canvas", 350, 350 288 | 289 | rescale = (point) -> 290 | [x, y] = point 291 | y -= 4 292 | [x * 10 + 150, -y * 10 + 170] 293 | 294 | draw_poly = (poly, color, points) -> 295 | coords = (points[poly.charAt(i)] for i in [0...poly.length]) 296 | canvas.draw_polygon color, coords... 297 | 298 | 299 | a = 8.5 300 | b = 2.5 301 | c = (a * b) / (a + b) 302 | points = 303 | A: -> [-a, 0] 304 | B: -> [-a-b, 0] 305 | C: -> [-a, b+c] 306 | D: -> [-a, b] 307 | E: -> [-a-b, b] 308 | F: -> [-a-b, a+b] 309 | G: -> [-a, a+b] 310 | H: -> [0, a+b] 311 | I: -> [a, a+b] 312 | J: -> [a+b, a+b] 313 | K: -> [a+b, a] 314 | L: -> [a, a] 315 | M: -> [b, a+b] 316 | N: -> [a+b, 2*a+b] 317 | O: -> [2*a+b, a] 318 | P: -> [a+b, c] 319 | Q: -> [a, 0] 320 | R: -> [a, -a] 321 | S: -> [0, c-a] 322 | T: -> [0, -a] 323 | U: -> [0, 0] 324 | V: -> [b, a+b+c] 325 | W: -> [0, b] 326 | X: -> [-b, -a] 327 | Y: -> [-b, 0] 328 | 329 | blue = "#AAAADD" 330 | 331 | triangles = [ 332 | ["EDAB", "cyan"] 333 | ["ECGF", "pink"] 334 | ["EDC", "lightblue"] 335 | ["AUG", "yellow"] 336 | ["UGH", "lightgreen"] 337 | # 338 | ["STRQ", blue] 339 | ["UQS", "red"] 340 | # 341 | ["HIQ", "lightgreen"] 342 | ["HMV", "lightblue"] 343 | ["VNJM", blue] 344 | ["NKO", "yellow"] 345 | ["OKP", "red"] 346 | ["LKPQ", "pink"] 347 | ["IJKL", "cyan"] 348 | ] 349 | 350 | redraw = -> 351 | canvas.clear() 352 | scaled_points = {} 353 | for vertex, point of points 354 | scaled_points[vertex] = rescale point() 355 | 356 | segment = (segment, color) -> 357 | point1 = scaled_points[segment.charAt(0)] 358 | point2 = scaled_points[segment.charAt(1)] 359 | canvas.segment color, point1, point2 360 | 361 | for triangle in triangles 362 | [vertices, color] = triangle 363 | draw_poly vertices, color, scaled_points 364 | 365 | 366 | # black > blue > green 367 | segment "EH", "black" 368 | segment "HQ", "black" 369 | segment "HN", "black" 370 | segment "NO", "black" 371 | segment "OQ", "black" 372 | segment "GU", "black" 373 | 374 | segment "BU", "blue" 375 | segment "UH", "blue" 376 | segment "HF", "blue" 377 | segment "FB", "blue" 378 | 379 | segment "QI", "blue" 380 | segment "NK", "blue" 381 | 382 | segment "ED", "orange" 383 | segment "DW", "green" 384 | segment "UQ", "green" 385 | segment "AG", "blue" 386 | 387 | segment "XY", "green" 388 | segment "XT", "orange" 389 | segment "XQ", "black" 390 | segment "TR", "green" 391 | segment "QR", "green" 392 | segment "UT", "green" 393 | 394 | redraw() 395 | 396 | TwelveTriangles = -> 397 | canvas = Canvas $("#twelve_triangles"), "twelve_triangles_canvas" 398 | skew = 0 399 | height = 1 400 | 401 | rescale = (point) -> 402 | [x, y] = point 403 | x -= 4 # keep centroid centered 404 | x += y * skew 405 | x /= height 406 | y *= height 407 | [x * 40 + 300, -y * 40 + 150] 408 | 409 | draw_triangle = (triangle, color, points) -> 410 | v0 = triangle.charAt(0) 411 | v1 = triangle.charAt(1) 412 | v2 = triangle.charAt(2) 413 | canvas.draw_polygon color, points[v0], points[v1], points[v2] 414 | 415 | draw = -> 416 | points = 417 | A: [0, 2] 418 | B: [2, 2] 419 | C: [6, 2] 420 | D: [3, 1] 421 | E: [0, 0] 422 | F: [4, 0] 423 | G: [6, 0] 424 | H: [3, -1] 425 | I: [0, -2] 426 | J: [2, -2] 427 | K: [6, -2] 428 | 429 | 430 | triangles = [ 431 | ["ABE", "red"] 432 | ["BED", "green"] 433 | ["BCD", "blue"] 434 | ["DEF", "blue"] 435 | ["DFC", "green"] 436 | ["CFG", "red"] 437 | # 438 | ["EFH", "pink"] 439 | ["FHK", "lightgreen"] 440 | ["FGK", "lightblue"] 441 | ["EIJ", "lightblue"] 442 | ["EJH", "lightgreen"] 443 | ["JHK", "pink"] 444 | ] 445 | 446 | redraw = -> 447 | canvas.clear() 448 | scaled_points = {} 449 | for vertex, point of points 450 | scaled_points[vertex] = rescale point 451 | 452 | for triangle in triangles 453 | [vertices, color] = triangle 454 | draw_triangle vertices, color, scaled_points 455 | 456 | vertices = (scaled_points[vertex] for vertex in ["E", "C", "K"]) 457 | canvas.outline_triangle "black", vertices... 458 | 459 | redraw() 460 | 461 | $("#more_skew").click -> 462 | skew += 0.2 463 | redraw() 464 | 465 | $("#less_skew").click -> 466 | skew -= 0.2 467 | redraw() 468 | 469 | $("#taller").click -> 470 | height += 0.1 471 | redraw() 472 | 473 | $("#wider").click -> 474 | height -= 0.1 475 | redraw() 476 | 477 | draw() 478 | 479 | MultiplicationTables = -> 480 | colors = 481 | 2: "#EEEEEE" 482 | 4: "#CCCCCC" 483 | 8: "#AAAAAA" 484 | 16: "#999999" 485 | 3: "#EEAAAA" 486 | 6: "#DDAAAA" 487 | 9: "#CCAAAA" 488 | 11: "DD6666" 489 | 5: "#AAEEAA" 490 | 10: "#AADDAA" 491 | 15: "#AACCAA" 492 | 20: "#AABBAA" 493 | 7: "#AAAADD" 494 | 14: "#AAAACC" 495 | 12: "#00FFFF" 496 | 13: "#FFFF00" 497 | 17: "#FF00FF" 498 | 18: "#55DD55" 499 | 19: "#DD00DD" 500 | 501 | set_color = (n, width) -> 502 | color = "white" 503 | for delta in [0, 1] 504 | i = width + delta 505 | if n % i == 0 and colors[i] 506 | return colors[i] 507 | for i in [20..1] by -1 508 | if n % i == 0 and width % i == 0 and colors[i] 509 | return colors[i] 510 | if n % 2 == 0 511 | return colors[2] 512 | color 513 | 514 | max_prime_factor = (n) -> 515 | return n if n <= 15 516 | max = null 517 | for i in [2...n] 518 | break if i * 2 > n 519 | if n % i == 0 520 | max = i 521 | return max if i * i >= n 522 | return max if max 523 | return n if n <= 23 524 | return max_prime_factor(n+1) 525 | 526 | width = 10 527 | special_number = 10 528 | draw = -> 529 | html = """ 530 |

#{special_number}

531 | """ 532 | for i in [1..special_number] 533 | if special_number % i == 0 534 | html += """ 535 | #{i} * #{special_number / i} = #{special_number}
536 | """ 537 | html += "
" 538 | for i in [1..10] 539 | html += """ 540 | #{special_number} * #{i} = #{special_number * i}
541 | """ 542 | facts = $ "
" 543 | facts.html html 544 | facts.css "border", "1px black solid" 545 | $("#multi_right").html facts 546 | 547 | table = $("#multiplication") 548 | table.empty() 549 | height = Math.floor 169 / width 550 | max = null 551 | for n in [2, 3, 4, 5, 7, 11, 13, 17] 552 | if width % n == 0 553 | max = n 554 | 555 | 556 | for i in [0...height] 557 | tr = $ "" 558 | table.append tr 559 | for j in [0...width] 560 | n = i * width + j + 1 561 | color = set_color(n, width) 562 | style = "background: #{color}" 563 | td = $ "#{n}" 564 | if max and (j + 1) % max == 0 565 | td.css "border-right", "2px black solid" 566 | td.attr "height", 40 567 | td.attr "width", 40 568 | td.attr "font-size", "13px" 569 | if n == special_number 570 | td.css "border", "5px black solid" 571 | else if special_number % n == 0 572 | td.css "border", "3px blue solid" 573 | else if n % special_number == 0 574 | td.css "border", "3px red solid" 575 | if (n % width == 0) or (n % (width-1) == 0) or (n % (width+1) == 0) 576 | td.css "font-weight", "bold" 577 | f = (n) -> 578 | td.click -> 579 | special_number = n 580 | width = max_prime_factor n 581 | draw() 582 | f(n) 583 | tr.append td 584 | td = $ " #{width} * #{i+1} = #{(i+1)*(width)} " 585 | td.css "border", "1px blue solid" 586 | tr.append td 587 | 588 | $("#multi_wide").click -> 589 | width += 1 590 | special_number = width 591 | draw() 592 | 593 | $("#multi_narrow").click -> 594 | width -= 1 595 | special_number = width 596 | draw() 597 | 598 | $("#multi_plus_one").click -> 599 | special_number += 1 600 | width = max_prime_factor special_number 601 | draw() 602 | 603 | draw() 604 | 605 | jQuery(document).ready -> 606 | $("body").css "width", 800 607 | Linkage() 608 | PythagProof() 609 | PythagFolding() 610 | TwelveTriangles() 611 | MultiplicationTables() -------------------------------------------------------------------------------- /examples/coffeekup.coffee: -------------------------------------------------------------------------------- 1 | # NOTE!! This is borrowed from https://github.com/mauricemach/coffeekup, and it's 2 | # here only for demonstration purposes. 3 | 4 | # **CoffeeKup** lets you to write HTML templates in 100% pure 5 | # [CoffeeScript](http://coffeescript.org). 6 | # 7 | # You can run it on [node.js](http://nodejs.org) or the browser, or compile your 8 | # templates down to self-contained javascript functions, that will take in data 9 | # and options and return generated HTML on any JS runtime. 10 | # 11 | # The concept is directly stolen from the amazing 12 | # [Markaby](http://markaby.rubyforge.org/) by Tim Fletcher and why the lucky 13 | # stiff. 14 | 15 | if window? 16 | coffeekup = window.CoffeeKup = {} 17 | coffee = if CoffeeScript? then CoffeeScript else null 18 | else 19 | coffeekup = exports 20 | coffee = require 'coffee-script' 21 | 22 | coffeekup.version = '0.3.1edge' 23 | 24 | # Values available to the `doctype` function inside a template. 25 | # Ex.: `doctype 'strict'` 26 | coffeekup.doctypes = 27 | 'default': '' 28 | '5': '' 29 | 'xml': '' 30 | 'transitional': '' 31 | 'strict': '' 32 | 'frameset': '' 33 | '1.1': '', 34 | 'basic': '' 35 | 'mobile': '' 36 | 'ce': '' 37 | 38 | # CoffeeScript-generated JavaScript may contain anyone of these; but when we 39 | # take a function to string form to manipulate it, and then recreate it through 40 | # the `Function()` constructor, it loses access to its parent scope and 41 | # consequently to any helpers it might need. So we need to reintroduce these 42 | # inside any "rewritten" function. 43 | coffeescript_helpers = """ 44 | var __slice = Array.prototype.slice; 45 | var __hasProp = Object.prototype.hasOwnProperty; 46 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 47 | var __extends = function(child, parent) { 48 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 49 | function ctor() { this.constructor = child; } 50 | ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; 51 | return child; }; 52 | var __indexOf = Array.prototype.indexOf || function(item) { 53 | for (var i = 0, l = this.length; i < l; i++) { 54 | if (this[i] === item) return i; 55 | } return -1; }; 56 | """.replace /\n/g, '' 57 | 58 | # Private HTML element reference. 59 | # Please mind the gap (1 space at the beginning of each subsequent line). 60 | elements = 61 | # Valid HTML 5 elements requiring a closing tag. 62 | # Note: the `var` element is out for obvious reasons, please use `tag 'var'`. 63 | regular: 'a abbr address article aside audio b bdi bdo blockquote body button 64 | canvas caption cite code colgroup datalist dd del details dfn div dl dt em 65 | fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup 66 | html i iframe ins kbd label legend li map mark menu meter nav noscript object 67 | ol optgroup option output p pre progress q rp rt ruby s samp script section 68 | select small span strong style sub summary sup table tbody td textarea tfoot 69 | th thead time title tr u ul video' 70 | 71 | # Valid self-closing HTML 5 elements. 72 | void: 'area base br col command embed hr img input keygen link meta param 73 | source track wbr' 74 | 75 | obsolete: 'applet acronym bgsound dir frameset noframes isindex listing 76 | nextid noembed plaintext rb strike xmp big blink center font marquee multicol 77 | nobr spacer tt' 78 | 79 | obsolete_void: 'basefont frame' 80 | 81 | # Create a unique list of element names merging the desired groups. 82 | merge_elements = (args...) -> 83 | result = [] 84 | for a in args 85 | for element in elements[a].split ' ' 86 | result.push element unless element in result 87 | result 88 | 89 | # Public/customizable list of possible elements. 90 | # For each name in this list that is also present in the input template code, 91 | # a function with the same name will be added to the compiled template. 92 | coffeekup.tags = merge_elements 'regular', 'obsolete', 'void', 'obsolete_void' 93 | 94 | # Public/customizable list of elements that should be rendered self-closed. 95 | coffeekup.self_closing = merge_elements 'void', 'obsolete_void' 96 | 97 | # This is the basic material from which compiled templates will be formed. 98 | # It will be manipulated in its string form at the `coffeekup.compile` function 99 | # to generate the final template function. 100 | skeleton = (data = {}) -> 101 | # Whether to generate formatted HTML with indentation and line breaks, or 102 | # just the natural "faux-minified" output. 103 | data.format ?= off 104 | 105 | # Whether to autoescape all content or let you handle it on a case by case 106 | # basis with the `h` function. 107 | data.autoescape ?= off 108 | 109 | # Internal CoffeeKup stuff. 110 | __ck = 111 | buffer: [] 112 | 113 | esc: (txt) -> 114 | if data.autoescape then h(txt) else String(txt) 115 | 116 | tabs: 0 117 | 118 | repeat: (string, count) -> Array(count + 1).join string 119 | 120 | indent: -> text @repeat(' ', @tabs) if data.format 121 | 122 | # Adapter to keep the builtin tag functions DRY. 123 | tag: (name, args) -> 124 | combo = [name] 125 | combo.push i for i in args 126 | tag.apply data, combo 127 | 128 | render_idclass: (str) -> 129 | classes = [] 130 | 131 | for i in str.split '.' 132 | if '#' in i 133 | id = i.replace '#', '' 134 | else 135 | classes.push i unless i is '' 136 | 137 | text " id=\"#{id}\"" if id 138 | 139 | if classes.length > 0 140 | text " class=\"" 141 | for c in classes 142 | text ' ' unless c is classes[0] 143 | text c 144 | text '"' 145 | 146 | render_attrs: (obj, prefix = '') -> 147 | for k, v of obj 148 | # `true` is rendered as `selected="selected"`. 149 | v = k if typeof v is 'boolean' and v 150 | 151 | # Functions are rendered in an executable form. 152 | v = "(#{v}).call(this);" if typeof v is 'function' 153 | 154 | # Prefixed attribute. 155 | if typeof v is 'object' and v not instanceof Array 156 | # `data: {icon: 'foo'}` is rendered as `data-icon="foo"`. 157 | @render_attrs(v, prefix + k + '-') 158 | # `undefined`, `false` and `null` result in the attribute not being rendered. 159 | else if v 160 | # strings, numbers, arrays and functions are rendered "as is". 161 | text " #{prefix + k}=\"#{@esc(v)}\"" 162 | 163 | render_contents: (contents) -> 164 | switch typeof contents 165 | when 'string', 'number', 'boolean' 166 | text @esc(contents) 167 | when 'function' 168 | text '\n' if data.format 169 | @tabs++ 170 | result = contents.call data 171 | if typeof result is 'string' 172 | @indent() 173 | text @esc(result) 174 | text '\n' if data.format 175 | @tabs-- 176 | @indent() 177 | 178 | render_tag: (name, idclass, attrs, contents) -> 179 | @indent() 180 | 181 | text "<#{name}" 182 | @render_idclass(idclass) if idclass 183 | @render_attrs(attrs) if attrs 184 | 185 | if name in @self_closing 186 | text ' />' 187 | text '\n' if data.format 188 | else 189 | text '>' 190 | 191 | @render_contents(contents) 192 | 193 | text "" 194 | text '\n' if data.format 195 | 196 | null 197 | 198 | tag = (name, args...) -> 199 | for a in args 200 | switch typeof a 201 | when 'function' 202 | contents = a 203 | when 'object' 204 | attrs = a 205 | when 'number', 'boolean' 206 | contents = a 207 | when 'string' 208 | if args.length is 1 209 | contents = a 210 | else 211 | if a is args[0] 212 | idclass = a 213 | else 214 | contents = a 215 | 216 | __ck.render_tag(name, idclass, attrs, contents) 217 | 218 | yield = (f) -> 219 | temp_buffer = [] 220 | old_buffer = __ck.buffer 221 | __ck.buffer = temp_buffer 222 | f() 223 | __ck.buffer = old_buffer 224 | temp_buffer.join '' 225 | 226 | h = (txt) -> 227 | String(txt).replace(/&/g, '&') 228 | .replace(//g, '>') 230 | .replace(/"/g, '"') 231 | 232 | doctype = (type = 'default') -> 233 | text __ck.doctypes[type] 234 | text '\n' if data.format 235 | 236 | text = (txt) -> 237 | __ck.buffer.push String(txt) 238 | null 239 | 240 | comment = (cmt) -> 241 | text "" 242 | text '\n' if data.format 243 | 244 | coffeescript = (param) -> 245 | switch typeof param 246 | # `coffeescript -> alert 'hi'` becomes: 247 | # `` 248 | when 'function' 249 | script "#{__ck.coffeescript_helpers}(#{param}).call(this);" 250 | # `coffeescript "alert 'hi'"` becomes: 251 | # `` 252 | when 'string' 253 | script type: 'text/coffeescript', -> param 254 | # `coffeescript src: 'script.coffee'` becomes: 255 | # `` 256 | when 'object' 257 | param.type = 'text/coffeescript' 258 | script param 259 | 260 | # Conditional IE comments. 261 | ie = (condition, contents) -> 262 | __ck.indent() 263 | 264 | text "" 267 | text '\n' if data.format 268 | 269 | null 270 | 271 | # Stringify the skeleton and unwrap it from its enclosing `function(){}`, then 272 | # add the CoffeeScript helpers. 273 | skeleton = String(skeleton) 274 | .replace(/function\s*\(.*\)\s*\{/, '') 275 | .replace(/return null;\s*\}$/, '') 276 | 277 | skeleton = coffeescript_helpers + skeleton 278 | 279 | # Compiles a template into a standalone JavaScript function. 280 | coffeekup.compile = (template, options = {}) -> 281 | # The template can be provided as either a function or a CoffeeScript string 282 | # (in the latter case, the CoffeeScript compiler must be available). 283 | if typeof template is 'function' then template = String(template) 284 | else if typeof template is 'string' and coffee? 285 | template = coffee.compile template, bare: yes 286 | template = "function(){#{template}}" 287 | 288 | # If an object `hardcode` is provided, insert the stringified value 289 | # of each variable directly in the function body. This is a less flexible but 290 | # faster alternative to the standard method of using `with` (see below). 291 | hardcoded_locals = '' 292 | 293 | if options.hardcode 294 | for k, v of options.hardcode 295 | if typeof v is 'function' 296 | # Make sure these functions have access to `data` as `@/this`. 297 | hardcoded_locals += "var #{k} = function(){return (#{v}).apply(data, arguments);};" 298 | else hardcoded_locals += "var #{k} = #{JSON.stringify v};" 299 | 300 | # Add a function for each tag this template references. We don't want to have 301 | # all hundred-odd tags wasting space in the compiled function. 302 | tag_functions = '' 303 | tags_used = [] 304 | 305 | for t in coffeekup.tags 306 | if template.indexOf(t) > -1 or hardcoded_locals.indexOf(t) > -1 307 | tags_used.push t 308 | 309 | tag_functions += "var #{tags_used.join ','};" 310 | for t in tags_used 311 | tag_functions += "#{t} = function(){return __ck.tag('#{t}', arguments);};" 312 | 313 | # Main function assembly. 314 | code = tag_functions + hardcoded_locals + skeleton 315 | 316 | code += "__ck.doctypes = #{JSON.stringify coffeekup.doctypes};" 317 | code += "__ck.coffeescript_helpers = #{JSON.stringify coffeescript_helpers};" 318 | code += "__ck.self_closing = #{JSON.stringify coffeekup.self_closing};" 319 | 320 | # If `locals` is set, wrap the template inside a `with` block. This is the 321 | # most flexible but slower approach to specifying local variables. 322 | code += 'with(data.locals){' if options.locals 323 | code += "(#{template}).call(data);" 324 | code += '}' if options.locals 325 | code += "return __ck.buffer.join('');" 326 | 327 | new Function('data', code) 328 | 329 | cache = {} 330 | 331 | # Template in, HTML out. Accepts functions or strings as does `coffeekup.compile`. 332 | # 333 | # Accepts an option `cache`, by default `false`. If set to `false` templates will 334 | # be recompiled each time. 335 | # 336 | # `options` is just a convenience parameter to pass options separately from the 337 | # data, but the two will be merged and passed down to the compiler (which uses 338 | # `locals` and `hardcode`), and the template (which understands `locals`, `format` 339 | # and `autoescape`). 340 | coffeekup.render = (template, data = {}, options = {}) -> 341 | data[k] = v for k, v of options 342 | data.cache ?= off 343 | 344 | if data.cache and cache[template]? then tpl = cache[template] 345 | else if data.cache then tpl = cache[template] = coffeekup.compile(template, data) 346 | else tpl = coffeekup.compile(template, data) 347 | tpl(data) 348 | 349 | unless window? 350 | coffeekup.adapters = 351 | # Legacy adapters for when CoffeeKup expected data in the `context` attribute. 352 | simple: coffeekup.render 353 | meryl: coffeekup.render 354 | 355 | express: 356 | TemplateError: class extends Error 357 | constructor: (@message) -> 358 | Error.call this, @message 359 | Error.captureStackTrace this, arguments.callee 360 | name: 'TemplateError' 361 | 362 | compile: (template, data) -> 363 | # Allows `partial 'foo'` instead of `text @partial 'foo'`. 364 | data.hardcode ?= {} 365 | data.hardcode.partial = -> 366 | text @partial.apply @, arguments 367 | 368 | TemplateError = @TemplateError 369 | try tpl = coffeekup.compile(template, data) 370 | catch e then throw new TemplateError "Error compiling #{data.filename}: #{e.message}" 371 | 372 | return -> 373 | try tpl arguments... 374 | catch e then throw new TemplateError "Error rendering #{data.filename}: #{e.message}" -------------------------------------------------------------------------------- /examples/game_of_life.coffee: -------------------------------------------------------------------------------- 1 | # This is Conway's Game of Life, with some tests interspersed. 2 | # It is written in Coffeescript. 3 | # 4 | # - [live demo](http://shpaml.webfactional.com/misc/Game-Of-Life/game.html) 5 | # - [Github repo](https://github.com/showell/Game-Of-Life/blob/master/game.coffee) 6 | 7 | #
8 | # At an abstract level, the Game of Live just involves cells 9 | # that mutate according to their own liveness and the number of 10 | # alive neighbors. Details about the world's geometry or exact 11 | # rules for survival can be configured later. 12 | # 13 | # IMPORTANT NOTE: This function returns another function. The 14 | # function it returns will be called several times during the 15 | # simulation (once per generation). 16 | # 17 | abstract_game_of_life = (world_factory, point_lives_next_gen) -> 18 | # This method controls the functional mapping of an old world to a 19 | # new world, across one generation. 20 | return (old_world) -> 21 | # We will create a new world and populate it. 22 | new_world = world_factory() 23 | # No fancy algorithm here: we just iterate the entire world. 24 | for cell in old_world.cells() 25 | is_alive = old_world.alive(cell) 26 | n = old_world.num_alive_neighbors(cell) 27 | fate = point_lives_next_gen(is_alive, n) 28 | new_world.set(cell, fate) 29 | new_world 30 | 31 | #
32 | # To keep things simple, we roll our own assert method. 33 | assert = (cond) -> 34 | if !cond 35 | debugger 36 | throw("assertion error") 37 | 38 | #
39 | # Inling unit testing. We don't need a complicated world to test 40 | # out the basic logic of an abstract game. In fact, we use a 41 | # one-cell world. 42 | do -> 43 | world_factory = -> 44 | cells = [false] 45 | obj = 46 | cells: 47 | -> [0] 48 | num_alive_neighbors: 49 | (i) -> 0 50 | alive: 51 | (i) -> cells[i] 52 | set: 53 | (i, fate) -> cells[0] = fate 54 | status: 55 | -> cells[0] 56 | toggle = (alive, n) -> !alive 57 | f = abstract_game_of_life(world_factory, toggle) 58 | w = world_factory() 59 | assert !w.status() 60 | w = f(w) 61 | assert w.status() 62 | w = f(w) 63 | assert !w.status() 64 | 65 | 66 | #
67 | # Out internal data structure is a simple hash, with keys that are comma-delimited 68 | # coordinates. We have a light abstraction here (set/alive), which should be 69 | # durable across other data structures we might choose in the future. 70 | data_2d = -> 71 | hash = {} 72 | key = (point) -> 73 | [x, y] = point 74 | "#{x},#{y}" 75 | obj = 76 | set: (point, fate) -> 77 | hash[key(point)] = fate 78 | alive: (point) -> 79 | hash[key(point)] 80 | 81 | #
82 | # Testing an object with two methods is fairly straightforward. 83 | do -> 84 | d = data_2d() 85 | assert !d.alive([5,7]) 86 | d.set([5,7], true) 87 | assert d.alive([5,7]) 88 | 89 | #
90 | # Our geometry is toroidal, which is a fancy term for saying we 91 | # work like PacMan. The left/right edges of the world are virtually 92 | # connected to each other, as are the bottom/top. 93 | get_toroidal_neighbors = (point, width, height) -> 94 | [x, y] = point 95 | # Perhaps a little overly clever in the code layout here? These are 96 | # actually 1-D arrays with eight values each. 97 | x_deltas = [ 98 | -1, 0, 1, 99 | -1, 1, 100 | -1, 0, 1 101 | ] 102 | y_deltas = [ 103 | -1, -1, -1, 104 | 0, 0, 105 | 1, 1, 1 106 | ] 107 | 108 | # Now we will return the coordinates of the eight neighbors, using the 109 | # relative values from above. 110 | x_deltas.map (dx, i) -> 111 | dy = y_deltas[i] 112 | # A little gotch in Javascript in modular 113 | # arithmetic is that -1 % 10 is -1, not 9 as you expect. Since dx can 114 | # be negative, we have to add width first. 115 | xx = (x + dx + width) % width 116 | yy = (y + dy + height) % height 117 | [xx, yy] 118 | 119 | #
120 | # Testing. Writing in a very functional style makes code easy to test. 121 | do -> 122 | result = get_toroidal_neighbors([1,1], 10, 10) 123 | expected = [ 124 | [0,0], [1,0], [2,0], 125 | [0,1], [2,1], 126 | [0,2], [1,2], [2,2] 127 | ] 128 | assert(result.toString() == expected.toString()) 129 | 130 | #
131 | # Our "world" object makes this program implement a concrete 132 | # version of the Game of Life. There are many variations. 133 | # 134 | # The objects that use "world" are still abstracted from many 135 | # details of the game. But our implementation here restricts 136 | # us to a rectangular two-dimensional toroidal geometry. 137 | pacman_world = (width, height) -> 138 | # Create our internal data structure and populate it. 139 | data = data_2d() 140 | # We use "do ->" to make sure we don't pollute our scope 141 | # for one-time setup. 142 | cells = do -> 143 | points = [] 144 | for x in [0...width] 145 | for y in [0...height] 146 | points.push([x,y]) 147 | points 148 | 149 | # Report the number of alive neighbors for any cell. This is 150 | # just glue on top of our internal data structure and an 151 | # external function. 152 | num_alive_neighbors = (loc) -> 153 | num = 0 154 | neighbors = get_toroidal_neighbors(loc, width, height) 155 | for n in neighbors 156 | num += 1 if data.alive(n) 157 | num 158 | 159 | # Set up our interface to the outside world. 160 | obj = 161 | alive: (x,y) -> data.alive(x,y) 162 | set: (x,y) -> data.set(x,y) 163 | cells: -> cells 164 | num_alive_neighbors: (point) -> num_alive_neighbors(point) 165 | 166 | #
167 | # Testing. 168 | do -> 169 | w = pacman_world(10, 10) 170 | assert(w.cells().length == 100) 171 | w.set([5,5], true) 172 | assert(w.alive([5,5])) 173 | assert(w.num_alive_neighbors([5,5]) == 0) 174 | w.set([5,6], true) 175 | assert(w.num_alive_neighbors([5,5]) == 1) 176 | w.set([6,6], true) 177 | assert(w.num_alive_neighbors([5,5]) == 2) 178 | # try to fool us with a far-off cell 179 | w.set([9,9], true) 180 | assert(w.num_alive_neighbors([5,5]) == 2) 181 | # kill thy neighbor 182 | w.set([6,6], false) 183 | assert(w.num_alive_neighbors([5,5]) == 1) 184 | 185 | #
186 | # The SURVIVAL RULE. If you are alive this generation, you need 187 | # 2 or 3 neighbors to survive. Unpopulated cells come into existence 188 | # when there are exactly three neighbors. 189 | point_lives_next_gen = (alive, n) -> 190 | if alive 191 | n in [2, 3] 192 | else 193 | n == 3 194 | 195 | #
196 | # Inline unit testing. For a pretty simple functional method, we really 197 | # just want a smoke test. Laziness when it comes to testing leads to 198 | # virtuous behavior--we extract methods that are dirt simple. 199 | do -> 200 | assert point_lives_next_gen(true, 2) 201 | assert point_lives_next_gen(true, 3) 202 | assert point_lives_next_gen(false, 3) 203 | assert !point_lives_next_gen(false, 4) 204 | 205 | 206 | #
207 | # Our transform function uses an abstract method to do the heavy 208 | # lifting. We are just configuring stuff here. 209 | board_transform_function = (width, height) -> 210 | create_world = -> pacman_world(width, height) 211 | abstract_game_of_life( 212 | create_world, 213 | point_lives_next_gen) 214 | 215 | do -> 216 | f = board_transform_function() 217 | w = pacman_world(10, 10) 218 | w.set([0,0], true) 219 | w.set([1,0], true) 220 | w.set([2,0], true) 221 | assert(w.alive([1,0])) 222 | assert(!w.alive([1,1])) 223 | w = f(w) 224 | assert(!w.alive([0,0])) 225 | assert(w.alive([1,0])) 226 | assert(w.alive([1,1])) 227 | 228 | #
229 | # Seed our world with a configuration of cells. This specific seeding has 230 | # the nice property that the simulation will run for quite a while with 231 | # many interesting variations. 232 | seed_coords = -> 233 | # We could seed coordinates more directly than this algorithm, but we 234 | # represent the coordinates with strings to make the program easier to 235 | # inspect. 236 | seed = [ 237 | "X ", 238 | " ", 239 | "XX ", 240 | " ", 241 | " XXX ", 242 | " ", 243 | " XXX ", 244 | " X ", 245 | ] 246 | points = [] 247 | for s, x in seed 248 | for y in [0...s.length] 249 | points.push([x,y]) if s.charAt(y) != ' ' 250 | [x+5, y+5] for [x,y] in points 251 | 252 | seed_world = (world) -> 253 | for coord in seed_coords() 254 | world.set(coord, true) 255 | 256 | do -> 257 | w = pacman_world(20, 20) 258 | seed_world(w) 259 | assert(w.alive([5, 5])) 260 | assert(w.alive([7, 5])) 261 | assert(!w.alive([8, 5])) 262 | 263 | #
264 | # Drawing code. There is nothing fancy here. We represent a 2D 265 | # matrix of cells using rectangles on a canvas. 266 | view_2d = (width, height) -> 267 | canvas = document.getElementById("canvas") 268 | ctx = canvas.getContext("2d") 269 | 270 | draw: (x, y, fate) -> 271 | ctx.fillStyle = 272 | if fate 273 | 'black' 274 | else 275 | 'white' 276 | w = 10 277 | h = 10 278 | x = x * w 279 | y = y * h 280 | ctx.fillRect(x, y, w, h) 281 | 282 | #
283 | # This object ties together a view-agnostic model with a model-agnostic view. 284 | # It is very light, but it decouples two objects that do more heavy lifting. 285 | display = (width, height) -> 286 | view = view_2d(width, height) 287 | render_board: (world) -> 288 | for x in [0...width] 289 | for y in [0...height] 290 | fate = world.alive([x, y]) 291 | view.draw(x, y, fate) 292 | 293 | #
294 | # This is a very abstract animation object. It doesn't know anything about 295 | # the Game of Life. It just renders frames in succession, using a step_function 296 | # to iterate from one opaque data object to another. 297 | animate = (initial_data, step_function, render_func, delay, max_ticks) -> 298 | tick = 0 299 | current_data = initial_data 300 | 301 | pulse = -> 302 | tick += 1 303 | render_func(current_data) 304 | if (tick < max_ticks) 305 | current_data = step_function(current_data) 306 | render_func(current_data) 307 | setTimeout(pulse, delay) 308 | pulse() 309 | 310 | #
311 | # Now we set everything in motion! 312 | do -> 313 | # CONFIGURATION 314 | WIDTH = 50 315 | HEIGHT = 40 316 | MAX_TICKS = 800 317 | DELAY = 5 # milliseconds 318 | 319 | # Set up our key objects, starting with our model. 320 | initial_world = pacman_world(WIDTH, HEIGHT) 321 | seed_world(initial_world) 322 | # Layer on rendering. 323 | render_function = display(WIDTH, HEIGHT).render_board 324 | # Create the function to evolve our model. 325 | data_transform_function = board_transform_function(WIDTH, HEIGHT) 326 | # And then animate it. 327 | animate( 328 | initial_world, 329 | data_transform_function, 330 | render_function, 331 | DELAY, 332 | MAX_TICKS 333 | ) 334 | 335 | -------------------------------------------------------------------------------- /examples/hanoi.coffee: -------------------------------------------------------------------------------- 1 | hanoi = (ndisks, start_peg=1, end_peg=3) -> 2 | if ndisks 3 | staging_peg = 1 + 2 + 3 - start_peg - end_peg 4 | hanoi(ndisks-1, start_peg, staging_peg) 5 | console.log "Move disk #{ndisks} from peg #{start_peg} to #{end_peg}" 6 | hanoi(ndisks-1, staging_peg, end_peg) 7 | 8 | hanoi(4) -------------------------------------------------------------------------------- /examples/knight.coffee: -------------------------------------------------------------------------------- 1 | graph_tours = (graph, max_num_solutions) -> 2 | # graph is an array of arrays 3 | # graph[3] = [4, 5] means nodes 4 and 5 are reachable from node 3 4 | # 5 | # Returns an array of tours (up to max_num_solutions in size), where 6 | # each tour is an array of nodes visited in order, and where each 7 | # tour visits every node in the graph exactly once. 8 | # 9 | complete_tours = [] 10 | visited = (false for node in graph) 11 | dead_ends = ({} for node in graph) 12 | tour = [0] 13 | 14 | valid_neighbors = (i) -> 15 | arr = [] 16 | for neighbor in graph[i] 17 | continue if visited[neighbor] 18 | continue if dead_ends[i][neighbor] 19 | arr.push neighbor 20 | arr 21 | 22 | next_square_to_visit = (i) -> 23 | arr = valid_neighbors i 24 | return null if arr.length == 0 25 | 26 | # We traverse to our neighbor who has the fewest neighbors itself. 27 | fewest_neighbors = valid_neighbors(arr[0]).length 28 | neighbor = arr[0] 29 | for i in [1...arr.length] 30 | n = valid_neighbors(arr[i]).length 31 | if n < fewest_neighbors 32 | fewest_neighbors = n 33 | neighbor = arr[i] 34 | neighbor 35 | 36 | while tour.length > 0 37 | current_square = tour[tour.length - 1] 38 | visited[current_square] = true 39 | next_square = next_square_to_visit current_square 40 | if next_square? 41 | tour.push next_square 42 | if tour.length == graph.length 43 | complete_tours.push (n for n in tour) # clone 44 | break if complete_tours.length == max_num_solutions 45 | # pessimistically call this a dead end 46 | dead_ends[current_square][next_square] = true 47 | current_square = next_square 48 | else 49 | # we backtrack 50 | doomed_square = tour.pop() 51 | dead_ends[doomed_square] = {} 52 | visited[doomed_square] = false 53 | complete_tours 54 | 55 | 56 | knight_graph = (board_width) -> 57 | # Turn the Knight's Tour into a pure graph-traversal problem 58 | # by precomputing all the legal moves. Returns an array of arrays, 59 | # where each element in any subarray is the index of a reachable node. 60 | index = (i, j) -> 61 | # index squares from 0 to n*n - 1 62 | board_width * i + j 63 | 64 | reachable_squares = (i, j) -> 65 | deltas = [ 66 | [ 1, 2] 67 | [ 1, -2] 68 | [ 2, 1] 69 | [ 2, -1] 70 | [-1, 2] 71 | [-1, -2] 72 | [-2, 1] 73 | [-2, -1] 74 | ] 75 | neighbors = [] 76 | for delta in deltas 77 | [di, dj] = delta 78 | ii = i + di 79 | jj = j + dj 80 | if 0 <= ii < board_width 81 | if 0 <= jj < board_width 82 | neighbors.push index(ii, jj) 83 | neighbors 84 | 85 | graph = [] 86 | for i in [0...board_width] 87 | for j in [0...board_width] 88 | graph[index(i, j)] = reachable_squares i, j 89 | graph 90 | 91 | illustrate_knights_tour = (tour, board_width) -> 92 | pad = (n) -> 93 | return " _" if !n? 94 | return " " + n if n < 10 95 | "#{n}" 96 | 97 | console.log "\n------" 98 | moves = {} 99 | for square, i in tour 100 | moves[square] = i + 1 101 | for i in [0...board_width] 102 | s = '' 103 | for j in [0...board_width] 104 | s += " " + pad moves[i*board_width + j] 105 | console.log s 106 | 107 | BOARD_WIDTH = 8 108 | MAX_NUM_SOLUTIONS = 100000 109 | 110 | graph = knight_graph BOARD_WIDTH 111 | tours = graph_tours graph, MAX_NUM_SOLUTIONS 112 | console.log "#{tours.length} tours found (showing first and last)" 113 | illustrate_knights_tour tours[0], BOARD_WIDTH 114 | illustrate_knights_tour tours.pop(), BOARD_WIDTH 115 | -------------------------------------------------------------------------------- /examples/lru.coffee: -------------------------------------------------------------------------------- 1 | lru_list = -> 2 | # This is a list supporting these operations quickly: 3 | # push, shift, size 4 | # 5 | # We don't need random access, so we use a doubly linked list 6 | # to get O(1) time on the operations we do support. 7 | # 8 | # The list is very opaque. Once you push an item on to the back of the 9 | # list, you can only retrieve it (later) when it's the front element. The call 10 | # to push() also gives you a callback to remove the element. 11 | # 12 | # Use case: helper for lru_cache. 13 | cnt = 0 14 | start_node = null 15 | end_node = null 16 | 17 | remove = (node) -> 18 | cnt -= 1 19 | throw "error" if cnt < 0 20 | if node.prev 21 | node.prev.next = node.next 22 | else 23 | start_node = node.next 24 | if node.next 25 | node.next.prev = node.prev 26 | else 27 | end_node = node.prev 28 | 29 | lst = 30 | push: (v) -> 31 | cnt += 1 32 | if cnt == 1 33 | node = 34 | v: v 35 | prev: null 36 | next: null 37 | start_node = node 38 | end_node = node 39 | else 40 | node = 41 | v: v 42 | prev: end_node 43 | next: null 44 | end_node.next = node 45 | end_node = node 46 | # return a function so caller can remove item 47 | # from the list 48 | -> remove(node) 49 | 50 | shift: -> 51 | cnt -= 1 52 | throw "error" if cnt < 0 53 | v = start_node.v 54 | if cnt == 0 55 | start_node = null 56 | end_node = null 57 | else 58 | start_node = start_node.next 59 | start_node.prev = null 60 | v 61 | 62 | debug: -> 63 | console.log '----' 64 | if cnt == 0 65 | console.log '(empty)' 66 | node = start_node 67 | while node 68 | console.log node.v 69 | node = node.next 70 | 71 | size: -> 72 | cnt 73 | 74 | test: -> 75 | # call lru_list().test() to see in action 76 | lst.push "hello" 77 | lst.push "goodbye" 78 | lst.debug() 79 | lst.shift() 80 | lst.debug() 81 | lst.shift() 82 | lst.debug() 83 | remove_a = lst.push "a" 84 | remove_b = lst.push "b" 85 | remove_c = lst.push "c" 86 | lst.debug() 87 | remove_b() 88 | lst.debug() 89 | remove_c() 90 | lst.debug() 91 | remove_a() 92 | lst.debug() 93 | 94 | lru_cache = (capacity) -> 95 | # This is an LRU cache. An LRU behaves like a hash where old items expire. 96 | # We implement it as a hash for the core data structure, then we have a linked 97 | # list of keys that allows us to keep track of expiring keys. 98 | lst = lru_list() 99 | cache = {} 100 | 101 | add = (k, v) -> 102 | if lst.size() == capacity 103 | old_key = lst.shift() 104 | # console.log "purging #{old_key} from cache!" 105 | delete cache[old_key] 106 | cache[k] = 107 | remover: lst.push k 108 | v: v 109 | 110 | update = (k) -> 111 | cell = cache[k] 112 | cell.remover() 113 | cell.remover = lst.push k 114 | 115 | self = 116 | put: (k, v) -> 117 | cell = cache[k] 118 | if cell 119 | update k 120 | else 121 | add k, v 122 | 123 | get: (k) -> 124 | cell = cache[k] 125 | return [false, null] if !cell 126 | update k 127 | [true, cell.v] 128 | 129 | test: -> 130 | # call lru_cache(2).test() to see in action 131 | self.put(1, "one") 132 | self.put(2, "two") 133 | self.put(3, "three") 134 | console.log self.get(3) 135 | console.log self.get(2) 136 | console.log self.get(1) 137 | self.put(4, "four") 138 | console.log self.get(3) 139 | console.log self.get(2) 140 | console.log self.get(4) 141 | 142 | lru_cache(2).test() 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /examples/rosetta_crawl.coffee: -------------------------------------------------------------------------------- 1 | # use npm install for below modules: 2 | soupselect = require 'soupselect' 3 | htmlparser = require 'htmlparser' 4 | 5 | # configure these for your language 6 | LANGUAGE = 'CoffeeScript' 7 | LANGUAGE_WEBSITE = "http://coffeescript.org" 8 | LANG_SELECTOR = 'pre.coffeescript.highlighted_source' 9 | BLACKLIST = (title) -> 10 | # These are programs that just don't add a lot of value out of context, 11 | # or that have distracting style issues. 12 | return true if title in [ 13 | '24 game' # whitespace 14 | '100 doors' 15 | 'A+B' 16 | 'Comments' 17 | 'CSV to HTML translation' 18 | 'Empty program' 19 | 'First-class functions' # for now 20 | 'Infinity' 21 | 'Permutations' # whitespace 22 | 'Quine' 23 | ] 24 | return true if title.match /Hello world/ 25 | return true if title.match /Loops\// 26 | return true if title.match /Vigen.* cipher/ # unicode 27 | false 28 | 29 | ROSETTA_INTRO = """ 30 |

31 | This site aggregates some content from Rosetta Code, which is a website 32 | that presents solutions to programming tasks in many different languages. 33 |

34 | 35 |

36 | In particular, we focus on the #{LANGUAGE} programming language. 37 | Most of the content here was originally posted on Rosetta Code, and this content remains licensed under the 38 | GNU Free Documentation Licence version 1.2. 39 |

40 | 41 |

42 | You can see the code used for crawling Rosetta by 43 | following this link. 44 |

45 | 46 |

47 | Be a contributor! You can enhance the Rosetta Code site by 48 | 49 | implementing new tasks for #{LANGUAGE}. 50 |

51 | """ 52 | 53 | 54 | #### 55 | PAGE_INTRO = """ 56 | 57 | #{LANGUAGE} Examples from Rosetta Code 58 | 59 | 70 | 71 | #{ROSETTA_INTRO} 72 | """ 73 | LANGUAGE_PAGES_SELECTOR = "#mw-pages li a" 74 | HOST = 'rosettacode.org' 75 | 76 | http = require 'http' 77 | 78 | process_task_page = (link_info, done) -> 79 | path = link_info.href 80 | page_link = "http://#{HOST}#{link_info.href}" 81 | lang_link = "#{page_link}##{LANGUAGE}" 82 | html = """ 83 |
84 | 85 |

#{link_info.title}

86 | #{LANGUAGE} section on Rosetta Code 87 |
88 | (local link)
89 | """ 90 | 91 | process_page path, (err, dom) -> 92 | snippets = soupselect.select dom, LANG_SELECTOR 93 | 94 | i = 0 95 | process_snippet = (snippet, done) -> 96 | source = fix_tabs dom_to_text snippet 97 | color_syntax source, (code) -> 98 | if snippets.length > 1 99 | i += 1 100 | html += """ 101 |
Example #{i}
102 | """ 103 | html += """ 104 |
105 |           #{code}
106 |           
107 | """ 108 | done() 109 | 110 | process_list snippets, process_snippet, -> 111 | done("
#{html}
") 112 | 113 | process_language_page = (done) -> 114 | html = PAGE_INTRO 115 | handler = (err, dom) -> 116 | links = soupselect.select dom, LANGUAGE_PAGES_SELECTOR 117 | link_info = (link) -> 118 | href: link.attribs.href 119 | title: link.attribs.title 120 | links = (link_info(link) for link in links) 121 | links = (link for link in links when !BLACKLIST link.title) 122 | task_page_handler = (link_info, done) -> 123 | process_task_page link_info, (new_html) -> 124 | html += new_html 125 | done() 126 | process_list links, task_page_handler, -> 127 | console.log html 128 | done() 129 | 130 | process_page "/wiki/Category:#{LANGUAGE}", handler 131 | 132 | process_page = (path, cb) -> 133 | wget path, (body) -> 134 | handler = new htmlparser.DefaultHandler(cb) 135 | parser = new htmlparser.Parser(handler) 136 | parser.parseComplete body 137 | 138 | process_list = (list, f, done_cb) -> 139 | # WOO HOO! async complexity, hopefully somewhat encapsulated here 140 | # This serializes callback-based invocations of f for each element of our list. 141 | i = 0 142 | _process = -> 143 | if i < list.length 144 | f list[i], -> 145 | i += 1 146 | # return done_cb() if i == 3 147 | _process() 148 | else 149 | done_cb() 150 | _process() 151 | 152 | wget = (path, cb) -> 153 | options = 154 | host: HOST 155 | path: path 156 | headers: 157 | "Cache-Control": "max-age=0" 158 | 159 | req = http.request options, (res) -> 160 | s = '' 161 | res.on 'data', (chunk) -> 162 | s += chunk 163 | res.on 'end', -> 164 | cb s 165 | req.end() 166 | 167 | color_syntax = (source, callback) -> 168 | {spawn} = require 'child_process' 169 | pygments = spawn 'pygmentize', ['-l', LANGUAGE.toLowerCase(), '-f', 'html', '-O', 'encoding=utf-8'] 170 | output = '' 171 | pygments.stderr.addListener 'data', (error) -> 172 | console.error error if error 173 | pygments.stdout.addListener 'data', (result) -> 174 | output += result if result 175 | pygments.addListener 'exit', -> 176 | callback(output) 177 | pygments.stdin.write(source) 178 | pygments.stdin.end() 179 | 180 | dom_to_text = (dom) -> 181 | if dom.name == 'br' 182 | return '\n' 183 | s = '' 184 | if dom.type == 'text' 185 | s += dom.data 186 | if dom.children 187 | for child in dom.children 188 | s += dom_to_text child 189 | html_decode(s) 190 | 191 | fix_tabs = (s) -> 192 | s.replace "\t", "TABS, REALLY?" 193 | 194 | html_decode = (s) -> 195 | s = s.replace /&#(\d+);/g, (a, b) -> 196 | return ' ' if b == '160' # npbsp 197 | String.fromCharCode(b) 198 | s = s.replace /&(.*?);/g, (a, b) -> 199 | map = 200 | amp: '&' 201 | gt: '>' 202 | lt: '<' 203 | quot: '"' 204 | map[b] || "UNKNOWN CHAR #{b}" 205 | 206 | process_language_page -> # do nothing 207 | -------------------------------------------------------------------------------- /examples/spine.coffee: -------------------------------------------------------------------------------- 1 | Events = 2 | bind: (ev, callback) -> 3 | evs = ev.split(' ') 4 | calls = @hasOwnProperty('_callbacks') and @_callbacks or= {} 5 | 6 | for name in evs 7 | calls[name] or= [] 8 | calls[name].push(callback) 9 | this 10 | 11 | one: (ev, callback) -> 12 | @bind ev, -> 13 | @unbind(ev, arguments.callee) 14 | callback.apply(@, arguments) 15 | 16 | trigger: (args...) -> 17 | ev = args.shift() 18 | 19 | list = @hasOwnProperty('_callbacks') and @_callbacks?[ev] 20 | return unless list 21 | 22 | for callback in list 23 | if callback.apply(@, args) is false 24 | break 25 | true 26 | 27 | unbind: (ev, callback) -> 28 | unless ev 29 | @_callbacks = {} 30 | return this 31 | 32 | list = @_callbacks?[ev] 33 | return this unless list 34 | 35 | unless callback 36 | delete @_callbacks[ev] 37 | return this 38 | 39 | for cb, i in list when cb is callback 40 | list = list.slice() 41 | list.splice(i, 1) 42 | @_callbacks[ev] = list 43 | break 44 | this 45 | 46 | Log = 47 | trace: true 48 | 49 | logPrefix: '(App)' 50 | 51 | log: (args...) -> 52 | return unless @trace 53 | if @logPrefix then args.unshift(@logPrefix) 54 | console?.log?(args...) 55 | this 56 | 57 | moduleKeywords = ['included', 'extended'] 58 | 59 | class Module 60 | @include: (obj) -> 61 | throw('include(obj) requires obj') unless obj 62 | for key, value of obj when key not in moduleKeywords 63 | @::[key] = value 64 | obj.included?.apply(@) 65 | this 66 | 67 | @extend: (obj) -> 68 | throw('extend(obj) requires obj') unless obj 69 | for key, value of obj when key not in moduleKeywords 70 | @[key] = value 71 | obj.extended?.apply(@) 72 | this 73 | 74 | @proxy: (func) -> 75 | => func.apply(@, arguments) 76 | 77 | proxy: (func) -> 78 | => func.apply(@, arguments) 79 | 80 | constructor: -> 81 | @init?(arguments...) 82 | 83 | class Model extends Module 84 | @extend Events 85 | 86 | @records: {} 87 | @crecords: {} 88 | @attributes: [] 89 | 90 | @configure: (name, attributes...) -> 91 | @className = name 92 | @records = {} 93 | @crecords = {} 94 | @attributes = attributes if attributes.length 95 | @attributes and= makeArray(@attributes) 96 | @attributes or= [] 97 | @unbind() 98 | this 99 | 100 | @toString: -> "#{@className}(#{@attributes.join(", ")})" 101 | 102 | @find: (id) -> 103 | if ("#{id}").match(/c-\d+/) 104 | return @findCID(id) 105 | 106 | record = @records[id] 107 | throw('Unknown record') unless record 108 | record.clone() 109 | 110 | @findCID: (cid) -> 111 | record = @crecords[cid] 112 | throw('Unknown record') unless record 113 | record.clone() 114 | 115 | @exists: (id) -> 116 | try 117 | return @find(id) 118 | catch e 119 | return false 120 | 121 | @refresh: (values, options = {}) -> 122 | if options.clear 123 | @records = {} 124 | @crecords = {} 125 | 126 | records = @fromJSON(values) 127 | 128 | records = [records] unless isArray(records) 129 | 130 | for record in records 131 | record.id or= record.cid 132 | @records[record.id] = record 133 | 134 | @crecords[record.cid] = record 135 | 136 | @trigger('refresh', not options.clear and records) 137 | this 138 | 139 | @select: (callback) -> 140 | result = (record for id, record of @records when callback(record)) 141 | @cloneArray(result) 142 | 143 | @findByAttribute: (name, value) -> 144 | for id, record of @records 145 | if record[name] is value 146 | return record.clone() 147 | null 148 | 149 | @findAllByAttribute: (name, value) -> 150 | @select (item) -> 151 | item[name] is value 152 | 153 | @each: (callback) -> 154 | for key, value of @records 155 | callback(value.clone()) 156 | 157 | @all: -> 158 | @cloneArray(@recordsValues()) 159 | 160 | @first: -> 161 | record = @recordsValues()[0] 162 | record?.clone() 163 | 164 | @last: -> 165 | values = @recordsValues() 166 | record = values[values.length - 1] 167 | record?.clone() 168 | 169 | @count: -> 170 | @recordsValues().length 171 | 172 | @deleteAll: -> 173 | for key, value of @records 174 | delete @records[key] 175 | 176 | @destroyAll: -> 177 | for key, value of @records 178 | @records[key].destroy() 179 | 180 | @update: (id, atts, options) -> 181 | @find(id).updateAttributes(atts, options) 182 | 183 | @create: (atts, options) -> 184 | record = new @(atts) 185 | record.save(options) 186 | 187 | @destroy: (id, options) -> 188 | @find(id).destroy(options) 189 | 190 | @change: (callbackOrParams) -> 191 | if typeof callbackOrParams is 'function' 192 | @bind('change', callbackOrParams) 193 | else 194 | @trigger('change', callbackOrParams) 195 | 196 | @fetch: (callbackOrParams) -> 197 | if typeof callbackOrParams is 'function' 198 | @bind('fetch', callbackOrParams) 199 | else 200 | @trigger('fetch', callbackOrParams) 201 | 202 | @toJSON: -> 203 | @recordsValues() 204 | 205 | @fromJSON: (objects) -> 206 | return unless objects 207 | if typeof objects is 'string' 208 | objects = JSON.parse(objects) 209 | if isArray(objects) 210 | (new @(value) for value in objects) 211 | else 212 | new @(objects) 213 | 214 | @fromForm: -> 215 | (new this).fromForm(arguments...) 216 | 217 | # Private 218 | 219 | @recordsValues: -> 220 | result = [] 221 | for key, value of @records 222 | result.push(value) 223 | result 224 | 225 | @cloneArray: (array) -> 226 | (value.clone() for value in array) 227 | 228 | @idCounter: 0 229 | 230 | @uid: -> 231 | @idCounter++ 232 | 233 | # Instance 234 | 235 | constructor: (atts) -> 236 | super 237 | @load atts if atts 238 | @cid or= 'c-' + @constructor.uid() 239 | 240 | isNew: -> 241 | not @exists() 242 | 243 | isValid: -> 244 | not @validate() 245 | 246 | validate: -> 247 | 248 | load: (atts) -> 249 | for key, value of atts 250 | if typeof @[key] is 'function' 251 | @[key](value) 252 | else 253 | @[key] = value 254 | this 255 | 256 | attributes: -> 257 | result = {} 258 | for key in @constructor.attributes when key of this 259 | if typeof @[key] is 'function' 260 | result[key] = @[key]() 261 | else 262 | result[key] = @[key] 263 | result.id = @id if @id 264 | result 265 | 266 | eql: (rec) -> 267 | rec and rec.constructor is @constructor and 268 | (rec.id is @id or rec.cid is @cid) 269 | 270 | save: (options = {}) -> 271 | unless options.validate is false 272 | error = @validate() 273 | if error 274 | @trigger('error', error) 275 | return false 276 | 277 | @trigger('beforeSave', options) 278 | record = if @isNew() then @create(options) else @update(options) 279 | @trigger('save', options) 280 | record 281 | 282 | updateAttribute: (name, value) -> 283 | @[name] = value 284 | @save() 285 | 286 | updateAttributes: (atts, options) -> 287 | @load(atts) 288 | @save(options) 289 | 290 | changeID: (id) -> 291 | records = @constructor.records 292 | records[id] = records[@id] 293 | delete records[@id] 294 | @id = id 295 | @save() 296 | 297 | destroy: (options = {}) -> 298 | @trigger('beforeDestroy', options) 299 | delete @constructor.records[@id] 300 | delete @constructor.crecords[@cid] 301 | @destroyed = true 302 | @trigger('destroy', options) 303 | @trigger('change', 'destroy', options) 304 | @unbind() 305 | this 306 | 307 | dup: (newRecord) -> 308 | result = new @constructor(@attributes()) 309 | if newRecord is false 310 | result.cid = @cid 311 | else 312 | delete result.id 313 | result 314 | 315 | clone: -> 316 | Object.create(@) 317 | 318 | reload: -> 319 | return this if @isNew() 320 | original = @constructor.find(@id) 321 | @load(original.attributes()) 322 | original 323 | 324 | toJSON: -> 325 | @attributes() 326 | 327 | toString: -> 328 | "<#{@constructor.className} (#{JSON.stringify(@)})>" 329 | 330 | fromForm: (form) -> 331 | result = {} 332 | for key in $(form).serializeArray() 333 | result[key.name] = key.value 334 | @load(result) 335 | 336 | exists: -> 337 | @id && @id of @constructor.records 338 | 339 | # Private 340 | 341 | update: (options) -> 342 | @trigger('beforeUpdate', options) 343 | records = @constructor.records 344 | records[@id].load @attributes() 345 | clone = records[@id].clone() 346 | clone.trigger('update', options) 347 | clone.trigger('change', 'update', options) 348 | clone 349 | 350 | create: (options) -> 351 | @trigger('beforeCreate', options) 352 | @id = @cid unless @id 353 | 354 | record = @dup(false) 355 | @constructor.records[@id] = record 356 | @constructor.crecords[@cid] = record 357 | 358 | clone = record.clone() 359 | clone.trigger('create', options) 360 | clone.trigger('change', 'create', options) 361 | clone 362 | 363 | bind: (events, callback) -> 364 | @constructor.bind events, binder = (record) => 365 | if record && @eql(record) 366 | callback.apply(@, arguments) 367 | @constructor.bind 'unbind', unbinder = (record) => 368 | if record && @eql(record) 369 | @constructor.unbind(events, binder) 370 | @constructor.unbind('unbind', unbinder) 371 | binder 372 | 373 | one: (events, callback) -> 374 | binder = @bind events, => 375 | @constructor.unbind(events, binder) 376 | callback.apply(@) 377 | 378 | trigger: (args...) -> 379 | args.splice(1, 0, @) 380 | @constructor.trigger(args...) 381 | 382 | unbind: -> 383 | @trigger('unbind') 384 | 385 | class Controller extends Module 386 | @include Events 387 | @include Log 388 | 389 | eventSplitter: /^(\S+)\s*(.*)$/ 390 | tag: 'div' 391 | 392 | constructor: (options) -> 393 | @options = options 394 | 395 | for key, value of @options 396 | @[key] = value 397 | 398 | @el = document.createElement(@tag) unless @el 399 | @el = $(@el) 400 | 401 | @el.addClass(@className) if @className 402 | 403 | @release -> @el.remove() 404 | 405 | @events = @constructor.events unless @events 406 | @elements = @constructor.elements unless @elements 407 | 408 | @delegateEvents() if @events 409 | @refreshElements() if @elements 410 | 411 | super 412 | 413 | release: (callback) => 414 | if typeof callback is 'function' 415 | @bind 'release', callback 416 | else 417 | @trigger 'release' 418 | 419 | $: (selector) -> $(selector, @el) 420 | 421 | delegateEvents: -> 422 | for key, method of @events 423 | unless typeof(method) is 'function' 424 | method = @proxy(@[method]) 425 | 426 | match = key.match(@eventSplitter) 427 | eventName = match[1] 428 | selector = match[2] 429 | 430 | if selector is '' 431 | @el.bind(eventName, method) 432 | else 433 | @el.delegate(selector, eventName, method) 434 | 435 | refreshElements: -> 436 | for key, value of @elements 437 | @[value] = @$(key) 438 | 439 | delay: (func, timeout) -> 440 | setTimeout(@proxy(func), timeout || 0) 441 | 442 | html: (element) -> 443 | @el.html(element.el or element) 444 | @refreshElements() 445 | @el 446 | 447 | append: (elements...) -> 448 | elements = (e.el or e for e in elements) 449 | @el.append(elements...) 450 | @refreshElements() 451 | @el 452 | 453 | appendTo: (element) -> 454 | @el.appendTo(element.el or element) 455 | @refreshElements() 456 | @el 457 | 458 | prepend: (elements...) -> 459 | elements = (e.el or e for e in elements) 460 | @el.prepend(elements...) 461 | @refreshElements() 462 | @el 463 | 464 | replace: (element) -> 465 | [previous, @el] = [@el, $(element.el or element)] 466 | previous.replaceWith(@el) 467 | @delegateEvents() 468 | @refreshElements() 469 | @el 470 | 471 | # Utilities & Shims 472 | 473 | $ = window.jQuery or window.Zepto or (element) -> element 474 | 475 | unless typeof Object.create is 'function' 476 | Object.create = (o) -> 477 | Func = -> 478 | Func.prototype = o 479 | new Func() 480 | 481 | isArray = (value) -> 482 | Object::toString.call(value) is '[object Array]' 483 | 484 | isBlank = (value) -> 485 | return true unless value 486 | return false for key of value 487 | true 488 | 489 | makeArray = (args) -> 490 | Array.prototype.slice.call(args, 0) 491 | 492 | # Globals 493 | 494 | Spine = @Spine = {} 495 | module?.exports = Spine 496 | 497 | Spine.version = '1.0.5' 498 | Spine.isArray = isArray 499 | Spine.isBlank = isBlank 500 | Spine.$ = $ 501 | Spine.Events = Events 502 | Spine.Log = Log 503 | Spine.Module = Module 504 | Spine.Controller = Controller 505 | Spine.Model = Model 506 | 507 | # Global events 508 | 509 | Module.extend.call(Spine, Events) 510 | 511 | # JavaScript compatability 512 | 513 | Module.create = Module.sub = 514 | Controller.create = Controller.sub = 515 | Model.sub = (instances, statics) -> 516 | class result extends this 517 | result.include(instances) if instances 518 | result.extend(statics) if statics 519 | result.unbind?() 520 | result 521 | 522 | Model.setup = (name, attributes = []) -> 523 | class Instance extends this 524 | Instance.configure(name, attributes...) 525 | Instance 526 | 527 | Module.init = Controller.init = Model.init = (a1, a2, a3, a4, a5) -> 528 | new this(a1, a2, a3, a4, a5) 529 | 530 | Spine.Class = Module -------------------------------------------------------------------------------- /examples/underscore.coffee: -------------------------------------------------------------------------------- 1 | # **Underscore.coffee 2 | # (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.** 3 | # Underscore is freely distributable under the terms of the 4 | # [MIT license](http://en.wikipedia.org/wiki/MIT_License). 5 | # Portions of Underscore are inspired by or borrowed from 6 | # [Prototype.js](http://prototypejs.org/api), Oliver Steele's 7 | # [Functional](http://osteele.com), and John Resig's 8 | # [Micro-Templating](http://ejohn.org). 9 | # For all details and documentation: 10 | # http://documentcloud.github.com/underscore/ 11 | 12 | 13 | # Baseline setup 14 | # -------------- 15 | 16 | # Establish the root object, `window` in the browser, or `global` on the server. 17 | root = this 18 | 19 | 20 | # Save the previous value of the `_` variable. 21 | previousUnderscore = root._ 22 | 23 | 24 | # Establish the object that gets thrown to break out of a loop iteration. 25 | # `StopIteration` is SOP on Mozilla. 26 | breaker = if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration 27 | 28 | 29 | # Helper function to escape **RegExp** contents, because JS doesn't have one. 30 | escapeRegExp = (string) -> string.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1') 31 | 32 | 33 | # Save bytes in the minified (but not gzipped) version: 34 | ArrayProto = Array.prototype 35 | ObjProto = Object.prototype 36 | 37 | 38 | # Create quick reference variables for speed access to core prototypes. 39 | slice = ArrayProto.slice 40 | unshift = ArrayProto.unshift 41 | toString = ObjProto.toString 42 | hasOwnProperty = ObjProto.hasOwnProperty 43 | propertyIsEnumerable = ObjProto.propertyIsEnumerable 44 | 45 | 46 | # All **ECMA5** native implementations we hope to use are declared here. 47 | nativeForEach = ArrayProto.forEach 48 | nativeMap = ArrayProto.map 49 | nativeReduce = ArrayProto.reduce 50 | nativeReduceRight = ArrayProto.reduceRight 51 | nativeFilter = ArrayProto.filter 52 | nativeEvery = ArrayProto.every 53 | nativeSome = ArrayProto.some 54 | nativeIndexOf = ArrayProto.indexOf 55 | nativeLastIndexOf = ArrayProto.lastIndexOf 56 | nativeIsArray = Array.isArray 57 | nativeKeys = Object.keys 58 | 59 | 60 | # Create a safe reference to the Underscore object for use below. 61 | _ = (obj) -> new wrapper(obj) 62 | 63 | 64 | # Export the Underscore object for **CommonJS**. 65 | if typeof(exports) != 'undefined' then exports._ = _ 66 | 67 | 68 | # Export Underscore to global scope. 69 | root._ = _ 70 | 71 | 72 | # Current version. 73 | _.VERSION = '1.1.0' 74 | 75 | 76 | # Collection Functions 77 | # -------------------- 78 | 79 | # The cornerstone, an **each** implementation. 80 | # Handles objects implementing **forEach**, arrays, and raw objects. 81 | _.each = (obj, iterator, context) -> 82 | try 83 | if nativeForEach and obj.forEach is nativeForEach 84 | obj.forEach iterator, context 85 | else if _.isNumber obj.length 86 | iterator.call context, obj[i], i, obj for i in [0...obj.length] 87 | else 88 | iterator.call context, val, key, obj for own key, val of obj 89 | catch e 90 | throw e if e isnt breaker 91 | obj 92 | 93 | 94 | # Return the results of applying the iterator to each element. Use JavaScript 95 | # 1.6's version of **map**, if possible. 96 | _.map = (obj, iterator, context) -> 97 | return obj.map(iterator, context) if nativeMap and obj.map is nativeMap 98 | results = [] 99 | _.each obj, (value, index, list) -> 100 | results.push iterator.call context, value, index, list 101 | results 102 | 103 | 104 | # **Reduce** builds up a single result from a list of values. Also known as 105 | # **inject**, or **foldl**. Uses JavaScript 1.8's version of **reduce**, if possible. 106 | _.reduce = (obj, iterator, memo, context) -> 107 | if nativeReduce and obj.reduce is nativeReduce 108 | iterator = _.bind iterator, context if context 109 | return obj.reduce iterator, memo 110 | _.each obj, (value, index, list) -> 111 | memo = iterator.call context, memo, value, index, list 112 | memo 113 | 114 | 115 | # The right-associative version of **reduce**, also known as **foldr**. Uses 116 | # JavaScript 1.8's version of **reduceRight**, if available. 117 | _.reduceRight = (obj, iterator, memo, context) -> 118 | if nativeReduceRight and obj.reduceRight is nativeReduceRight 119 | iterator = _.bind iterator, context if context 120 | return obj.reduceRight iterator, memo 121 | reversed = _.clone(_.toArray(obj)).reverse() 122 | _.reduce reversed, iterator, memo, context 123 | 124 | 125 | # Return the first value which passes a truth test. 126 | _.detect = (obj, iterator, context) -> 127 | result = null 128 | _.each obj, (value, index, list) -> 129 | if iterator.call context, value, index, list 130 | result = value 131 | _.breakLoop() 132 | result 133 | 134 | 135 | # Return all the elements that pass a truth test. Use JavaScript 1.6's 136 | # **filter**, if it exists. 137 | _.filter = (obj, iterator, context) -> 138 | return obj.filter iterator, context if nativeFilter and obj.filter is nativeFilter 139 | results = [] 140 | _.each obj, (value, index, list) -> 141 | results.push value if iterator.call context, value, index, list 142 | results 143 | 144 | 145 | # Return all the elements for which a truth test fails. 146 | _.reject = (obj, iterator, context) -> 147 | results = [] 148 | _.each obj, (value, index, list) -> 149 | results.push value if not iterator.call context, value, index, list 150 | results 151 | 152 | 153 | # Determine whether all of the elements match a truth test. Delegate to 154 | # JavaScript 1.6's **every**, if it is present. 155 | _.every = (obj, iterator, context) -> 156 | iterator ||= _.identity 157 | return obj.every iterator, context if nativeEvery and obj.every is nativeEvery 158 | result = true 159 | _.each obj, (value, index, list) -> 160 | _.breakLoop() unless (result = result and iterator.call(context, value, index, list)) 161 | result 162 | 163 | 164 | # Determine if at least one element in the object matches a truth test. Use 165 | # JavaScript 1.6's **some**, if it exists. 166 | _.some = (obj, iterator, context) -> 167 | iterator ||= _.identity 168 | return obj.some iterator, context if nativeSome and obj.some is nativeSome 169 | result = false 170 | _.each obj, (value, index, list) -> 171 | _.breakLoop() if (result = iterator.call(context, value, index, list)) 172 | result 173 | 174 | 175 | # Determine if a given value is included in the array or object, 176 | # based on `===`. 177 | _.include = (obj, target) -> 178 | return _.indexOf(obj, target) isnt -1 if nativeIndexOf and obj.indexOf is nativeIndexOf 179 | return true for own key, val of obj when val is target 180 | false 181 | 182 | 183 | # Invoke a method with arguments on every item in a collection. 184 | _.invoke = (obj, method) -> 185 | args = _.rest arguments, 2 186 | (if method then val[method] else val).apply(val, args) for val in obj 187 | 188 | 189 | # Convenience version of a common use case of **map**: fetching a property. 190 | _.pluck = (obj, key) -> 191 | _.map(obj, (val) -> val[key]) 192 | 193 | 194 | # Return the maximum item or (item-based computation). 195 | _.max = (obj, iterator, context) -> 196 | return Math.max.apply(Math, obj) if not iterator and _.isArray(obj) 197 | result = computed: -Infinity 198 | _.each obj, (value, index, list) -> 199 | computed = if iterator then iterator.call(context, value, index, list) else value 200 | computed >= result.computed and (result = {value: value, computed: computed}) 201 | result.value 202 | 203 | 204 | # Return the minimum element (or element-based computation). 205 | _.min = (obj, iterator, context) -> 206 | return Math.min.apply(Math, obj) if not iterator and _.isArray(obj) 207 | result = computed: Infinity 208 | _.each obj, (value, index, list) -> 209 | computed = if iterator then iterator.call(context, value, index, list) else value 210 | computed < result.computed and (result = {value: value, computed: computed}) 211 | result.value 212 | 213 | 214 | # Sort the object's values by a criterion produced by an iterator. 215 | _.sortBy = (obj, iterator, context) -> 216 | _.pluck(((_.map obj, (value, index, list) -> 217 | {value: value, criteria: iterator.call(context, value, index, list)} 218 | ).sort((left, right) -> 219 | a = left.criteria; b = right.criteria 220 | if a < b then -1 else if a > b then 1 else 0 221 | )), 'value') 222 | 223 | 224 | # Use a comparator function to figure out at what index an object should 225 | # be inserted so as to maintain order. Uses binary search. 226 | _.sortedIndex = (array, obj, iterator) -> 227 | iterator ||= _.identity 228 | low = 0 229 | high = array.length 230 | while low < high 231 | mid = (low + high) >> 1 232 | if iterator(array[mid]) < iterator(obj) then low = mid + 1 else high = mid 233 | low 234 | 235 | 236 | # Convert anything iterable into a real, live array. 237 | _.toArray = (iterable) -> 238 | return [] if (!iterable) 239 | return iterable.toArray() if (iterable.toArray) 240 | return iterable if (_.isArray(iterable)) 241 | return slice.call(iterable) if (_.isArguments(iterable)) 242 | _.values(iterable) 243 | 244 | 245 | # Return the number of elements in an object. 246 | _.size = (obj) -> _.toArray(obj).length 247 | 248 | 249 | # Array Functions 250 | # --------------- 251 | 252 | # Get the first element of an array. Passing `n` will return the first N 253 | # values in the array. Aliased as **head**. The `guard` check allows it to work 254 | # with **map**. 255 | _.first = (array, n, guard) -> 256 | if n and not guard then slice.call(array, 0, n) else array[0] 257 | 258 | 259 | # Returns everything but the first entry of the array. Aliased as **tail**. 260 | # Especially useful on the arguments object. Passing an `index` will return 261 | # the rest of the values in the array from that index onward. The `guard` 262 | # check allows it to work with **map**. 263 | _.rest = (array, index, guard) -> 264 | slice.call(array, if _.isUndefined(index) or guard then 1 else index) 265 | 266 | 267 | # Get the last element of an array. 268 | _.last = (array) -> array[array.length - 1] 269 | 270 | 271 | # Trim out all falsy values from an array. 272 | _.compact = (array) -> item for item in array when item 273 | 274 | 275 | # Return a completely flattened version of an array. 276 | _.flatten = (array) -> 277 | _.reduce array, (memo, value) -> 278 | return memo.concat(_.flatten(value)) if _.isArray value 279 | memo.push value 280 | memo 281 | , [] 282 | 283 | 284 | # Return a version of the array that does not contain the specified value(s). 285 | _.without = (array) -> 286 | values = _.rest arguments 287 | val for val in _.toArray(array) when not _.include values, val 288 | 289 | 290 | # Produce a duplicate-free version of the array. If the array has already 291 | # been sorted, you have the option of using a faster algorithm. 292 | _.uniq = (array, isSorted) -> 293 | memo = [] 294 | for el, i in _.toArray array 295 | memo.push el if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el)) 296 | memo 297 | 298 | 299 | # Produce an array that contains every item shared between all the 300 | # passed-in arrays. 301 | _.intersect = (array) -> 302 | rest = _.rest arguments 303 | _.select _.uniq(array), (item) -> 304 | _.all rest, (other) -> 305 | _.indexOf(other, item) >= 0 306 | 307 | 308 | # Zip together multiple lists into a single array -- elements that share 309 | # an index go together. 310 | _.zip = -> 311 | length = _.max _.pluck arguments, 'length' 312 | results = new Array length 313 | for i in [0...length] 314 | results[i] = _.pluck arguments, String i 315 | results 316 | 317 | 318 | # If the browser doesn't supply us with **indexOf** (I'm looking at you, MSIE), 319 | # we need this function. Return the position of the first occurrence of an 320 | # item in an array, or -1 if the item is not included in the array. 321 | _.indexOf = (array, item) -> 322 | return array.indexOf item if nativeIndexOf and array.indexOf is nativeIndexOf 323 | i = 0; l = array.length 324 | while l - i 325 | if array[i] is item then return i else i++ 326 | -1 327 | 328 | 329 | # Provide JavaScript 1.6's **lastIndexOf**, delegating to the native function, 330 | # if possible. 331 | _.lastIndexOf = (array, item) -> 332 | return array.lastIndexOf(item) if nativeLastIndexOf and array.lastIndexOf is nativeLastIndexOf 333 | i = array.length 334 | while i 335 | if array[i] is item then return i else i-- 336 | -1 337 | 338 | 339 | # Generate an integer Array containing an arithmetic progression. A port of 340 | # [the native Python **range** function](http://docs.python.org/library/functions.html#range). 341 | _.range = (start, stop, step) -> 342 | a = arguments 343 | solo = a.length <= 1 344 | i = start = if solo then 0 else a[0] 345 | stop = if solo then a[0] else a[1] 346 | step = a[2] or 1 347 | len = Math.ceil((stop - start) / step) 348 | return [] if len <= 0 349 | range = new Array len 350 | idx = 0 351 | loop 352 | return range if (if step > 0 then i - stop else stop - i) >= 0 353 | range[idx] = i 354 | idx++ 355 | i+= step 356 | 357 | 358 | # Function Functions 359 | # ------------------ 360 | 361 | # Create a function bound to a given object (assigning `this`, and arguments, 362 | # optionally). Binding with arguments is also known as **curry**. 363 | _.bind = (func, obj) -> 364 | args = _.rest arguments, 2 365 | -> func.apply obj or root, args.concat arguments 366 | 367 | 368 | # Bind all of an object's methods to that object. Useful for ensuring that 369 | # all callbacks defined on an object belong to it. 370 | _.bindAll = (obj) -> 371 | funcs = if arguments.length > 1 then _.rest(arguments) else _.functions(obj) 372 | _.each funcs, (f) -> obj[f] = _.bind obj[f], obj 373 | obj 374 | 375 | 376 | # Delays a function for the given number of milliseconds, and then calls 377 | # it with the arguments supplied. 378 | _.delay = (func, wait) -> 379 | args = _.rest arguments, 2 380 | setTimeout((-> func.apply(func, args)), wait) 381 | 382 | 383 | # Memoize an expensive function by storing its results. 384 | _.memoize = (func, hasher) -> 385 | memo = {} 386 | hasher or= _.identity 387 | -> 388 | key = hasher.apply this, arguments 389 | return memo[key] if key of memo 390 | memo[key] = func.apply this, arguments 391 | 392 | 393 | # Defers a function, scheduling it to run after the current call stack has 394 | # cleared. 395 | _.defer = (func) -> 396 | _.delay.apply _, [func, 1].concat _.rest arguments 397 | 398 | 399 | # Returns the first function passed as an argument to the second, 400 | # allowing you to adjust arguments, run code before and after, and 401 | # conditionally execute the original function. 402 | _.wrap = (func, wrapper) -> 403 | -> wrapper.apply wrapper, [func].concat arguments 404 | 405 | 406 | # Returns a function that is the composition of a list of functions, each 407 | # consuming the return value of the function that follows. 408 | _.compose = -> 409 | funcs = arguments 410 | -> 411 | args = arguments 412 | for i in [funcs.length - 1..0] by -1 413 | args = [funcs[i].apply(this, args)] 414 | args[0] 415 | 416 | 417 | # Object Functions 418 | # ---------------- 419 | 420 | # Retrieve the names of an object's properties. 421 | _.keys = nativeKeys or (obj) -> 422 | return _.range 0, obj.length if _.isArray(obj) 423 | key for key, val of obj 424 | 425 | 426 | # Retrieve the values of an object's properties. 427 | _.values = (obj) -> 428 | _.map obj, _.identity 429 | 430 | 431 | # Return a sorted list of the function names available in Underscore. 432 | _.functions = (obj) -> 433 | _.filter(_.keys(obj), (key) -> _.isFunction(obj[key])).sort() 434 | 435 | 436 | # Extend a given object with all of the properties in a source object. 437 | _.extend = (obj) -> 438 | for source in _.rest(arguments) 439 | obj[key] = val for key, val of source 440 | obj 441 | 442 | 443 | # Create a (shallow-cloned) duplicate of an object. 444 | _.clone = (obj) -> 445 | return obj.slice 0 if _.isArray obj 446 | _.extend {}, obj 447 | 448 | 449 | # Invokes interceptor with the obj, and then returns obj. 450 | # The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain. 451 | _.tap = (obj, interceptor) -> 452 | interceptor obj 453 | obj 454 | 455 | 456 | # Perform a deep comparison to check if two objects are equal. 457 | _.isEqual = (a, b) -> 458 | # Check object identity. 459 | return true if a is b 460 | # Different types? 461 | atype = typeof(a); btype = typeof(b) 462 | return false if atype isnt btype 463 | # Basic equality test (watch out for coercions). 464 | return true if `a == b` 465 | # One is falsy and the other truthy. 466 | return false if (!a and b) or (a and !b) 467 | # One of them implements an `isEqual()`? 468 | return a.isEqual(b) if a.isEqual 469 | # Check dates' integer values. 470 | return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b) 471 | # Both are NaN? 472 | return false if _.isNaN(a) and _.isNaN(b) 473 | # Compare regular expressions. 474 | if _.isRegExp(a) and _.isRegExp(b) 475 | return a.source is b.source and 476 | a.global is b.global and 477 | a.ignoreCase is b.ignoreCase and 478 | a.multiline is b.multiline 479 | # If a is not an object by this point, we can't handle it. 480 | return false if atype isnt 'object' 481 | # Check for different array lengths before comparing contents. 482 | return false if a.length and (a.length isnt b.length) 483 | # Nothing else worked, deep compare the contents. 484 | aKeys = _.keys(a); bKeys = _.keys(b) 485 | # Different object sizes? 486 | return false if aKeys.length isnt bKeys.length 487 | # Recursive comparison of contents. 488 | return false for key, val of a when !(key of b) or !_.isEqual(val, b[key]) 489 | true 490 | 491 | 492 | # Is a given array or object empty? 493 | _.isEmpty = (obj) -> 494 | return obj.length is 0 if _.isArray(obj) or _.isString(obj) 495 | return false for own key of obj 496 | true 497 | 498 | 499 | # Is a given value a DOM element? 500 | _.isElement = (obj) -> obj and obj.nodeType is 1 501 | 502 | 503 | # Is a given value an array? 504 | _.isArray = nativeIsArray or (obj) -> !!(obj and obj.concat and obj.unshift and not obj.callee) 505 | 506 | 507 | # Is a given variable an arguments object? 508 | _.isArguments = (obj) -> obj and obj.callee 509 | 510 | 511 | # Is the given value a function? 512 | _.isFunction = (obj) -> !!(obj and obj.constructor and obj.call and obj.apply) 513 | 514 | 515 | # Is the given value a string? 516 | _.isString = (obj) -> !!(obj is '' or (obj and obj.charCodeAt and obj.substr)) 517 | 518 | 519 | # Is a given value a number? 520 | _.isNumber = (obj) -> (obj is +obj) or toString.call(obj) is '[object Number]' 521 | 522 | 523 | # Is a given value a boolean? 524 | _.isBoolean = (obj) -> obj is true or obj is false 525 | 526 | 527 | # Is a given value a Date? 528 | _.isDate = (obj) -> !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear) 529 | 530 | 531 | # Is the given value a regular expression? 532 | _.isRegExp = (obj) -> !!(obj and obj.exec and (obj.ignoreCase or obj.ignoreCase is false)) 533 | 534 | 535 | # Is the given value NaN -- this one is interesting. `NaN != NaN`, and 536 | # `isNaN(undefined) == true`, so we make sure it's a number first. 537 | _.isNaN = (obj) -> _.isNumber(obj) and window.isNaN(obj) 538 | 539 | 540 | # Is a given value equal to null? 541 | _.isNull = (obj) -> obj is null 542 | 543 | 544 | # Is a given variable undefined? 545 | _.isUndefined = (obj) -> typeof obj is 'undefined' 546 | 547 | 548 | # Utility Functions 549 | # ----------------- 550 | 551 | # Run Underscore.js in noConflict mode, returning the `_` variable to its 552 | # previous owner. Returns a reference to the Underscore object. 553 | _.noConflict = -> 554 | root._ = previousUnderscore 555 | this 556 | 557 | 558 | # Keep the identity function around for default iterators. 559 | _.identity = (value) -> value 560 | 561 | 562 | # Run a function `n` times. 563 | _.times = (n, iterator, context) -> 564 | iterator.call context, i for i in [0...n] 565 | 566 | 567 | # Break out of the middle of an iteration. 568 | _.breakLoop = -> throw breaker 569 | 570 | 571 | # Add your own custom functions to the Underscore object, ensuring that 572 | # they're correctly added to the OOP wrapper as well. 573 | _.mixin = (obj) -> 574 | for name in _.functions(obj) 575 | addToWrapper name, _[name] = obj[name] 576 | 577 | 578 | # Generate a unique integer id (unique within the entire client session). 579 | # Useful for temporary DOM ids. 580 | idCounter = 0 581 | _.uniqueId = (prefix) -> 582 | (prefix or '') + idCounter++ 583 | 584 | 585 | # By default, Underscore uses **ERB**-style template delimiters, change the 586 | # following template settings to use alternative delimiters. 587 | _.templateSettings = { 588 | start: '<%' 589 | end: '%>' 590 | interpolate: /<%=(.+?)%>/g 591 | } 592 | 593 | 594 | # JavaScript templating a-la **ERB**, pilfered from John Resig's 595 | # *Secrets of the JavaScript Ninja*, page 83. 596 | # Single-quote fix from Rick Strahl. 597 | # With alterations for arbitrary delimiters, and to preserve whitespace. 598 | _.template = (str, data) -> 599 | c = _.templateSettings 600 | endMatch = new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g") 601 | fn = new Function 'obj', 602 | 'var p=[],print=function(){p.push.apply(p,arguments);};' + 603 | 'with(obj||{}){p.push(\'' + 604 | str.replace(/\r/g, '\\r') 605 | .replace(/\n/g, '\\n') 606 | .replace(/\t/g, '\\t') 607 | .replace(endMatch,"✄") 608 | .split("'").join("\\'") 609 | .split("✄").join("'") 610 | .replace(c.interpolate, "',$1,'") 611 | .split(c.start).join("');") 612 | .split(c.end).join("p.push('") + 613 | "');}return p.join('');" 614 | if data then fn(data) else fn 615 | 616 | 617 | # Aliases 618 | # ------- 619 | 620 | _.forEach = _.each 621 | _.foldl = _.inject = _.reduce 622 | _.foldr = _.reduceRight 623 | _.select = _.filter 624 | _.all = _.every 625 | _.any = _.some 626 | _.contains = _.include 627 | _.head = _.first 628 | _.tail = _.rest 629 | _.methods = _.functions 630 | 631 | 632 | # Setup the OOP Wrapper 633 | # --------------------- 634 | 635 | # If Underscore is called as a function, it returns a wrapped object that 636 | # can be used OO-style. This wrapper holds altered versions of all the 637 | # underscore functions. Wrapped objects may be chained. 638 | wrapper = (obj) -> 639 | this._wrapped = obj 640 | this 641 | 642 | 643 | # Helper function to continue chaining intermediate results. 644 | result = (obj, chain) -> 645 | if chain then _(obj).chain() else obj 646 | 647 | 648 | # A method to easily add functions to the OOP wrapper. 649 | addToWrapper = (name, func) -> 650 | wrapper.prototype[name] = -> 651 | args = _.toArray arguments 652 | unshift.call args, this._wrapped 653 | result func.apply(_, args), this._chain 654 | 655 | 656 | # Add all ofthe Underscore functions to the wrapper object. 657 | _.mixin _ 658 | 659 | 660 | # Add all mutator Array functions to the wrapper. 661 | _.each ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], (name) -> 662 | method = Array.prototype[name] 663 | wrapper.prototype[name] = -> 664 | method.apply(this._wrapped, arguments) 665 | result(this._wrapped, this._chain) 666 | 667 | 668 | # Add all accessor Array functions to the wrapper. 669 | _.each ['concat', 'join', 'slice'], (name) -> 670 | method = Array.prototype[name] 671 | wrapper.prototype[name] = -> 672 | result(method.apply(this._wrapped, arguments), this._chain) 673 | 674 | 675 | # Start chaining a wrapped Underscore object. 676 | wrapper::chain = -> 677 | this._chain = true 678 | this 679 | 680 | 681 | # Extracts the result from a wrapped and chained object. 682 | wrapper::value = -> this._wrapped -------------------------------------------------------------------------------- /file_utils.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | 3 | file_lines = (fn) -> 4 | fs.readFileSync(fn).toString().split '\n' 5 | 6 | get_num_lines_in_file = (fn) -> 7 | (fs.readFileSync(fn).toString().split '\n').length 8 | 9 | relative_path = (dir, fn) -> 10 | fn.substring(dir.length + 1, fn.length) 11 | 12 | walk = (dir, f_match, f_visit) -> 13 | _walk = (dir) -> 14 | fns = fs.readdirSync dir 15 | dirs = [] 16 | for fn in fns 17 | fn = dir + '/' + fn 18 | if f_match fn 19 | f_visit fn 20 | if fs.statSync(fn).isDirectory() 21 | dirs.push fn 22 | for dir in dirs 23 | _walk dir 24 | return 25 | _walk(dir) 26 | 27 | split_file = (fn) -> 28 | parts = fn.split '/' 29 | short_fn = parts.pop() 30 | [root, ext] = short_fn.split '.' 31 | [parts, root, ext] 32 | 33 | get_files = (dir, regex) -> 34 | matcher = (fn) -> 35 | fn.match(regex) 36 | files = [] 37 | walk dir, matcher, (fn) -> files.push fn 38 | files 39 | 40 | score_path_similarity = (path1, path2) -> 41 | # find out how many common directories there are, to 42 | # help us determine the likelihood of path2 being an 43 | # output directory for path1 44 | score = 0 45 | for part in path1 46 | score += 1 if part in path2 47 | score 48 | 49 | js_file_for = (cs_file, js_files) -> 50 | [cs_path, cs_root] = split_file cs_file 51 | match = null 52 | score = 0 53 | for js_file in js_files 54 | [js_path, js_root] = split_file js_file 55 | if js_root == cs_root 56 | new_score = score_path_similarity(cs_path, js_path) 57 | if new_score > score 58 | match = js_file 59 | score = new_score 60 | if match 61 | cs_time = fs.statSync(cs_file).mtime.toISOString() 62 | js_time = fs.statSync(match).mtime.toISOString() 63 | return null if cs_time > js_time 64 | match 65 | 66 | exports.file_lines = file_lines 67 | exports.get_num_lines_in_file = get_num_lines_in_file 68 | exports.relative_path = relative_path 69 | exports.split_file = split_file 70 | exports.get_files = get_files 71 | exports.js_file_for = js_file_for 72 | 73 | 74 | -------------------------------------------------------------------------------- /list_files.coffee: -------------------------------------------------------------------------------- 1 | file_utils = require './file_utils' 2 | {render} = require './render_file_list' 3 | 4 | list_files = (top_level_dir, get_files, coffee_file_regex, cb) -> 5 | 6 | html = """ 7 | 8 | 9 | CS/JS Code Browser 10 | 11 |

CS/JS Files in #{top_level_dir}

12 | About 13 | """ 14 | html += list_files_body top_level_dir, get_files, coffee_file_regex 15 | cb html 16 | 17 | list_files_body = (top_level_dir, get_files, coffee_file_regex) -> 18 | cs_files = get_files coffee_file_regex 19 | js_files = get_files /\.js$/ 20 | 21 | curr_cs_path = null 22 | dirs = [] 23 | for cs_file in cs_files 24 | [cs_path, cs_root] = file_utils.split_file cs_file 25 | cs_path = cs_path.join '/' 26 | if cs_path != curr_cs_path 27 | curr_cs_path = cs_path 28 | dir = 29 | path: file_utils.relative_path top_level_dir, cs_path 30 | rows: [] 31 | dirs.push dir 32 | row = data_for_file cs_file, cs_root, js_files, top_level_dir 33 | dir.rows.push row 34 | render dirs 35 | 36 | data_for_file = (cs_file, cs_root, js_files, top_level_dir) -> 37 | data = 38 | cs_href: "./view?FILE=#{encodeURI cs_file}" 39 | cs_root: cs_root 40 | cs_num_lines: file_utils.get_num_lines_in_file(cs_file) 41 | 42 | js_file = file_utils.js_file_for cs_file, js_files 43 | if js_file 44 | data.js_file = js_file 45 | data.js_path = file_utils.relative_path top_level_dir, js_file 46 | data 47 | 48 | exports.list_files = list_files -------------------------------------------------------------------------------- /render_file_list.coffee: -------------------------------------------------------------------------------- 1 | # This code renders the main part of the main page, where you list all 2 | # the coffeescript files, broken out by directory. 3 | 4 | render = (dirs) -> 5 | # Uncomment the next line if you want to see a raw view of the data. 6 | # return "
#{JSON.stringify dirs, null, '  '}
" 7 | html = '' 8 | for dir in dirs 9 | html += dir_header dir.path 10 | rows = (row_for_file data for data in dir.rows) 11 | html += render_dir_files rows 12 | html 13 | 14 | row_for_file = (data) -> 15 | view_link = "#{data.cs_root}" 16 | row = [data.cs_num_lines, view_link] 17 | if data.js_path 18 | row.push data.js_path 19 | row 20 | 21 | dir_header = (path) -> 22 | """ 23 |
24 |

#{path}

25 | """ 26 | 27 | render_dir_files = (rows) -> 28 | headers = ['line count for CS', 'coffee', 'JS file'] 29 | table headers, rows 30 | 31 | table = (headers, rows) -> 32 | html = '' 33 | html += '' 34 | ths = ("" for th in headers) 35 | html += ths.join '' 36 | html += '' 37 | for row in rows 38 | html += '' 39 | td = (cell) -> 40 | if cell.toString().match /^\d+$/ 41 | align = "center" 42 | else 43 | align = "left" 44 | "" 45 | html += (td cell for cell in row).join '' 46 | html += '' 47 | html += '
#{th}
#{cell}
' 48 | html 49 | 50 | exports.render = render -------------------------------------------------------------------------------- /side_by_side.coffee: -------------------------------------------------------------------------------- 1 | html_escape = (text) -> 2 | text = text.replace /&/g, "&" 3 | text = text.replace //g, ">" 5 | text 6 | 7 | pre = (s, klass) -> 8 | "
#{html_escape s}
" 9 | 10 | exports.side_by_side = (matches, source_lines, dest_lines) -> 11 | html = """ 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | """ 20 | 21 | row = (cells) -> 22 | html += '' 23 | html += ("" for cell in cells).join '' 24 | html += '' 25 | 26 | text = (lines, start, end) -> 27 | code = lines[start...end].join('\n').replace /\t/g, ' ' 28 | pre code, "code" 29 | 30 | line_numbers = (start, end, prefix) -> 31 | line_number = (ln) -> 32 | id = "#{prefix}_#{ln}" 33 | """#{prefix}:#{ln}""" 34 | numbers = (line_number(ln+1) for ln in [start...end]) 35 | "
#{numbers.join('\n')}
" 36 | 37 | last_match = '' 38 | s_start = 0 39 | d_start = 0 40 | for match in matches 41 | [s_end, d_end] = match 42 | s_line_numbers = line_numbers s_start, s_end, 'cs' 43 | s_snippet = text source_lines, s_start, s_end 44 | d_line_numbers = line_numbers d_start, d_end, 'js' 45 | d_snippet = text dest_lines, d_start, d_end 46 | row [s_line_numbers, s_snippet, d_line_numbers, d_snippet] 47 | s_start = s_end 48 | d_start = d_end 49 | last_match = match 50 | 51 | html += '
CSCoffeeScriptJSJavaScript
#{cell}
' 52 | html += '
End
' 53 | html 54 | -------------------------------------------------------------------------------- /static_website.coffee: -------------------------------------------------------------------------------- 1 | file_utils = require './file_utils' 2 | {side_by_side} = require './side_by_side' 3 | {source_line_mappings} = require './cs_js_source_mapping' 4 | JQUERY_CDN = """ 5 | 6 | """ 7 | 8 | head = -> 9 | console.log """ 10 | 11 | CoffeeScriptLineMatcher -- static demo 12 | 34 | #{JQUERY_CDN} 35 | 51 | 52 | 53 |

54 | This is a static demo of CoffeeScriptLineMatcher features. 55 |

56 |

57 | The source files shown here come from the CoffeeScriptLineMatcher project itself. You can learn more about the project 58 | here. 59 |

60 | """ 61 | 62 | generate_html = (fn, js_fn, cs_fn) -> 63 | html = "
" 64 | html += "
Back to top
" 65 | html += "JS file: #{js_fn}
" 66 | coffee_lines = file_utils.file_lines(cs_fn) 67 | js_lines = file_utils.file_lines(js_fn) 68 | matches = source_line_mappings coffee_lines, js_lines 69 | html += side_by_side matches, coffee_lines, js_lines 70 | console.log html 71 | 72 | do -> 73 | files = [ 74 | 'dashboard' 75 | 'cs_js_source_mapping' 76 | 'file_utils' 77 | 'side_by_side' 78 | ] 79 | head() 80 | 81 | html = '
    ' 82 | for fn in files 83 | html += "
  • #{fn}
  • " 84 | html += '
' 85 | console.log html 86 | 87 | for fn in files 88 | js_fn = fn + '.js' 89 | cs_fn = fn + '.coffee' 90 | generate_html fn, js_fn, cs_fn 91 | -------------------------------------------------------------------------------- /test/test.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | 3 | {source_line_mappings} = require '../cs_js_source_mapping' 4 | file_utils = require '../file_utils' 5 | 6 | get_old_results = -> 7 | data = fs.readFileSync('test.json').toString() 8 | JSON.parse data 9 | 10 | ensure_no_regression = (results) -> 11 | old_results = get_old_results() 12 | good = true 13 | for fn, old_map of old_results 14 | map = results[fn] 15 | for cs, js of old_map 16 | if map[cs] != js 17 | good = false 18 | console.log "mapping regression #{fn} line #{parseInt(cs)+1} js#{parseInt(js)+1} -> #{parseInt(map[cs]) + 1}" 19 | throw Error "bad mappings" unless good 20 | 21 | do -> 22 | 23 | dir = '../examples' 24 | cs_files = file_utils.get_files dir, /\.coffee/ 25 | js_files = file_utils.get_files dir, /\.js/ 26 | 27 | results = {} 28 | for fn in cs_files 29 | js_fn = file_utils.js_file_for fn, js_files 30 | if js_fn is null 31 | throw Error "need to compile js" 32 | 33 | coffee_lines = file_utils.file_lines(fn) 34 | js_lines = file_utils.file_lines(js_fn) 35 | matches = source_line_mappings coffee_lines, js_lines 36 | map = {} 37 | for match in matches 38 | [cs, js] = match 39 | map[cs] = js 40 | results[file_utils.relative_path dir, fn] = map 41 | 42 | ensure_no_regression results 43 | 44 | data = JSON.stringify results, null, ' ' 45 | fs.writeFileSync 'test.json', data 46 | -------------------------------------------------------------------------------- /test/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "bot.coffee": { 3 | "0": 4, 4 | "4": 4, 5 | "5": 6, 6 | "6": 8, 7 | "7": 10, 8 | "8": 12, 9 | "9": 14, 10 | "10": 16, 11 | "11": 18, 12 | "12": 20, 13 | "13": 22, 14 | "14": 24, 15 | "15": 26, 16 | "16": 28, 17 | "17": 30, 18 | "18": 32, 19 | "19": 34, 20 | "20": 34, 21 | "21": 36, 22 | "22": 38, 23 | "23": 40, 24 | "24": 40, 25 | "25": 42, 26 | "26": 44, 27 | "27": 46, 28 | "30": 51, 29 | "32": 53, 30 | "33": 54, 31 | "34": 56, 32 | "35": 58, 33 | "36": 59, 34 | "37": 60, 35 | "38": 64, 36 | "39": 65, 37 | "41": 67, 38 | "43": 73, 39 | "45": 78, 40 | "46": 82, 41 | "47": 82, 42 | "48": 84, 43 | "49": 86, 44 | "50": 88, 45 | "51": 90, 46 | "54": 95, 47 | "56": 97, 48 | "57": 98, 49 | "58": 103, 50 | "59": 105, 51 | "60": 106, 52 | "62": 111, 53 | "63": 115, 54 | "64": 115, 55 | "65": 116, 56 | "68": 116, 57 | "70": 118, 58 | "71": 119, 59 | "73": 121, 60 | "74": 123, 61 | "75": 128, 62 | "76": 130, 63 | "78": 131, 64 | "79": 132, 65 | "80": 133, 66 | "82": 136, 67 | "84": 140, 68 | "85": 140, 69 | "86": 141, 70 | "89": 143, 71 | "90": 144, 72 | "91": 146, 73 | "93": 147, 74 | "94": 148, 75 | "95": 149, 76 | "99": 154, 77 | "101": 161, 78 | "102": 161, 79 | "106": 163, 80 | "107": 164, 81 | "108": 166, 82 | "109": 168, 83 | "110": 168, 84 | "114": 170, 85 | "115": 170, 86 | "116": 172, 87 | "117": 172, 88 | "121": 179, 89 | "123": 179, 90 | "124": 180, 91 | "125": 181, 92 | "126": 182, 93 | "127": 183, 94 | "128": 184, 95 | "129": 185, 96 | "132": 189, 97 | "133": 189, 98 | "135": 191, 99 | "136": 192, 100 | "137": 193, 101 | "138": 198, 102 | "139": 198, 103 | "140": 200, 104 | "141": 202, 105 | "142": 202, 106 | "143": 204, 107 | "144": 204, 108 | "145": 212, 109 | "146": 220, 110 | "147": 224, 111 | "151": 234, 112 | "152": 236, 113 | "154": 247, 114 | "155": 249, 115 | "157": 260, 116 | "158": 260, 117 | "159": 262, 118 | "160": 262, 119 | "164": 266, 120 | "166": 268, 121 | "167": 269, 122 | "170": 276, 123 | "171": 276, 124 | "172": 277, 125 | "173": 280, 126 | "174": 280, 127 | "175": 281, 128 | "177": 285, 129 | "178": 289, 130 | "182": 292, 131 | "183": 296, 132 | "184": 297, 133 | "185": 299, 134 | "188": 300, 135 | "197": 314, 136 | "198": 317, 137 | "200": 319, 138 | "201": 323, 139 | "202": 324, 140 | "207": 331, 141 | "209": 336, 142 | "210": 340, 143 | "212": 342, 144 | "213": 346, 145 | "215": 349, 146 | "216": 353, 147 | "217": 354, 148 | "221": 361, 149 | "222": 362, 150 | "224": 364, 151 | "231": 386, 152 | "232": 388, 153 | "234": 390, 154 | "235": 394, 155 | "236": 403, 156 | "238": 406, 157 | "239": 410, 158 | "241": 417, 159 | "248": 430, 160 | "249": 430, 161 | "251": 436, 162 | "259": 451, 163 | "260": 452, 164 | "261": 452, 165 | "262": 453, 166 | "264": 454, 167 | "269": 463, 168 | "271": 467, 169 | "274": 472, 170 | "278": 479, 171 | "284": 491, 172 | "285": 493, 173 | "286": 494, 174 | "287": 495, 175 | "291": 501, 176 | "294": 509, 177 | "295": 511, 178 | "296": 512, 179 | "297": 513, 180 | "301": 519, 181 | "303": 525, 182 | "305": 529, 183 | "306": 530, 184 | "308": 532, 185 | "309": 537, 186 | "310": 538, 187 | "313": 542, 188 | "314": 546, 189 | "316": 550, 190 | "318": 556, 191 | "323": 566, 192 | "326": 569, 193 | "329": 579, 194 | "333": 587, 195 | "335": 592, 196 | "338": 599, 197 | "339": 600, 198 | "342": 605, 199 | "343": 607, 200 | "345": 609, 201 | "347": 615, 202 | "348": 616, 203 | "349": 618, 204 | "351": 620, 205 | "352": 621, 206 | "353": 623, 207 | "355": 626, 208 | "359": 636, 209 | "362": 639, 210 | "366": 652, 211 | "368": 656, 212 | "369": 660, 213 | "371": 663, 214 | "374": 668, 215 | "378": 684, 216 | "380": 688, 217 | "383": 694, 218 | "384": 696, 219 | "386": 700, 220 | "388": 704, 221 | "390": 709, 222 | "393": 716, 223 | "395": 721, 224 | "396": 725, 225 | "397": 726, 226 | "400": 732, 227 | "401": 734, 228 | "403": 736, 229 | "405": 742, 230 | "406": 743, 231 | "407": 745, 232 | "409": 747, 233 | "410": 748, 234 | "414": 753, 235 | "419": 764, 236 | "429": 779, 237 | "437": 779, 238 | "440": 782, 239 | "443": 794, 240 | "445": 802, 241 | "446": 803, 242 | "447": 804, 243 | "448": 806, 244 | "449": 808, 245 | "450": 810, 246 | "451": 814, 247 | "452": 815, 248 | "456": 816, 249 | "461": 822, 250 | "465": 828, 251 | "467": 832, 252 | "468": 833, 253 | "470": 837, 254 | "471": 838, 255 | "472": 839, 256 | "473": 840, 257 | "474": 841, 258 | "475": 842, 259 | "476": 843, 260 | "477": 844, 261 | "478": 845, 262 | "479": 846, 263 | "480": 848, 264 | "481": 849, 265 | "486": 850, 266 | "487": 851, 267 | "488": 852, 268 | "489": 853, 269 | "490": 854, 270 | "491": 855, 271 | "492": 857, 272 | "493": 858, 273 | "495": 863, 274 | "497": 865, 275 | "499": 868, 276 | "502": 878, 277 | "503": 887, 278 | "505": 890, 279 | "506": 891, 280 | "507": 892, 281 | "508": 893, 282 | "509": 898, 283 | "511": 911, 284 | "521": 911, 285 | "522": 912, 286 | "523": 915, 287 | "524": 915, 288 | "526": 918, 289 | "527": 921, 290 | "528": 921, 291 | "529": 923, 292 | "530": 924, 293 | "531": 925, 294 | "532": 926, 295 | "533": 926, 296 | "534": 928, 297 | "535": 929, 298 | "543": 935, 299 | "544": 937, 300 | "545": 937, 301 | "550": 943, 302 | "551": 943, 303 | "552": 945, 304 | "560": 960, 305 | "561": 960, 306 | "562": 962, 307 | "565": 965, 308 | "567": 967, 309 | "569": 971, 310 | "572": 973, 311 | "573": 974, 312 | "575": 976, 313 | "576": 978, 314 | "579": 982, 315 | "582": 987, 316 | "585": 995, 317 | "586": 995, 318 | "587": 997, 319 | "588": 998, 320 | "589": 999, 321 | "590": 1000, 322 | "592": 1003, 323 | "593": 1004, 324 | "594": 1005, 325 | "597": 1012, 326 | "598": 1013, 327 | "599": 1014, 328 | "600": 1015, 329 | "601": 1020, 330 | "603": 1022, 331 | "604": 1023, 332 | "605": 1024, 333 | "606": 1025, 334 | "609": 1028, 335 | "611": 1030, 336 | "613": 1033, 337 | "614": 1040, 338 | "616": 1044, 339 | "617": 1045, 340 | "619": 1049, 341 | "621": 1051, 342 | "622": 1052, 343 | "623": 1062, 344 | "624": 1062, 345 | "625": 1064, 346 | "626": 1065, 347 | "627": 1066, 348 | "628": 1067, 349 | "629": 1070, 350 | "630": 1071, 351 | "632": 1074, 352 | "633": 1075, 353 | "636": 1082, 354 | "637": 1083, 355 | "638": 1085, 356 | "639": 1086, 357 | "640": 1088, 358 | "641": 1089, 359 | "642": 1091, 360 | "643": 1092, 361 | "644": 1094, 362 | "645": 1095, 363 | "648": 1105, 364 | "649": 1105, 365 | "650": 1107, 366 | "653": 1110, 367 | "659": 1113, 368 | "660": 1114, 369 | "662": 1118, 370 | "663": 1118, 371 | "664": 1120, 372 | "665": 1120, 373 | "667": 1122, 374 | "668": 1123, 375 | "670": 1128, 376 | "671": 1128, 377 | "676": 1140, 378 | "678": 1140, 379 | "679": 1142, 380 | "680": 1144, 381 | "681": 1144, 382 | "685": 1148, 383 | "686": 1149, 384 | "687": 1150, 385 | "691": 1162, 386 | "693": 1164, 387 | "695": 1167, 388 | "703": 1180, 389 | "704": 1181, 390 | "707": 1183, 391 | "708": 1189 392 | }, 393 | "chat.coffee": { 394 | "0": 6, 395 | "1": 8, 396 | "2": 10, 397 | "3": 12, 398 | "4": 12, 399 | "5": 14, 400 | "9": 21, 401 | "10": 21, 402 | "12": 31, 403 | "13": 32, 404 | "14": 33, 405 | "15": 36, 406 | "16": 36, 407 | "17": 38, 408 | "18": 39, 409 | "19": 40, 410 | "20": 41, 411 | "21": 42, 412 | "22": 43, 413 | "23": 46, 414 | "24": 46, 415 | "26": 50, 416 | "27": 50, 417 | "28": 51, 418 | "31": 56, 419 | "32": 56, 420 | "33": 57, 421 | "36": 62, 422 | "37": 62, 423 | "38": 63, 424 | "39": 66, 425 | "40": 66, 426 | "42": 70, 427 | "43": 70, 428 | "46": 79, 429 | "47": 79, 430 | "50": 96, 431 | "52": 96, 432 | "55": 105, 433 | "56": 106, 434 | "57": 107, 435 | "58": 108, 436 | "59": 109, 437 | "60": 110, 438 | "61": 111, 439 | "62": 114, 440 | "63": 114, 441 | "64": 115, 442 | "65": 116, 443 | "66": 117, 444 | "68": 119, 445 | "69": 120, 446 | "71": 122, 447 | "72": 123, 448 | "73": 127, 449 | "74": 127, 450 | "75": 128, 451 | "76": 131, 452 | "77": 131, 453 | "78": 132, 454 | "79": 135, 455 | "80": 135, 456 | "81": 136, 457 | "82": 143, 458 | "84": 143, 459 | "87": 149, 460 | "88": 150, 461 | "89": 151, 462 | "90": 154, 463 | "91": 154, 464 | "92": 156, 465 | "96": 160, 466 | "97": 161, 467 | "99": 163, 468 | "101": 165, 469 | "103": 167, 470 | "105": 178, 471 | "106": 178, 472 | "107": 182 473 | }, 474 | "client.coffee": { 475 | "0": 4, 476 | "1": 8, 477 | "6": 10, 478 | "7": 10, 479 | "8": 11, 480 | "9": 12, 481 | "10": 12, 482 | "13": 17, 483 | "14": 17, 484 | "17": 23, 485 | "18": 23, 486 | "19": 24, 487 | "20": 26, 488 | "21": 26, 489 | "22": 27, 490 | "23": 28, 491 | "29": 35, 492 | "30": 35, 493 | "31": 38, 494 | "39": 49, 495 | "40": 49, 496 | "41": 50, 497 | "42": 51, 498 | "50": 63, 499 | "51": 63, 500 | "52": 65, 501 | "53": 66, 502 | "54": 67, 503 | "55": 68, 504 | "56": 69, 505 | "61": 76, 506 | "62": 76, 507 | "68": 82, 508 | "69": 82, 509 | "70": 83, 510 | "71": 83, 511 | "72": 84, 512 | "73": 85, 513 | "74": 86, 514 | "75": 87, 515 | "76": 88, 516 | "77": 90, 517 | "84": 95, 518 | "86": 95, 519 | "99": 103, 520 | "100": 104, 521 | "101": 105, 522 | "102": 105, 523 | "105": 110, 524 | "106": 110, 525 | "108": 113, 526 | "109": 113, 527 | "110": 114, 528 | "111": 115, 529 | "115": 119, 530 | "123": 127, 531 | "124": 127, 532 | "125": 128, 533 | "126": 129, 534 | "134": 139, 535 | "139": 145, 536 | "144": 152, 537 | "146": 154, 538 | "147": 155, 539 | "149": 157, 540 | "151": 159, 541 | "152": 160, 542 | "153": 165, 543 | "154": 165, 544 | "155": 167, 545 | "156": 168, 546 | "157": 169, 547 | "160": 174, 548 | "161": 174, 549 | "170": 181, 550 | "171": 181, 551 | "172": 184, 552 | "174": 195, 553 | "175": 195, 554 | "177": 198, 555 | "178": 198, 556 | "179": 199, 557 | "180": 199, 558 | "183": 201, 559 | "184": 202, 560 | "185": 203, 561 | "186": 204, 562 | "187": 204, 563 | "190": 206, 564 | "192": 208, 565 | "193": 209, 566 | "194": 209, 567 | "195": 210, 568 | "196": 211, 569 | "198": 213, 570 | "199": 214, 571 | "200": 214, 572 | "203": 217, 573 | "204": 218, 574 | "205": 219, 575 | "206": 220, 576 | "207": 221, 577 | "208": 222, 578 | "209": 222, 579 | "213": 226, 580 | "214": 227, 581 | "215": 228, 582 | "216": 229, 583 | "217": 230, 584 | "218": 230, 585 | "220": 232, 586 | "222": 234, 587 | "223": 235, 588 | "225": 237, 589 | "226": 238, 590 | "227": 238, 591 | "229": 240, 592 | "231": 242, 593 | "232": 243, 594 | "235": 246, 595 | "236": 247, 596 | "237": 247, 597 | "239": 249, 598 | "241": 251, 599 | "242": 252, 600 | "244": 254, 601 | "245": 255, 602 | "246": 255, 603 | "250": 259, 604 | "251": 260, 605 | "254": 263, 606 | "255": 264, 607 | "256": 265, 608 | "257": 266, 609 | "258": 267, 610 | "261": 270, 611 | "262": 270, 612 | "263": 271, 613 | "266": 273, 614 | "267": 274, 615 | "268": 275, 616 | "269": 276, 617 | "270": 278, 618 | "271": 278, 619 | "272": 279, 620 | "273": 280, 621 | "274": 281, 622 | "275": 281, 623 | "277": 284, 624 | "281": 288, 625 | "282": 289, 626 | "283": 290, 627 | "284": 295, 628 | "285": 295, 629 | "286": 297, 630 | "287": 298, 631 | "288": 298, 632 | "292": 304, 633 | "293": 304, 634 | "294": 306, 635 | "301": 319, 636 | "302": 320, 637 | "303": 323, 638 | "304": 326, 639 | "305": 329, 640 | "306": 332, 641 | "307": 335, 642 | "308": 338, 643 | "309": 341, 644 | "310": 344, 645 | "311": 347, 646 | "312": 350, 647 | "313": 353, 648 | "314": 356, 649 | "315": 359, 650 | "316": 362, 651 | "317": 365, 652 | "318": 368, 653 | "319": 371, 654 | "320": 374, 655 | "321": 377, 656 | "322": 380, 657 | "323": 383, 658 | "324": 386, 659 | "325": 389, 660 | "326": 392, 661 | "327": 396, 662 | "328": 396, 663 | "329": 397, 664 | "330": 397, 665 | "348": 398, 666 | "349": 398, 667 | "351": 401, 668 | "353": 404, 669 | "354": 406, 670 | "355": 406, 671 | "356": 408, 672 | "357": 409, 673 | "363": 417, 674 | "366": 417, 675 | "372": 423, 676 | "373": 423, 677 | "380": 429, 678 | "381": 429, 679 | "382": 430, 680 | "384": 432, 681 | "385": 433, 682 | "386": 433, 683 | "387": 434, 684 | "388": 435, 685 | "389": 436, 686 | "394": 443, 687 | "395": 443, 688 | "396": 445, 689 | "397": 446, 690 | "398": 447, 691 | "399": 448, 692 | "400": 448, 693 | "407": 457, 694 | "408": 457, 695 | "413": 464, 696 | "414": 464, 697 | "415": 466, 698 | "416": 467, 699 | "417": 468, 700 | "418": 469, 701 | "419": 470, 702 | "420": 471, 703 | "421": 472, 704 | "422": 473, 705 | "423": 474, 706 | "424": 475, 707 | "425": 476, 708 | "426": 477, 709 | "427": 479, 710 | "429": 479, 711 | "444": 480, 712 | "445": 480, 713 | "447": 483, 714 | "449": 486, 715 | "454": 490, 716 | "455": 490, 717 | "456": 503, 718 | "459": 506, 719 | "460": 506, 720 | "461": 507, 721 | "463": 510, 722 | "464": 510, 723 | "467": 514, 724 | "468": 514, 725 | "469": 515, 726 | "471": 518, 727 | "472": 518, 728 | "477": 526, 729 | "478": 526, 730 | "479": 528, 731 | "480": 529, 732 | "481": 530, 733 | "482": 531, 734 | "483": 532, 735 | "484": 533, 736 | "485": 534, 737 | "486": 535, 738 | "487": 536, 739 | "488": 537, 740 | "489": 538, 741 | "490": 539, 742 | "491": 540, 743 | "492": 541, 744 | "493": 542, 745 | "494": 543, 746 | "495": 544, 747 | "496": 545, 748 | "497": 546, 749 | "498": 547, 750 | "499": 549, 751 | "500": 549, 752 | "512": 564, 753 | "513": 564, 754 | "515": 567, 755 | "524": 579, 756 | "525": 579, 757 | "526": 580, 758 | "527": 581, 759 | "528": 583, 760 | "533": 586, 761 | "541": 593, 762 | "543": 595, 763 | "544": 596, 764 | "545": 597, 765 | "546": 597, 766 | "548": 599, 767 | "549": 600, 768 | "556": 608, 769 | "560": 612, 770 | "561": 613, 771 | "564": 616, 772 | "565": 618, 773 | "566": 619, 774 | "567": 620, 775 | "569": 622, 776 | "575": 629, 777 | "578": 633, 778 | "579": 634, 779 | "584": 642, 780 | "586": 647, 781 | "587": 647, 782 | "588": 648, 783 | "589": 649, 784 | "591": 652, 785 | "592": 652, 786 | "594": 654, 787 | "596": 657, 788 | "597": 657, 789 | "598": 658, 790 | "599": 659, 791 | "605": 666, 792 | "611": 676 793 | }, 794 | "coffeekup.coffee": { 795 | "15": 8, 796 | "16": 9, 797 | "18": 11, 798 | "19": 12, 799 | "20": 15, 800 | "21": 15, 801 | "22": 17, 802 | "25": 17, 803 | "26": 18, 804 | "28": 20, 805 | "29": 21, 806 | "30": 22, 807 | "31": 23, 808 | "32": 24, 809 | "33": 25, 810 | "34": 26, 811 | "35": 27, 812 | "36": 30, 813 | "42": 30, 814 | "56": 32, 815 | "59": 32, 816 | "60": 33, 817 | "62": 33, 818 | "73": 42, 819 | "74": 42, 820 | "77": 45, 821 | "78": 45, 822 | "79": 48, 823 | "81": 48, 824 | "82": 51, 825 | "87": 63, 826 | "91": 63, 827 | "92": 65, 828 | "94": 65, 829 | "95": 67, 830 | "99": 67, 831 | "107": 72, 832 | "109": 72, 833 | "110": 73, 834 | "111": 74, 835 | "112": 74, 836 | "114": 81, 837 | "115": 81, 838 | "116": 82, 839 | "117": 82, 840 | "118": 85, 841 | "119": 85, 842 | "120": 88, 843 | "122": 88, 844 | "123": 90, 845 | "126": 97, 846 | "127": 97, 847 | "128": 99, 848 | "135": 109, 849 | "136": 109, 850 | "139": 111, 851 | "144": 120, 852 | "145": 120, 853 | "147": 126, 854 | "148": 126, 855 | "152": 128, 856 | "154": 128, 857 | "161": 138, 858 | "162": 138, 859 | "164": 141, 860 | "166": 145, 861 | "169": 148, 862 | "170": 149, 863 | "176": 158, 864 | "177": 158, 865 | "185": 164, 866 | "196": 175, 867 | "197": 175, 868 | "200": 181, 869 | "201": 182, 870 | "202": 184, 871 | "203": 185, 872 | "204": 187, 873 | "205": 189, 874 | "206": 191, 875 | "208": 193, 876 | "211": 196, 877 | "213": 198, 878 | "216": 205, 879 | "217": 205, 880 | "218": 207, 881 | "219": 208, 882 | "220": 209, 883 | "226": 215, 884 | "230": 217, 885 | "231": 217, 886 | "234": 222, 887 | "235": 222, 888 | "238": 226, 889 | "239": 226, 890 | "242": 230, 891 | "243": 230, 892 | "245": 232, 893 | "247": 232, 894 | "249": 234, 895 | "251": 234, 896 | "252": 236, 897 | "253": 240, 898 | "255": 240, 899 | "256": 241, 900 | "258": 245, 901 | "260": 245, 902 | "265": 249, 903 | "269": 255, 904 | "272": 255, 905 | "277": 259, 906 | "279": 259, 907 | "280": 262, 908 | "282": 262, 909 | "283": 264, 910 | "286": 270, 911 | "290": 270, 912 | "291": 271, 913 | "292": 271, 914 | "294": 275, 915 | "295": 276, 916 | "296": 276, 917 | "298": 282, 918 | "301": 282, 919 | "302": 283, 920 | "307": 291, 921 | "308": 291, 922 | "311": 296, 923 | "313": 296, 924 | "314": 297, 925 | "315": 297, 926 | "325": 304, 927 | "326": 304, 928 | "327": 307, 929 | "328": 307, 930 | "329": 309, 931 | "339": 309, 932 | "340": 315, 933 | "349": 329, 934 | "350": 330, 935 | "351": 330, 936 | "352": 331, 937 | "355": 333, 938 | "359": 343, 939 | "360": 348, 940 | "361": 348, 941 | "364": 351, 942 | "366": 354, 943 | "367": 354, 944 | "374": 374 945 | }, 946 | "game_of_life.coffee": { 947 | "0": 3, 948 | "16": 3, 949 | "20": 6, 950 | "21": 6, 951 | "24": 10, 952 | "26": 12, 953 | "29": 19, 954 | "32": 19, 955 | "33": 20, 956 | "35": 22, 957 | "36": 26, 958 | "41": 26, 959 | "42": 28, 960 | "43": 30, 961 | "44": 31, 962 | "52": 41, 963 | "55": 49, 964 | "63": 61, 965 | "69": 61, 966 | "70": 63, 967 | "71": 64, 968 | "74": 69, 969 | "75": 70, 970 | "77": 73, 971 | "79": 79, 972 | "82": 79, 973 | "87": 87, 974 | "92": 87, 975 | "94": 90, 976 | "96": 90, 977 | "101": 91, 978 | "106": 92, 979 | "109": 92, 980 | "121": 103, 981 | "122": 104, 982 | "128": 108, 983 | "136": 108, 984 | "137": 110, 985 | "138": 110, 986 | "139": 111, 987 | "141": 111, 988 | "142": 113, 989 | "147": 121, 990 | "151": 121, 991 | "152": 123, 992 | "153": 124, 993 | "155": 127, 994 | "157": 131, 995 | "159": 131, 996 | "160": 132, 997 | "161": 135, 998 | "162": 138, 999 | "163": 141, 1000 | "164": 147, 1001 | "167": 147, 1002 | "183": 164, 1003 | "188": 164, 1004 | "189": 165, 1005 | "193": 172, 1006 | "198": 172, 1007 | "203": 179, 1008 | "208": 179, 1009 | "209": 181, 1010 | "213": 187, 1011 | "214": 187, 1012 | "226": 202, 1013 | "231": 202, 1014 | "232": 204, 1015 | "235": 204, 1016 | "245": 205, 1017 | "250": 220, 1018 | "251": 220, 1019 | "254": 231, 1020 | "255": 231, 1021 | "261": 240, 1022 | "265": 240, 1023 | "266": 242, 1024 | "267": 243, 1025 | "268": 245, 1026 | "269": 245, 1027 | "270": 247, 1028 | "280": 257, 1029 | "284": 257, 1030 | "285": 259, 1031 | "286": 261, 1032 | "289": 269, 1033 | "291": 280, 1034 | "296": 280, 1035 | "297": 282, 1036 | "298": 283, 1037 | "299": 284, 1038 | "300": 284, 1039 | "301": 285, 1040 | "304": 288, 1041 | "308": 296, 1042 | "311": 296, 1043 | "312": 298, 1044 | "313": 298, 1045 | "314": 299, 1046 | "315": 300, 1047 | "316": 301, 1048 | "317": 302, 1049 | "319": 302, 1050 | "321": 304, 1051 | "322": 304, 1052 | "323": 305, 1053 | "324": 305, 1054 | "335": 311 1055 | }, 1056 | "hanoi.coffee": { 1057 | "0": 3, 1058 | "1": 7, 1059 | "2": 8, 1060 | "8": 19 1061 | }, 1062 | "knight.coffee": { 1063 | "0": 3, 1064 | "1": 5, 1065 | "8": 5, 1066 | "9": 6, 1067 | "10": 15, 1068 | "11": 24, 1069 | "12": 25, 1070 | "13": 25, 1071 | "14": 27, 1072 | "20": 37, 1073 | "21": 37, 1074 | "22": 39, 1075 | "24": 41, 1076 | "26": 41, 1077 | "27": 42, 1078 | "31": 46, 1079 | "32": 47, 1080 | "36": 53, 1081 | "37": 54, 1082 | "38": 55, 1083 | "44": 70, 1084 | "45": 70, 1085 | "46": 71, 1086 | "48": 73, 1087 | "49": 73, 1088 | "50": 74, 1089 | "51": 75, 1090 | "53": 81, 1091 | "55": 81, 1092 | "56": 83, 1093 | "59": 83, 1094 | "62": 86, 1095 | "63": 86, 1096 | "64": 88, 1097 | "74": 89, 1098 | "83": 101, 1099 | "84": 101, 1100 | "89": 110, 1101 | "90": 110, 1102 | "91": 112, 1103 | "95": 117, 1104 | "96": 117, 1105 | "97": 118, 1106 | "99": 121, 1107 | "105": 134, 1108 | "106": 134, 1109 | "107": 136, 1110 | "108": 138, 1111 | "109": 138, 1112 | "110": 140, 1113 | "115": 150 1114 | }, 1115 | "lru.coffee": { 1116 | "0": 3, 1117 | "1": 5, 1118 | "12": 5, 1119 | "13": 6, 1120 | "14": 7, 1121 | "15": 8, 1122 | "16": 8, 1123 | "18": 10, 1124 | "19": 11, 1125 | "20": 12, 1126 | "22": 14, 1127 | "23": 16, 1128 | "24": 17, 1129 | "26": 19, 1130 | "27": 22, 1131 | "28": 22, 1132 | "29": 23, 1133 | "30": 25, 1134 | "32": 27, 1135 | "33": 28, 1136 | "34": 29, 1137 | "35": 30, 1138 | "36": 32, 1139 | "37": 33, 1140 | "40": 36, 1141 | "41": 37, 1142 | "42": 38, 1143 | "43": 40, 1144 | "44": 41, 1145 | "48": 47, 1146 | "49": 47, 1147 | "51": 50, 1148 | "54": 53, 1149 | "55": 54, 1150 | "57": 56, 1151 | "58": 57, 1152 | "60": 61, 1153 | "61": 61, 1154 | "62": 63, 1155 | "64": 64, 1156 | "65": 65, 1157 | "69": 73, 1158 | "70": 73, 1159 | "72": 76, 1160 | "73": 76, 1161 | "74": 78, 1162 | "75": 78, 1163 | "76": 79, 1164 | "82": 85, 1165 | "83": 86, 1166 | "84": 87, 1167 | "92": 99, 1168 | "93": 99, 1169 | "94": 101, 1170 | "97": 101, 1171 | "98": 102, 1172 | "99": 103, 1173 | "100": 103, 1174 | "102": 106, 1175 | "105": 109, 1176 | "106": 110, 1177 | "107": 111, 1178 | "108": 114, 1179 | "109": 114, 1180 | "110": 116, 1181 | "112": 118, 1182 | "113": 120, 1183 | "114": 120, 1184 | "115": 121, 1185 | "116": 123, 1186 | "117": 124, 1187 | "121": 130, 1188 | "122": 130, 1189 | "123": 132, 1190 | "127": 137, 1191 | "128": 137, 1192 | "129": 138, 1193 | "130": 138, 1194 | "131": 139, 1195 | "132": 140, 1196 | "136": 144, 1197 | "146": 156 1198 | }, 1199 | "nodes.coffee": { 1200 | "0": 6, 1201 | "5": 6, 1202 | "6": 8, 1203 | "7": 10, 1204 | "9": 10, 1205 | "10": 12, 1206 | "11": 12, 1207 | "12": 14, 1208 | "14": 14, 1209 | "16": 22, 1210 | "17": 26, 1211 | "18": 31, 1212 | "30": 31, 1213 | "31": 35, 1214 | "38": 35, 1215 | "40": 38, 1216 | "41": 39, 1217 | "42": 40, 1218 | "47": 48, 1219 | "50": 48, 1220 | "52": 50, 1221 | "53": 52, 1222 | "55": 56, 1223 | "59": 56, 1224 | "61": 59, 1225 | "65": 63, 1226 | "67": 72, 1227 | "69": 72, 1228 | "70": 74, 1229 | "74": 81, 1230 | "78": 81, 1231 | "80": 84, 1232 | "84": 91, 1233 | "89": 91, 1234 | "96": 103, 1235 | "98": 103, 1236 | "100": 109, 1237 | "102": 109, 1238 | "106": 118, 1239 | "109": 118, 1240 | "110": 122, 1241 | "114": 123, 1242 | "115": 124, 1243 | "118": 131, 1244 | "125": 131, 1245 | "127": 134, 1246 | "130": 139, 1247 | "131": 144, 1248 | "133": 144, 1249 | "139": 161, 1250 | "140": 161, 1251 | "144": 168, 1252 | "145": 168, 1253 | "147": 172, 1254 | "148": 172, 1255 | "149": 174, 1256 | "152": 181, 1257 | "155": 181, 1258 | "156": 183, 1259 | "157": 183, 1260 | "158": 185, 1261 | "159": 187, 1262 | "160": 189, 1263 | "161": 191, 1264 | "162": 193, 1265 | "163": 193, 1266 | "164": 195, 1267 | "165": 197, 1268 | "167": 197, 1269 | "168": 203, 1270 | "174": 203, 1271 | "176": 208, 1272 | "177": 211, 1273 | "178": 211, 1274 | "179": 213, 1275 | "181": 213, 1276 | "184": 218, 1277 | "186": 218, 1278 | "188": 222, 1279 | "190": 222, 1280 | "193": 227, 1281 | "196": 227, 1282 | "198": 235, 1283 | "200": 235, 1284 | "202": 239, 1285 | "203": 239, 1286 | "207": 249, 1287 | "208": 249, 1288 | "211": 258, 1289 | "214": 258, 1290 | "215": 260, 1291 | "217": 262, 1292 | "219": 264, 1293 | "223": 274, 1294 | "225": 274, 1295 | "227": 283, 1296 | "231": 283, 1297 | "232": 285, 1298 | "233": 286, 1299 | "234": 287, 1300 | "236": 290, 1301 | "244": 296, 1302 | "245": 297, 1303 | "249": 303, 1304 | "250": 304, 1305 | "251": 305, 1306 | "254": 310, 1307 | "256": 318, 1308 | "261": 318, 1309 | "262": 320, 1310 | "263": 321, 1311 | "264": 322, 1312 | "265": 323, 1313 | "266": 324, 1314 | "268": 332, 1315 | "271": 332, 1316 | "272": 334, 1317 | "274": 337, 1318 | "277": 344, 1319 | "278": 345, 1320 | "279": 346, 1321 | "280": 347, 1322 | "281": 349, 1323 | "284": 352, 1324 | "285": 353, 1325 | "287": 354, 1326 | "288": 355, 1327 | "289": 356, 1328 | "290": 358, 1329 | "291": 359, 1330 | "293": 365, 1331 | "296": 365, 1332 | "299": 374, 1333 | "305": 374, 1334 | "307": 382, 1335 | "308": 382, 1336 | "310": 390, 1337 | "311": 390, 1338 | "313": 394, 1339 | "314": 394, 1340 | "315": 396, 1341 | "316": 399, 1342 | "317": 399, 1343 | "318": 401, 1344 | "319": 401, 1345 | "321": 405, 1346 | "322": 405, 1347 | "324": 407, 1348 | "325": 414, 1349 | "326": 414, 1350 | "327": 416, 1351 | "336": 424, 1352 | "337": 424, 1353 | "339": 432, 1354 | "344": 432, 1355 | "346": 437, 1356 | "347": 440, 1357 | "348": 440, 1358 | "349": 442, 1359 | "350": 442, 1360 | "351": 444, 1361 | "352": 446, 1362 | "353": 448, 1363 | "354": 448, 1364 | "355": 450, 1365 | "357": 458, 1366 | "358": 458, 1367 | "360": 466, 1368 | "365": 466, 1369 | "368": 472, 1370 | "369": 473, 1371 | "372": 478, 1372 | "373": 478, 1373 | "374": 480, 1374 | "376": 480, 1375 | "377": 481, 1376 | "379": 485, 1377 | "380": 485, 1378 | "382": 489, 1379 | "384": 489, 1380 | "385": 493, 1381 | "386": 497, 1382 | "387": 501, 1383 | "388": 505, 1384 | "392": 515, 1385 | "393": 515, 1386 | "394": 519, 1387 | "395": 523, 1388 | "396": 527, 1389 | "397": 527, 1390 | "400": 532, 1391 | "401": 532, 1392 | "403": 536, 1393 | "406": 536, 1394 | "408": 544, 1395 | "412": 544, 1396 | "413": 546, 1397 | "416": 550, 1398 | "417": 551, 1399 | "418": 552, 1400 | "419": 553, 1401 | "421": 556, 1402 | "422": 557, 1403 | "423": 558, 1404 | "424": 559, 1405 | "426": 564, 1406 | "431": 564, 1407 | "432": 566, 1408 | "433": 567, 1409 | "434": 568, 1410 | "436": 574, 1411 | "438": 579, 1412 | "440": 579, 1413 | "442": 583, 1414 | "447": 593, 1415 | "448": 594, 1416 | "449": 595, 1417 | "450": 596, 1418 | "451": 597, 1419 | "452": 598, 1420 | "453": 599, 1421 | "456": 607, 1422 | "457": 614, 1423 | "462": 614, 1424 | "464": 622, 1425 | "465": 622, 1426 | "466": 624, 1427 | "467": 626, 1428 | "468": 626, 1429 | "469": 628, 1430 | "472": 637, 1431 | "477": 637, 1432 | "479": 644, 1433 | "480": 645, 1434 | "481": 646, 1435 | "482": 649, 1436 | "483": 649, 1437 | "484": 651, 1438 | "486": 651, 1439 | "487": 653, 1440 | "491": 657, 1441 | "493": 662, 1442 | "496": 662, 1443 | "498": 665, 1444 | "500": 668, 1445 | "501": 670, 1446 | "502": 671, 1447 | "503": 672, 1448 | "508": 680, 1449 | "510": 680, 1450 | "511": 682, 1451 | "512": 683, 1452 | "513": 684, 1453 | "516": 685, 1454 | "519": 691, 1455 | "520": 692, 1456 | "522": 697, 1457 | "523": 698, 1458 | "527": 702, 1459 | "533": 712, 1460 | "535": 714, 1461 | "537": 716, 1462 | "538": 719, 1463 | "540": 724, 1464 | "543": 724, 1465 | "544": 726, 1466 | "549": 733, 1467 | "558": 749, 1468 | "560": 749, 1469 | "564": 755, 1470 | "566": 765, 1471 | "569": 768, 1472 | "570": 772, 1473 | "573": 772, 1474 | "575": 776, 1475 | "580": 776, 1476 | "582": 781, 1477 | "583": 782, 1478 | "591": 785, 1479 | "593": 787, 1480 | "594": 788, 1481 | "598": 792, 1482 | "599": 793, 1483 | "600": 794, 1484 | "602": 796, 1485 | "604": 806, 1486 | "610": 806, 1487 | "612": 815, 1488 | "613": 815, 1489 | "614": 817, 1490 | "616": 817, 1491 | "617": 818, 1492 | "618": 825, 1493 | "623": 825, 1494 | "625": 831, 1495 | "626": 832, 1496 | "627": 835, 1497 | "628": 835, 1498 | "629": 837, 1499 | "630": 837, 1500 | "631": 839, 1501 | "633": 847, 1502 | "634": 847, 1503 | "635": 853, 1504 | "639": 853, 1505 | "641": 861, 1506 | "642": 861, 1507 | "643": 863, 1508 | "644": 863, 1509 | "646": 867, 1510 | "647": 867, 1511 | "649": 875, 1512 | "655": 875, 1513 | "656": 879, 1514 | "657": 879, 1515 | "660": 884, 1516 | "661": 885, 1517 | "662": 888, 1518 | "665": 888, 1519 | "669": 895, 1520 | "671": 899, 1521 | "672": 902, 1522 | "675": 902, 1523 | "678": 906, 1524 | "680": 906, 1525 | "681": 907, 1526 | "682": 908, 1527 | "683": 909, 1528 | "686": 912, 1529 | "688": 912, 1530 | "696": 913, 1531 | "698": 913, 1532 | "707": 917, 1533 | "710": 917, 1534 | "712": 920, 1535 | "715": 928, 1536 | "717": 930, 1537 | "718": 931, 1538 | "720": 933, 1539 | "721": 934, 1540 | "723": 936, 1541 | "724": 937, 1542 | "725": 938, 1543 | "726": 940, 1544 | "727": 941, 1545 | "728": 946, 1546 | "730": 954, 1547 | "736": 954, 1548 | "737": 958, 1549 | "738": 958, 1550 | "742": 965, 1551 | "746": 965, 1552 | "748": 968, 1553 | "749": 969, 1554 | "751": 971, 1555 | "758": 980, 1556 | "762": 980, 1557 | "764": 986, 1558 | "765": 989, 1559 | "766": 989, 1560 | "767": 991, 1561 | "768": 991, 1562 | "769": 993, 1563 | "770": 994, 1564 | "771": 995, 1565 | "773": 999, 1566 | "774": 1003, 1567 | "775": 1004, 1568 | "776": 1005, 1569 | "777": 1010, 1570 | "783": 1011, 1571 | "785": 1013, 1572 | "791": 1023, 1573 | "792": 1024, 1574 | "794": 1032, 1575 | "795": 1032, 1576 | "798": 1046, 1577 | "802": 1046, 1578 | "804": 1051, 1579 | "805": 1054, 1580 | "806": 1054, 1581 | "807": 1056, 1582 | "808": 1056, 1583 | "809": 1058, 1584 | "810": 1058, 1585 | "812": 1061, 1586 | "813": 1062, 1587 | "815": 1063, 1588 | "820": 1080, 1589 | "821": 1080, 1590 | "824": 1094, 1591 | "830": 1094, 1592 | "832": 1102, 1593 | "833": 1103, 1594 | "834": 1106, 1595 | "835": 1106, 1596 | "836": 1108, 1597 | "838": 1108, 1598 | "840": 1111, 1599 | "845": 1115, 1600 | "848": 1115, 1601 | "851": 1118, 1602 | "854": 1121, 1603 | "855": 1122, 1604 | "856": 1127, 1605 | "859": 1127, 1606 | "860": 1129, 1607 | "862": 1134, 1608 | "864": 1141, 1609 | "867": 1141, 1610 | "868": 1143, 1611 | "869": 1144, 1612 | "871": 1149, 1613 | "873": 1151, 1614 | "874": 1152, 1615 | "875": 1153, 1616 | "876": 1154, 1617 | "877": 1156, 1618 | "878": 1157, 1619 | "880": 1160, 1620 | "882": 1162, 1621 | "883": 1163, 1622 | "886": 1167, 1623 | "888": 1169, 1624 | "891": 1172, 1625 | "894": 1184, 1626 | "896": 1184, 1627 | "902": 1194, 1628 | "903": 1197, 1629 | "904": 1202, 1630 | "907": 1202, 1631 | "909": 1204, 1632 | "913": 1213, 1633 | "914": 1214, 1634 | "915": 1215, 1635 | "916": 1218, 1636 | "920": 1218, 1637 | "921": 1220, 1638 | "922": 1221, 1639 | "924": 1223, 1640 | "929": 1227, 1641 | "934": 1234, 1642 | "935": 1234, 1643 | "938": 1243, 1644 | "943": 1243, 1645 | "945": 1251, 1646 | "946": 1252, 1647 | "947": 1255, 1648 | "948": 1255, 1649 | "949": 1257, 1650 | "950": 1257, 1651 | "952": 1261, 1652 | "953": 1261, 1653 | "954": 1262, 1654 | "955": 1265, 1655 | "956": 1265, 1656 | "957": 1266, 1657 | "958": 1269, 1658 | "963": 1269, 1659 | "967": 1276, 1660 | "968": 1280, 1661 | "971": 1283, 1662 | "973": 1286, 1663 | "974": 1287, 1664 | "978": 1294, 1665 | "979": 1295, 1666 | "980": 1297, 1667 | "984": 1307, 1668 | "989": 1307, 1669 | "990": 1309, 1670 | "994": 1313, 1671 | "996": 1320, 1672 | "1005": 1323, 1673 | "1009": 1331, 1674 | "1010": 1332, 1675 | "1012": 1334, 1676 | "1015": 1341, 1677 | "1016": 1342, 1678 | "1017": 1343, 1679 | "1020": 1346, 1680 | "1022": 1350, 1681 | "1023": 1350, 1682 | "1024": 1351, 1683 | "1033": 1353, 1684 | "1035": 1363, 1685 | "1036": 1364, 1686 | "1037": 1365, 1687 | "1039": 1367, 1688 | "1040": 1368, 1689 | "1043": 1372, 1690 | "1044": 1373, 1691 | "1046": 1375, 1692 | "1048": 1377, 1693 | "1051": 1380, 1694 | "1053": 1382, 1695 | "1056": 1386, 1696 | "1057": 1388, 1697 | "1061": 1397, 1698 | "1063": 1405, 1699 | "1067": 1405, 1700 | "1071": 1412, 1701 | "1074": 1412, 1702 | "1076": 1415, 1703 | "1078": 1417, 1704 | "1083": 1422, 1705 | "1084": 1423, 1706 | "1086": 1426, 1707 | "1088": 1429, 1708 | "1090": 1441, 1709 | "1096": 1441, 1710 | "1098": 1446, 1711 | "1099": 1447, 1712 | "1100": 1448, 1713 | "1101": 1449, 1714 | "1102": 1452, 1715 | "1103": 1452, 1716 | "1104": 1454, 1717 | "1105": 1454, 1718 | "1106": 1458, 1719 | "1107": 1458, 1720 | "1108": 1460, 1721 | "1114": 1460, 1722 | "1115": 1462, 1723 | "1116": 1463, 1724 | "1117": 1464, 1725 | "1119": 1466, 1726 | "1120": 1467, 1727 | "1122": 1475, 1728 | "1123": 1477, 1729 | "1124": 1486, 1730 | "1127": 1492, 1731 | "1128": 1493, 1732 | "1133": 1494, 1733 | "1134": 1501, 1734 | "1135": 1502, 1735 | "1138": 1508, 1736 | "1143": 1520, 1737 | "1145": 1522, 1738 | "1147": 1524, 1739 | "1148": 1527, 1740 | "1149": 1528, 1741 | "1150": 1529, 1742 | "1156": 1543, 1743 | "1159": 1543, 1744 | "1161": 1553, 1745 | "1167": 1553, 1746 | "1169": 1563, 1747 | "1170": 1563, 1748 | "1171": 1565, 1749 | "1172": 1565, 1750 | "1174": 1569, 1751 | "1175": 1569, 1752 | "1177": 1572, 1753 | "1185": 1581, 1754 | "1186": 1584, 1755 | "1187": 1584, 1756 | "1189": 1592, 1757 | "1194": 1592, 1758 | "1195": 1596, 1759 | "1196": 1596, 1760 | "1197": 1598, 1761 | "1198": 1598, 1762 | "1201": 1601, 1763 | "1202": 1604, 1764 | "1203": 1604, 1765 | "1205": 1608, 1766 | "1206": 1608, 1767 | "1208": 1616, 1768 | "1209": 1616, 1769 | "1210": 1620, 1770 | "1213": 1620, 1771 | "1214": 1622, 1772 | "1218": 1628, 1773 | "1221": 1632, 1774 | "1223": 1635, 1775 | "1224": 1636, 1776 | "1228": 1641, 1777 | "1230": 1658, 1778 | "1236": 1658, 1779 | "1238": 1663, 1780 | "1239": 1664, 1781 | "1240": 1667, 1782 | "1241": 1667, 1783 | "1242": 1669, 1784 | "1243": 1669, 1785 | "1244": 1671, 1786 | "1245": 1671, 1787 | "1246": 1672, 1788 | "1249": 1675, 1789 | "1251": 1680, 1790 | "1252": 1680, 1791 | "1254": 1685, 1792 | "1255": 1685, 1793 | "1261": 1698, 1794 | "1265": 1698, 1795 | "1266": 1700, 1796 | "1267": 1701, 1797 | "1269": 1703, 1798 | "1270": 1704, 1799 | "1272": 1706, 1800 | "1273": 1707, 1801 | "1274": 1708, 1802 | "1275": 1710, 1803 | "1277": 1712, 1804 | "1279": 1714, 1805 | "1281": 1719, 1806 | "1282": 1720, 1807 | "1285": 1728, 1808 | "1290": 1728, 1809 | "1294": 1737, 1810 | "1297": 1741, 1811 | "1299": 1746, 1812 | "1300": 1749, 1813 | "1301": 1750, 1814 | "1302": 1751, 1815 | "1303": 1752, 1816 | "1305": 1756, 1817 | "1307": 1756, 1818 | "1308": 1757, 1819 | "1309": 1758, 1820 | "1311": 1762, 1821 | "1313": 1762, 1822 | "1314": 1763, 1823 | "1316": 1767, 1824 | "1317": 1767, 1825 | "1318": 1769, 1826 | "1319": 1769, 1827 | "1320": 1771, 1828 | "1321": 1771, 1829 | "1323": 1775, 1830 | "1324": 1775, 1831 | "1326": 1780, 1832 | "1329": 1780, 1833 | "1330": 1782, 1834 | "1331": 1785, 1835 | "1332": 1785, 1836 | "1334": 1788, 1837 | "1335": 1789, 1838 | "1342": 1797, 1839 | "1343": 1798, 1840 | "1344": 1799, 1841 | "1347": 1803, 1842 | "1354": 1808, 1843 | "1358": 1815, 1844 | "1359": 1815, 1845 | "1360": 1817, 1846 | "1361": 1820, 1847 | "1362": 1820, 1848 | "1363": 1822, 1849 | "1364": 1823, 1850 | "1366": 1823, 1851 | "1370": 1827, 1852 | "1373": 1835, 1853 | "1379": 1835, 1854 | "1381": 1838, 1855 | "1382": 1839, 1856 | "1384": 1843, 1857 | "1385": 1843, 1858 | "1386": 1845, 1859 | "1387": 1846, 1860 | "1388": 1847, 1861 | "1391": 1850, 1862 | "1393": 1857, 1863 | "1395": 1857, 1864 | "1396": 1859, 1865 | "1397": 1860, 1866 | "1398": 1861, 1867 | "1401": 1865, 1868 | "1405": 1872, 1869 | "1406": 1872, 1870 | "1408": 1880, 1871 | "1410": 1880, 1872 | "1412": 1889, 1873 | "1413": 1889, 1874 | "1414": 1891, 1875 | "1415": 1891, 1876 | "1416": 1893, 1877 | "1417": 1893, 1878 | "1420": 1900, 1879 | "1425": 1908, 1880 | "1426": 1908, 1881 | "1429": 1912, 1882 | "1430": 1913, 1883 | "1434": 1931, 1884 | "1435": 1931, 1885 | "1437": 1934, 1886 | "1442": 1944, 1887 | "1443": 1944, 1888 | "1445": 1952, 1889 | "1449": 1952, 1890 | "1451": 1963, 1891 | "1452": 1963, 1892 | "1453": 1965, 1893 | "1454": 1965, 1894 | "1455": 1967, 1895 | "1456": 1967, 1896 | "1457": 1972, 1897 | "1458": 1972, 1898 | "1459": 1973, 1899 | "1460": 1974, 1900 | "1462": 1978, 1901 | "1465": 1978, 1902 | "1466": 1980, 1903 | "1467": 1981, 1904 | "1468": 1982, 1905 | "1469": 1983, 1906 | "1470": 1983, 1907 | "1475": 1984, 1908 | "1476": 1984, 1909 | "1479": 1992, 1910 | "1485": 1992, 1911 | "1487": 2000, 1912 | "1488": 2000, 1913 | "1489": 2002, 1914 | "1490": 2002, 1915 | "1491": 2004, 1916 | "1492": 2006, 1917 | "1494": 2006, 1918 | "1495": 2008, 1919 | "1496": 2008, 1920 | "1498": 2016, 1921 | "1504": 2016, 1922 | "1506": 2024, 1923 | "1507": 2024, 1924 | "1508": 2026, 1925 | "1509": 2026, 1926 | "1510": 2028, 1927 | "1511": 2028, 1928 | "1512": 2030, 1929 | "1513": 2031, 1930 | "1515": 2033, 1931 | "1516": 2034, 1932 | "1521": 2049, 1933 | "1529": 2049, 1934 | "1531": 2057, 1935 | "1532": 2057, 1936 | "1533": 2059, 1937 | "1534": 2059, 1938 | "1535": 2063, 1939 | "1536": 2067, 1940 | "1537": 2067, 1941 | "1538": 2069, 1942 | "1540": 2071, 1943 | "1542": 2074, 1944 | "1543": 2075, 1945 | "1546": 2087, 1946 | "1556": 2087, 1947 | "1559": 2094, 1948 | "1560": 2095, 1949 | "1561": 2096, 1950 | "1563": 2101, 1951 | "1564": 2103, 1952 | "1565": 2104, 1953 | "1566": 2106, 1954 | "1567": 2109, 1955 | "1568": 2111, 1956 | "1569": 2114, 1957 | "1570": 2114, 1958 | "1571": 2116, 1959 | "1576": 2116, 1960 | "1577": 2118, 1961 | "1578": 2119, 1962 | "1579": 2120, 1963 | "1580": 2121, 1964 | "1581": 2122, 1965 | "1582": 2123, 1966 | "1583": 2124, 1967 | "1586": 2135, 1968 | "1587": 2136, 1969 | "1588": 2137, 1970 | "1589": 2137, 1971 | "1590": 2138, 1972 | "1591": 2139, 1973 | "1592": 2140, 1974 | "1593": 2141, 1975 | "1594": 2142, 1976 | "1595": 2143, 1977 | "1596": 2144, 1978 | "1598": 2149, 1979 | "1600": 2151, 1980 | "1601": 2152, 1981 | "1603": 2155, 1982 | "1605": 2158, 1983 | "1606": 2159, 1984 | "1607": 2160, 1985 | "1608": 2161, 1986 | "1609": 2164, 1987 | "1610": 2165, 1988 | "1611": 2166, 1989 | "1613": 2169, 1990 | "1615": 2171, 1991 | "1617": 2173, 1992 | "1618": 2176, 1993 | "1620": 2179, 1994 | "1621": 2180, 1995 | "1622": 2181, 1996 | "1623": 2182, 1997 | "1624": 2184, 1998 | "1625": 2187, 1999 | "1630": 2194, 2000 | "1631": 2194, 2001 | "1632": 2196, 2002 | "1634": 2199, 2003 | "1636": 2202, 2004 | "1641": 2203, 2005 | "1643": 2207, 2006 | "1644": 2208, 2007 | "1645": 2209, 2008 | "1647": 2210, 2009 | "1648": 2211, 2010 | "1650": 2220, 2011 | "1654": 2220, 2012 | "1656": 2230, 2013 | "1657": 2230, 2014 | "1658": 2232, 2015 | "1659": 2232, 2016 | "1660": 2234, 2017 | "1661": 2234, 2018 | "1665": 2249, 2019 | "1666": 2249, 2020 | "1668": 2257, 2021 | "1671": 2263, 2022 | "1672": 2263, 2023 | "1673": 2265, 2024 | "1674": 2266, 2025 | "1675": 2267, 2026 | "1678": 2273, 2027 | "1679": 2275, 2028 | "1682": 2279, 2029 | "1683": 2280, 2030 | "1684": 2283, 2031 | "1687": 2295, 2032 | "1695": 2295, 2033 | "1697": 2302, 2034 | "1698": 2303, 2035 | "1699": 2304, 2036 | "1701": 2308, 2037 | "1702": 2308, 2038 | "1703": 2310, 2039 | "1704": 2310, 2040 | "1705": 2315, 2041 | "1706": 2320, 2042 | "1708": 2320, 2043 | "1709": 2321, 2044 | "1712": 2324, 2045 | "1713": 2325, 2046 | "1715": 2330, 2047 | "1718": 2330, 2048 | "1721": 2335, 2049 | "1722": 2335, 2050 | "1723": 2340, 2051 | "1724": 2340, 2052 | "1726": 2348, 2053 | "1727": 2348, 2054 | "1728": 2350, 2055 | "1732": 2357, 2056 | "1733": 2357, 2057 | "1735": 2365, 2058 | "1738": 2365, 2059 | "1739": 2367, 2060 | "1740": 2368, 2061 | "1741": 2369, 2062 | "1742": 2369, 2063 | "1744": 2374, 2064 | "1745": 2374, 2065 | "1746": 2375, 2066 | "1747": 2376, 2067 | "1748": 2377, 2068 | "1758": 2382, 2069 | "1761": 2385, 2070 | "1767": 2388, 2071 | "1769": 2388, 2072 | "1770": 2390, 2073 | "1771": 2391, 2074 | "1772": 2392, 2075 | "1773": 2393, 2076 | "1775": 2401, 2077 | "1776": 2401, 2078 | "1778": 2409, 2079 | "1787": 2409, 2080 | "1788": 2410, 2081 | "1792": 2410, 2082 | "1794": 2413, 2083 | "1795": 2414, 2084 | "1797": 2416, 2085 | "1798": 2417, 2086 | "1799": 2418, 2087 | "1800": 2419, 2088 | "1801": 2421, 2089 | "1802": 2422, 2090 | "1804": 2429, 2091 | "1805": 2429, 2092 | "1806": 2430, 2093 | "1807": 2432, 2094 | "1808": 2433, 2095 | "1810": 2437, 2096 | "1812": 2437, 2097 | "1814": 2440, 2098 | "1815": 2441, 2099 | "1817": 2445, 2100 | "1821": 2445, 2101 | "1826": 2447, 2102 | "1828": 2449, 2103 | "1830": 2449, 2104 | "1833": 2452, 2105 | "1835": 2452, 2106 | "1836": 2453, 2107 | "1838": 2455, 2108 | "1840": 2455, 2109 | "1841": 2458, 2110 | "1842": 2463, 2111 | "1845": 2463, 2112 | "1846": 2465, 2113 | "1847": 2467, 2114 | "1848": 2469, 2115 | "1849": 2471, 2116 | "1850": 2473, 2117 | "1851": 2475, 2118 | "1853": 2475, 2119 | "1854": 2477, 2120 | "1855": 2477, 2121 | "1856": 2479, 2122 | "1857": 2481, 2123 | "1858": 2483, 2124 | "1873": 2485, 2125 | "1875": 2485, 2126 | "1876": 2487, 2127 | "1881": 2487, 2128 | "1882": 2489, 2129 | "1885": 2494, 2130 | "1886": 2494, 2131 | "1887": 2495, 2132 | "1888": 2496, 2133 | "1890": 2501 2134 | }, 2135 | "rosetta_crawl.coffee": { 2136 | "0": 3, 2137 | "1": 3, 2138 | "2": 5, 2139 | "3": 7, 2140 | "5": 7, 2141 | "6": 9, 2142 | "7": 11, 2143 | "8": 13, 2144 | "12": 14, 2145 | "23": 17, 2146 | "24": 18, 2147 | "25": 19, 2148 | "27": 23, 2149 | "28": 23, 2150 | "51": 25, 2151 | "54": 25, 2152 | "72": 27, 2153 | "73": 29, 2154 | "74": 31, 2155 | "75": 31, 2156 | "76": 33, 2157 | "77": 33, 2158 | "78": 35, 2159 | "79": 36, 2160 | "80": 37, 2161 | "81": 38, 2162 | "89": 39, 2163 | "90": 39, 2164 | "91": 41, 2165 | "94": 43, 2166 | "95": 45, 2167 | "99": 49, 2168 | "111": 61, 2169 | "112": 61, 2170 | "113": 63, 2171 | "114": 64, 2172 | "115": 66, 2173 | "116": 67, 2174 | "117": 69, 2175 | "118": 70, 2176 | "119": 73, 2177 | "121": 91, 2178 | "123": 93, 2179 | "130": 105, 2180 | "131": 105, 2181 | "133": 108, 2182 | "134": 109, 2183 | "136": 114, 2184 | "137": 114, 2185 | "141": 117, 2186 | "150": 130, 2187 | "151": 130, 2188 | "152": 132, 2189 | "153": 133, 2190 | "154": 134, 2191 | "156": 136, 2192 | "157": 139, 2193 | "158": 139, 2194 | "160": 142, 2195 | "162": 145, 2196 | "165": 152, 2197 | "166": 152, 2198 | "167": 154, 2199 | "168": 155, 2200 | "169": 156, 2201 | "170": 157, 2202 | "173": 161, 2203 | "174": 163, 2204 | "178": 170, 2205 | "179": 170, 2206 | "183": 174, 2207 | "185": 175, 2208 | "189": 185, 2209 | "190": 185, 2210 | "191": 186, 2211 | "192": 189, 2212 | "193": 189, 2213 | "195": 191, 2214 | "198": 196, 2215 | "199": 197, 2216 | "200": 198, 2217 | "201": 199, 2218 | "202": 200, 2219 | "207": 210 2220 | }, 2221 | "spine.coffee": { 2222 | "0": 8, 2223 | "1": 9, 2224 | "2": 11, 2225 | "3": 12, 2226 | "9": 20, 2227 | "10": 20, 2228 | "14": 26, 2229 | "15": 26, 2230 | "17": 30, 2231 | "18": 30, 2232 | "25": 38, 2233 | "26": 38, 2234 | "28": 41, 2235 | "30": 44, 2236 | "31": 44, 2237 | "41": 55, 2238 | "44": 62, 2239 | "45": 62, 2240 | "46": 63, 2241 | "47": 64, 2242 | "48": 64, 2243 | "49": 65, 2244 | "50": 65, 2245 | "55": 77, 2246 | "56": 77, 2247 | "57": 79, 2248 | "58": 79, 2249 | "59": 81, 2250 | "60": 83, 2251 | "65": 92, 2252 | "66": 92, 2253 | "67": 94, 2254 | "72": 103, 2255 | "73": 103, 2256 | "81": 125, 2257 | "82": 125, 2258 | "84": 131, 2259 | "85": 131, 2260 | "86": 133, 2261 | "87": 135, 2262 | "88": 137, 2263 | "89": 137, 2264 | "90": 140, 2265 | "91": 141, 2266 | "92": 142, 2267 | "93": 143, 2268 | "98": 150, 2269 | "99": 150, 2270 | "100": 154, 2271 | "101": 154, 2272 | "104": 157, 2273 | "105": 157, 2274 | "106": 158, 2275 | "108": 162, 2276 | "109": 162, 2277 | "110": 164, 2278 | "111": 165, 2279 | "113": 169, 2280 | "114": 169, 2281 | "115": 170, 2282 | "117": 172, 2283 | "119": 177, 2284 | "120": 177, 2285 | "121": 180, 2286 | "122": 181, 2287 | "123": 182, 2288 | "131": 189, 2289 | "132": 190, 2290 | "133": 190, 2291 | "134": 192, 2292 | "135": 192, 2293 | "137": 196, 2294 | "138": 196, 2295 | "139": 198, 2296 | "141": 211, 2297 | "142": 211, 2298 | "147": 221, 2299 | "148": 221, 2300 | "151": 227, 2301 | "152": 227, 2302 | "155": 238, 2303 | "156": 238, 2304 | "158": 242, 2305 | "159": 242, 2306 | "160": 244, 2307 | "162": 248, 2308 | "163": 248, 2309 | "164": 250, 2310 | "165": 251, 2311 | "167": 255, 2312 | "168": 255, 2313 | "170": 259, 2314 | "171": 259, 2315 | "174": 270, 2316 | "175": 270, 2317 | "178": 281, 2318 | "179": 281, 2319 | "181": 285, 2320 | "182": 285, 2321 | "183": 287, 2322 | "185": 291, 2323 | "186": 291, 2324 | "188": 295, 2325 | "189": 295, 2326 | "190": 296, 2327 | "191": 297, 2328 | "194": 303, 2329 | "195": 303, 2330 | "196": 304, 2331 | "197": 305, 2332 | "200": 311, 2333 | "201": 311, 2334 | "203": 315, 2335 | "204": 315, 2336 | "206": 318, 2337 | "208": 319, 2338 | "212": 331, 2339 | "213": 331, 2340 | "215": 336, 2341 | "218": 336, 2342 | "219": 338, 2343 | "223": 347, 2344 | "224": 347, 2345 | "226": 357, 2346 | "227": 357, 2347 | "228": 359, 2348 | "229": 359, 2349 | "238": 369, 2350 | "239": 369, 2351 | "241": 373, 2352 | "242": 373, 2353 | "244": 377, 2354 | "245": 377, 2355 | "246": 379, 2356 | "247": 379, 2357 | "249": 383, 2358 | "254": 392, 2359 | "255": 392, 2360 | "256": 394, 2361 | "258": 399, 2362 | "259": 400, 2363 | "262": 406, 2364 | "264": 410, 2365 | "265": 410, 2366 | "268": 414, 2367 | "269": 414, 2368 | "271": 418, 2369 | "272": 419, 2370 | "273": 420, 2371 | "275": 424, 2372 | "276": 424, 2373 | "277": 425, 2374 | "278": 426, 2375 | "280": 430, 2376 | "281": 430, 2377 | "284": 435, 2378 | "285": 435, 2379 | "288": 440, 2380 | "289": 440, 2381 | "290": 442, 2382 | "291": 443, 2383 | "293": 445, 2384 | "295": 449, 2385 | "296": 449, 2386 | "297": 451, 2387 | "300": 454, 2388 | "301": 455, 2389 | "302": 456, 2390 | "305": 461, 2391 | "306": 461, 2392 | "307": 463, 2393 | "309": 465, 2394 | "313": 472, 2395 | "314": 472, 2396 | "316": 476, 2397 | "317": 476, 2398 | "319": 479, 2399 | "322": 484, 2400 | "323": 484, 2401 | "325": 488, 2402 | "326": 488, 2403 | "328": 492, 2404 | "329": 492, 2405 | "330": 494, 2406 | "332": 498, 2407 | "334": 503, 2408 | "335": 503, 2409 | "337": 507, 2410 | "340": 507, 2411 | "341": 509, 2412 | "342": 510, 2413 | "344": 512, 2414 | "345": 513, 2415 | "346": 514, 2416 | "348": 518, 2417 | "349": 518, 2418 | "350": 520, 2419 | "351": 521, 2420 | "352": 522, 2421 | "353": 522, 2422 | "356": 525, 2423 | "357": 525, 2424 | "358": 526, 2425 | "359": 527, 2426 | "361": 531, 2427 | "362": 531, 2428 | "366": 537, 2429 | "371": 546, 2430 | "372": 546, 2431 | "373": 549, 2432 | "376": 555, 2433 | "377": 555, 2434 | "380": 562, 2435 | "381": 562, 2436 | "382": 563, 2437 | "383": 570, 2438 | "384": 570, 2439 | "387": 578, 2440 | "388": 578, 2441 | "389": 580, 2442 | "392": 585, 2443 | "396": 591, 2444 | "397": 591, 2445 | "403": 597, 2446 | "404": 597, 2447 | "405": 598, 2448 | "411": 604, 2449 | "412": 604, 2450 | "413": 605, 2451 | "414": 606, 2452 | "419": 616, 2453 | "420": 616, 2454 | "422": 622, 2455 | "424": 623, 2456 | "425": 623, 2457 | "426": 624, 2458 | "427": 625, 2459 | "433": 635, 2460 | "434": 635, 2461 | "437": 646, 2462 | "438": 646, 2463 | "440": 650, 2464 | "441": 650, 2465 | "445": 656, 2466 | "446": 656, 2467 | "447": 658, 2468 | "451": 673, 2469 | "452": 673, 2470 | "456": 679, 2471 | "457": 679, 2472 | "458": 681, 2473 | "462": 696, 2474 | "463": 696, 2475 | "473": 713, 2476 | "474": 713, 2477 | "475": 714, 2478 | "476": 716, 2479 | "477": 717, 2480 | "479": 722, 2481 | "480": 722, 2482 | "481": 723, 2483 | "482": 726, 2484 | "483": 726, 2485 | "487": 735, 2486 | "488": 735, 2487 | "490": 739, 2488 | "493": 739, 2489 | "495": 743, 2490 | "496": 743, 2491 | "497": 745, 2492 | "498": 747, 2493 | "499": 749, 2494 | "500": 751, 2495 | "501": 753, 2496 | "502": 755, 2497 | "503": 757, 2498 | "504": 759, 2499 | "509": 763, 2500 | "512": 763, 2501 | "515": 765, 2502 | "520": 782, 2503 | "521": 782, 2504 | "522": 785, 2505 | "525": 800, 2506 | "526": 800, 2507 | "528": 804, 2508 | "529": 804, 2509 | "530": 808 2510 | }, 2511 | "underscore.coffee": { 2512 | "0": 4, 2513 | "16": 4, 2514 | "17": 6, 2515 | "20": 6, 2516 | "21": 8, 2517 | "25": 8, 2518 | "26": 10, 2519 | "29": 10, 2520 | "30": 14, 2521 | "33": 14, 2522 | "34": 16, 2523 | "35": 18, 2524 | "38": 18, 2525 | "39": 20, 2526 | "40": 22, 2527 | "41": 24, 2528 | "42": 26, 2529 | "43": 28, 2530 | "46": 28, 2531 | "47": 30, 2532 | "48": 32, 2533 | "49": 34, 2534 | "50": 36, 2535 | "51": 38, 2536 | "52": 40, 2537 | "53": 42, 2538 | "54": 44, 2539 | "55": 46, 2540 | "56": 48, 2541 | "61": 54, 2542 | "64": 54, 2543 | "65": 56, 2544 | "68": 56, 2545 | "69": 58, 2546 | "72": 58, 2547 | "73": 60, 2548 | "80": 60, 2549 | "81": 62, 2550 | "88": 76, 2551 | "91": 82, 2552 | "95": 82, 2553 | "97": 85, 2554 | "98": 86, 2555 | "101": 92, 2556 | "105": 92, 2557 | "107": 94, 2558 | "109": 97, 2559 | "110": 98, 2560 | "112": 103, 2561 | "116": 103, 2562 | "118": 106, 2563 | "120": 109, 2564 | "122": 113, 2565 | "125": 113, 2566 | "126": 115, 2567 | "127": 116, 2568 | "129": 118, 2569 | "132": 125, 2570 | "136": 125, 2571 | "138": 130, 2572 | "139": 131, 2573 | "142": 137, 2574 | "145": 137, 2575 | "146": 139, 2576 | "147": 140, 2577 | "150": 146, 2578 | "154": 146, 2579 | "157": 152, 2580 | "158": 153, 2581 | "161": 161, 2582 | "165": 161, 2583 | "168": 165, 2584 | "169": 166, 2585 | "172": 174, 2586 | "176": 174, 2587 | "180": 187, 2588 | "183": 187, 2589 | "184": 189, 2590 | "186": 198, 2591 | "189": 198, 2592 | "191": 204, 2593 | "194": 204, 2594 | "196": 207, 2595 | "197": 210, 2596 | "198": 212, 2597 | "201": 221, 2598 | "204": 221, 2599 | "206": 224, 2600 | "207": 227, 2601 | "208": 229, 2602 | "211": 238, 2603 | "214": 238, 2604 | "215": 239, 2605 | "220": 255, 2606 | "221": 258, 2607 | "225": 258, 2608 | "227": 261, 2609 | "228": 262, 2610 | "230": 264, 2611 | "233": 274, 2612 | "236": 274, 2613 | "242": 282, 2614 | "245": 282, 2615 | "246": 286, 2616 | "254": 286, 2617 | "256": 294, 2618 | "262": 294, 2619 | "264": 298, 2620 | "267": 298, 2621 | "268": 302, 2622 | "271": 302, 2623 | "272": 312, 2624 | "275": 312, 2625 | "276": 313, 2626 | "281": 320, 2627 | "284": 320, 2628 | "285": 322, 2629 | "287": 332, 2630 | "291": 332, 2631 | "292": 334, 2632 | "296": 345, 2633 | "300": 345, 2634 | "301": 347, 2635 | "305": 355, 2636 | "309": 355, 2637 | "310": 357, 2638 | "311": 358, 2639 | "313": 360, 2640 | "315": 365, 2641 | "320": 365, 2642 | "326": 382, 2643 | "330": 382, 2644 | "336": 398, 2645 | "340": 398, 2646 | "342": 401, 2647 | "344": 403, 2648 | "345": 404, 2649 | "346": 405, 2650 | "348": 407, 2651 | "349": 408, 2652 | "352": 411, 2653 | "355": 417, 2654 | "362": 417, 2655 | "363": 419, 2656 | "365": 425, 2657 | "369": 425, 2658 | "370": 427, 2659 | "373": 434, 2660 | "377": 434, 2661 | "378": 436, 2662 | "380": 442, 2663 | "383": 442, 2664 | "384": 444, 2665 | "387": 448, 2666 | "389": 450, 2667 | "390": 454, 2668 | "394": 454, 2669 | "396": 458, 2670 | "401": 458, 2671 | "403": 464, 2672 | "407": 464, 2673 | "408": 466, 2674 | "410": 469, 2675 | "414": 477, 2676 | "420": 477, 2677 | "423": 488, 2678 | "426": 488, 2679 | "428": 492, 2680 | "431": 492, 2681 | "433": 498, 2682 | "436": 498, 2683 | "438": 505, 2684 | "440": 511, 2685 | "443": 511, 2686 | "446": 516, 2687 | "450": 516, 2688 | "453": 521, 2689 | "456": 521, 2690 | "459": 524, 2691 | "460": 524, 2692 | "478": 535, 2693 | "479": 535, 2694 | "482": 537, 2695 | "483": 537, 2696 | "489": 547, 2697 | "492": 547, 2698 | "496": 557, 2699 | "499": 557, 2700 | "500": 561, 2701 | "503": 561, 2702 | "504": 565, 2703 | "507": 565, 2704 | "508": 569, 2705 | "511": 569, 2706 | "512": 573, 2707 | "515": 573, 2708 | "516": 577, 2709 | "519": 577, 2710 | "520": 581, 2711 | "523": 581, 2712 | "524": 585, 2713 | "527": 585, 2714 | "528": 589, 2715 | "531": 589, 2716 | "532": 593, 2717 | "536": 593, 2718 | "537": 597, 2719 | "540": 597, 2720 | "541": 601, 2721 | "544": 601, 2722 | "545": 605, 2723 | "552": 605, 2724 | "553": 606, 2725 | "555": 610, 2726 | "558": 610, 2727 | "559": 614, 2728 | "562": 614, 2729 | "564": 623, 2730 | "567": 623, 2731 | "568": 627, 2732 | "572": 627, 2733 | "575": 638, 2734 | "579": 638, 2735 | "580": 640, 2736 | "582": 644, 2737 | "586": 644, 2738 | "587": 645, 2739 | "588": 646, 2740 | "589": 647, 2741 | "591": 650, 2742 | "597": 650, 2743 | "599": 653, 2744 | "600": 654, 2745 | "614": 662, 2746 | "619": 662, 2747 | "620": 664, 2748 | "621": 666, 2749 | "622": 668, 2750 | "623": 670, 2751 | "624": 672, 2752 | "625": 674, 2753 | "626": 676, 2754 | "627": 678, 2755 | "628": 680, 2756 | "629": 682, 2757 | "637": 682, 2758 | "638": 683, 2759 | "640": 687, 2760 | "643": 687, 2761 | "645": 695, 2762 | "648": 695, 2763 | "649": 696, 2764 | "650": 698, 2765 | "657": 706, 2766 | "660": 706, 2767 | "661": 708, 2768 | "662": 709, 2769 | "665": 715, 2770 | "668": 715, 2771 | "669": 717, 2772 | "670": 718, 2773 | "676": 724, 2774 | "682": 734 2775 | } 2776 | } --------------------------------------------------------------------------------