paige.coffee

Require our external dependencies, including Showdown.js (the JavaScript implementation of Markdown).

_ =               require "underscore"
fs =              require 'fs'
path =            require 'path'
showdown =        require('./../vendor/showdown').Showdown
{spawn, exec} =   require 'child_process'
events      =     require('events')
promise =         new events.EventEmitter

Ensure that the destination directory exists.

ensure_directory = (dir, callback) ->
  exec "mkdir -p #{dir}", -> callback()

Micro-templating, originally by John Resig, borrowed by way of Underscore.js.

template = (str) ->
  new Function 'obj',
    'var p=[],print=function(){p.push.apply(p,arguments);};' +
    'with(obj){p.push(\'' +
    str.replace(/[\r\t\n]/g, " ")
       .replace(/'(?=[^<]*%>)/g,"\t")
       .split("'").join("\\'")
       .split("\t").join("'")
       .replace(/<%=(.+?)%>/g, "',$1,'")
       .split('<%').join("');")
       .split('%>').join("p.push('") +
       "');}return p.join('');"

Kind of hacky, but I can't figure out another way of doing this cleanly. Will list all the files that will be used as your source file for passing onto Docco.

get_subfiles = (callback) ->
  results = []
  count = 0
  find_files = (file, total) ->
    f_path = file.substr(0,file.lastIndexOf('/')+1)
    f_file = file.substr(file.lastIndexOf('/')+1)
    exec "find ./#{f_path} -name '#{f_file}' -print", (error, stdout, stderr) ->
      count++
      results = _.uniq(_.union(results, stdout.trim().split("\n")))
      if count >= total
        callback results.sort() if callback

  if _.isArray(configuration.docco_files)
    _.each configuration.docco_files, (file) ->
      find_files(file,configuration.docco_files.length)
  else if _.isString(configuration.docco_files)
    find_files(configuration.docco_files,1)

Pass the list of files as process arguments, which is the only way I can interface with Docco at this point.

process_docco_files = ->
  get_subfiles (result) ->
    process.ARGV = process.argv = result
    require 'docco'

Creates html wrapper for all the Docco pages. The point here is that I can now keep a navigation bar at the top without having to mess with any of the Docco internals at all.

process_docco_wrappers = ->
  get_subfiles (result) ->
    result = clean_path_names result
    result = clean_file_extension result
    _.each result, (file) ->
      html = wrapper_template {
        title:            configuration.title,
        header:           configuration.header,
        subheader:        configuration.subheader,
        file:             file
      }
      fs.writeFile "docs/doc_#{file}.html", html

Process the configuration file

process_config = (config={}) ->
  _.map config, (value, key, list) ->
    configuration[key] = value if config[key]?

Remove trailing path names from each file from a list

clean_path_names = (names) ->
  clean_names = []
  _.each names, (name) ->
    clean_names.push name.substr(name.lastIndexOf('/')+1) || name
  return clean_names

Remove file extensions from each file from a list

clean_file_extension = (names) ->
  clean_names = []
  _.each names, (name) ->
    clean_names.push name.substr(0,name.lastIndexOf('.')).substr(name.lastIndexOf('/')+1) || name
  return clean_names

Build the main html file by reading the source Markdown file, and if necessary collecting all the filenames of our source. We will then use these names to construct the index that's shown at the top of the page.

We pass the source Markdown file to Showdown, get the result, then pipe it into our templating function described above.

process_html_file = ->
  source = configuration.content_file
  get_subfiles (result) ->
    subfiles_names = clean_file_extension(result) if configuration.include_index
    subfiles = clean_path_names(result) if configuration.include_index
    fs.readFile source, "utf-8", (error, code) ->
      if error
        console.log "\nThere was a problem reading your the content file: #{source}"
        throw error
      else
        content_html = showdown.makeHtml code
        html = mdown_template {
          content_html:     content_html,
          title:            configuration.title,
          header:           configuration.header,
          subheader:        configuration.subheader,
          include_index:    configuration.include_index,
          subfiles:         subfiles,
          subfiles_names:   subfiles_names
        }
        console.log "paige: #{source} -> docs/index.html"
        fs.writeFile "docs/index.html", html

Reads the background image.

paige_background    = ->
  fs.readFileSync(__dirname + "/../resources/#{configuration.background}.png")

Process the Docco files and wrappers if needed.

check_for_docco = ->
  if configuration.docco_files?
    process_docco_files()
    process_docco_wrappers()

Read our configuration file.

read_config = (callback) ->
  filename = "paige.config"
  filename = process.ARGV[2] if process.ARGV[2]?
  fs.readFile filename, "utf-8", (error, data) ->
    if error
      console.log "\nCould not find a configuration file. (default: ./paige.config)"
      console.log "Create and specify a configuration file. Example:\n\n"
      console.log config_template + "\n"
    else
      config = JSON.parse(data)
      callback(config) if callback

Some necessary files

mdown_template =    template fs.readFileSync(__dirname + '/../resources/paige.jst').toString()
wrapper_template =  template fs.readFileSync(__dirname + '/../resources/doc.jst').toString()
config_template =   fs.readFileSync(__dirname + '/../resources/paige.config').toString()

And our current, and base configuration

configuration = {}
base_config = {
  "title" :             "Untitled",
  "content_file" :      "README.mdown",
  "include_index" :     false,
  "docco_files" :       null,
  "header" :            "Untitled",
  "subheader" :         "Untitled",
  "background" :        "bright_squares"
}

Run the script

ensure_directory 'docs', ->
  read_config (config) ->
    process_config(config)
    fs.writeFile 'docs/bg.png', paige_background()
    process_html_file()
    check_for_docco()