├── .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 |
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('"
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 |
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 "#{name}>"
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 = ("#{th} " 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 | "#{cell} "
45 | html += (td cell for cell in row).join ''
46 | html += ' '
47 | html += '
'
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 | CS
15 | CoffeeScript
16 | JS
17 | JavaScript
18 |
19 | """
20 |
21 | row = (cells) ->
22 | html += ''
23 | html += ("#{cell} " 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 += '
'
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 | }
--------------------------------------------------------------------------------