Skip to content
This repository
branch: master
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 181 lines (157 sloc) 5.481 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
http = require 'http'
fs = require 'fs'
url = require 'url'

{side_by_side} = require './side_by_side'
{source_line_mappings} = require './cs_js_source_mapping'
{list_files} = require './list_files'
file_utils = require './file_utils'

DIR = null # will be cmd-line arg
GIT_REPO = "https://github.com/showell/CoffeeScriptLineMatcher"
JQUERY_CDN = """
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
"""
COFFEE_FILE_REGEX = /\.(coffee|cof)$/

relative_path = (fn) -> file_utils.relative_path DIR, fn
get_files = (regex) -> file_utils.get_files DIR, regex

worst_match = (matches) ->
  # debugging code
  last = 0
  max = 0
  worst = null
  for match in matches
    [cs, js] = match
    if cs - last > max
      max = cs - last
      worst = cs - max + 1
    last = cs
  "The longest CS section starts at cs:#{worst} (#{max} lines)."
 
timestamps = (cs_fn, cb) ->
  # Return timestamps of our files. Mostly used by AJAX calls to avoid
  # unnecessary page refreshes.
  cs_files = get_files COFFEE_FILE_REGEX
  ts = (fn) -> fs.statSync(fn).mtime.toISOString()
  js_files = get_files /\.js$/
  js_fn = file_utils.js_file_for cs_fn, js_files
  cb get_fingerprint cs_fn, js_fn
  
get_fingerprint = (cs_fn, js_fn) ->
  ts = (fn) -> fs.statSync(fn).mtime.toISOString()
  data =
    cs: ts cs_fn
  if js_fn
    data.js = ts js_fn
  data
   
view_file = (cs_fn, cb) ->
  html = """
<head>
<title>#{relative_path cs_fn}</title>
<link rel="stylesheet" href="./dashboard.css" />
#{JQUERY_CDN}
<script type="text/javascript" src="view_file.js"></script>
</head>
<h4>#{relative_path cs_fn}</h4>
<a href="./">View files</a> (#{DIR})
<br>
<a href="./about">About</a>
<hr>
"""
  
  cs_files = get_files COFFEE_FILE_REGEX
  js_files = get_files /\.js$/
  throw "illegal file #{cs_fn}" unless cs_fn in cs_files
  js_fn = file_utils.js_file_for cs_fn, js_files

  add_metadata = ->
    finger_print = get_fingerprint cs_fn, js_fn
    finger_print = JSON.stringify finger_print, null, " "
    html += """
<script>
CS_FN = #{JSON.stringify cs_fn};
FINGERPRINT = #{finger_print};
</script>
"""

  if js_fn is null
    html += "<b>No current JS file was found for #{cs_fn}</b>"
    add_metadata()
    return cb html
   
  html += "<b>JS file</b>: #{relative_path js_fn}<br>"
  coffee_lines = file_utils.file_lines(cs_fn)
  js_lines = file_utils.file_lines(js_fn)
  matches = source_line_mappings coffee_lines, js_lines
  html += worst_match(matches)
  html += side_by_side matches, coffee_lines, js_lines
  add_metadata()

  cb html
  
about = (cb) ->
  cb """
<head>
<title>About CoffeeScriptLineMatcher</title>
<link rel="stylesheet" href="./dashboard.css" />
</head>
<h2>About</h2>
<a href="./">View files</a>
<p>
GIT Repository: <a href="#{GIT_REPO}">CoffeeScriptLineMatcher</a>.
</p>
<p>
This tool lets you view CS and JS code side by side.
</p>
<p>
The algorithm for matching up CS lines to JS lines is
independent of the compiler itself. I've tested the
algorithm on several CS examples, but unorthodox coding
styles will likely confuse the algorithm. (Long term,
CS itself will have line number support, so this tool
can eventually be patched to use native mappings.)
</p>
"""
    
    
run_dashboard = (port) ->
  server = http.createServer (req, res) ->
    serve_page = (html) ->
      res.writeHeader 200, 'Content-Type': 'text/html'
      res.write html
      res.end()
      
    serve_css = (fn) ->
      res.writeHeader 200, 'Content-Type': 'text/css'
      res.write fs.readFileSync fn
      res.end()
      
    serve_js = (fn) ->
      res.writeHeader 200, 'Content-Type': 'text/javascript'
      res.write fs.readFileSync fn
      res.end()
      
    serve_json = (data) ->
      res.writeHeader 200, 'Content-Type': 'text/json'
      res.write JSON.stringify data, null, ' '
      res.end()
      
    parts = url.parse(req.url, true)
    
    # console.log "Serving #{parts.pathname} #{JSON.stringify parts.query}"
    try
      if parts.pathname == '/view'
        view_file parts.query.FILE, serve_page
      else if parts.pathname == '/timestamps'
        timestamps parts.query.FILE, serve_json
      else if parts.pathname == '/about'
        about serve_page
      else if parts.pathname == '/dashboard.css'
        serve_css './assets/dashboard.css'
      else if parts.pathname == '/view_file.js'
        serve_js './assets/view_file.js'
      else if parts.pathname == '/'
        list_files DIR, get_files, COFFEE_FILE_REGEX, serve_page
      else if parts.pathname == '/favicon.ico'
        # Patches welcome here, but favicon.ico is kind of pointless
        # in a localhost dev tool. Sending the 404 does nothing of
        # substance, so this is really just a placeholder.
        res.writeHeader 404
        res.end();
      else
        res.end()
    catch e
      # Right now our code is mostly synchronous, but this won't
      # catch async exceptions, so it's just a band-aid for now.
      serve_page "Exception: #{e}"

  server.listen port
  console.log "Server running at http://localhost:#{port}/"
  
do ->
  [ignore, ignore, DIR, port] = process.argv
  unless port? and port.match /^\d+/
    console.warn "You must supply a directory and port number."
    return
  run_dashboard(port)
Something went wrong with that request. Please try again.