Jump To …

docco.js

lib/
(function() {
  var cloc, destination, docco_styles, docco_template, dox, dox_template, ensure_directory, exec, ext, file_exists, fs, generate_documentation, generate_html, generate_readme, get_language, highlight, highlight_end, highlight_start, jade, l, languages, parse, parse_args, path, relative_base, showdown, spawn, _ref;
  generate_documentation = function(source, context, callback) {
    return fs.readFile(source, "utf-8", function(error, code) {
      var sections;
      if (error) {
        throw error;
      }
      sections = parse(source, code);
      return highlight(source, sections, function() {
        generate_html(source, context, sections);
        return callback();
      });
    });
  };
  parse = function(source, code) {
    var code_text, docs_text, has_code, in_multi, language, line, lines, multi_accum, parsed, save, sections, _i, _len;
    lines = code.split('\n');
    sections = [];
    language = get_language(source);
    has_code = docs_text = code_text = '';
    in_multi = false;
    multi_accum = "";
    save = function(docs, code) {
      return sections.push({
        docs_text: docs,
        code_text: code
      });
    };
    for (_i = 0, _len = lines.length; _i < _len; _i++) {
      line = lines[_i];
      if (line.match(language.multi_start_matcher) || in_multi) {
        if (has_code) {
          save(docs_text, code_text);
          has_code = docs_text = code_text = '';
        }
        in_multi = true;
        multi_accum += line + '\n';
        if (line.match(language.multi_end_matcher)) {
          in_multi = false;
          parsed = dox.parseComments(multi_accum)[0];
          console.log(require('util').inspect(parsed, false, 10));
          docs_text += dox_template(parsed);
          multi_accum = '';
        }
      } else if (line.match(language.comment_matcher) && !line.match(language.comment_filter)) {
        if (has_code) {
          save(docs_text, code_text);
          has_code = docs_text = code_text = '';
        }
        docs_text += line.replace(language.comment_matcher, '') + '\n';
      } else {
        has_code = true;
        code_text += line + '\n';
      }
    }
    save(docs_text, code_text);
    return sections;
  };
  highlight = function(source, sections, callback) {
    var language, output, pygments, section;
    language = get_language(source);
    pygments = spawn('pygmentize', ['-l', language.name, '-f', 'html', '-O', 'encoding=utf-8,tabsize=2']);
    output = '';
    pygments.stderr.addListener('data', function(error) {
      if (error) {
        return console.error(error.toString());
      }
    });
    pygments.stdin.addListener('error', function(error) {
      console.error("Could not use Pygments to highlight the source.");
      return process.exit(1);
    });
    pygments.stdout.addListener('data', function(result) {
      if (result) {
        return output += result;
      }
    });
    pygments.addListener('exit', function() {
      var fragments, i, section, _len;
      output = output.replace(highlight_start, '').replace(highlight_end, '');
      fragments = output.split(language.divider_html);
      for (i = 0, _len = sections.length; i < _len; i++) {
        section = sections[i];
        section.code_html = highlight_start + fragments[i] + highlight_end;
        section.docs_html = showdown.makeHtml(section.docs_text);
      }
      return callback();
    });
    if (pygments.stdin.writable) {
      pygments.stdin.write(((function() {
        var _i, _len, _results;
        _results = [];
        for (_i = 0, _len = sections.length; _i < _len; _i++) {
          section = sections[_i];
          _results.push(section.code_text);
        }
        return _results;
      })()).join(language.divider_text));
      return pygments.stdin.end();
    }
  };
  generate_html = function(source, context, sections) {
    var dest, html, target_dir, title, write_func;
    title = path.basename(source);
    dest = destination(source, context);
    html = docco_template({
      title: title,
      file_path: source,
      sections: sections,
      context: context,
      path: path,
      relative_base: relative_base
    });
    target_dir = path.dirname(dest);
    write_func = function() {
      console.log("docco: " + source + " -> " + dest);
      return fs.writeFile(dest, html, function(err) {
        if (err) {
          throw err;
        }
      });
    };
    return fs.stat(target_dir, function(err, stats) {
      if (err && err.code !== 'ENOENT') {
        throw err;
      }
      if (!err) {
        return write_func();
      }
      if (err) {
        return exec("mkdir -p " + target_dir, function(err) {
          if (err) {
            throw err;
          }
          return write_func();
        });
      }
    });
  };
  generate_readme = function(context, sources) {
    var content, dest, package_json, package_path, readme_markdown, readme_path, readme_template, source, title;
    title = "README";
    dest = "docs/readme.html";
    source = "README.md";
    readme_template = jade.compile(fs.readFileSync(__dirname + '/../resources/readme.jade').toString(), {
      filename: __dirname + '/../resources/readme.jade'
    });
    readme_path = process.cwd() + '/README.md';
    readme_markdown = file_exists(readme_path) ? fs.readFileSync(readme_path).toString() : "There is no README.md for this project yet :( ";
    package_path = process.cwd() + '/package.json';
    package_json = file_exists(package_path) ? JSON.parse(fs.readFileSync(package_path).toString()) : {};
    content = showdown.makeHtml(readme_markdown);
    return cloc(sources.join(" "), function(code_stats) {
      var html, target_dir, write_func;
      html = readme_template({
        title: title,
        context: context,
        content: content,
        file_path: source,
        path: path,
        relative_base: relative_base,
        package_json: package_json,
        code_stats: code_stats
      });
      target_dir = path.dirname(dest);
      write_func = function() {
        console.log("docco: " + source + " -> " + dest);
        return fs.writeFile(dest, html, function(err) {
          if (err) {
            throw err;
          }
        });
      };
      return fs.stat(target_dir, function(err, stats) {
        if (err && err.code !== 'ENOENT') {
          throw err;
        }
        if (!err) {
          return write_func();
        }
        if (err) {
          return exec("mkdir -p " + target_dir, function(err) {
            if (err) {
              throw err;
            }
            return write_func();
          });
        }
      });
    });
  };
  cloc = function(paths, callback) {
    return exec("" + __dirname + "/../vendor/cloc.pl --quiet --read-lang-def=" + __dirname + "/../resources/cloc_definitions.txt " + paths, function(err, stdout) {
      if (err) {
        console.log("Calculating project stats failed " + err);
      }
      return callback(stdout);
    });
  };
  fs = require('fs');
  path = require('path');
  showdown = require('./../vendor/showdown').Showdown;
  jade = require('jade');
  dox = require('dox');
  _ref = require('child_process'), spawn = _ref.spawn, exec = _ref.exec;
  languages = {
    '.coffee': {
      name: 'coffee-script',
      symbol: '#'
    },
    '.js': {
      name: 'javascript',
      symbol: '//',
      multi_start: "/*",
      multi_end: "*/"
    },
    '.rb': {
      name: 'ruby',
      symbol: '#'
    },
    '.py': {
      name: 'python',
      symbol: '#'
    }
  };
  for (ext in languages) {
    l = languages[ext];
    l.comment_matcher = new RegExp('^\\s*' + l.symbol + '\\s?');
    l.comment_filter = new RegExp('(^#![/]|^\\s*#\\{)');
    l.divider_text = '\n' + l.symbol + 'DIVIDER\n';
    l.divider_html = new RegExp('\\n*<span class="c1?">' + l.symbol + 'DIVIDER<\\/span>\\n*');
    if (l.multi_start === "/*") {
      l.multi_start_matcher = new RegExp(/^\s*\/\*[.]*/);
    }
    if (l.multi_end === "*/") {
      l.multi_end_matcher = new RegExp(/.*\*\/.*/);
    }
  }
  get_language = function(source) {
    return languages[path.extname(source)];
  };
  relative_base = function(filepath, context) {
    var result;
    result = path.dirname(filepath) + '/';
    if (result === '/') {
      return '';
    } else {
      return result;
    }
  };
  destination = function(filepath, context) {
    var base_path;
    base_path = relative_base(filepath, context);
    return 'docs/' + base_path + path.basename(filepath, path.extname(filepath)) + '.html';
  };
  ensure_directory = function(dir, callback) {
    return exec("mkdir -p " + dir, function() {
      return callback();
    });
  };
  file_exists = function(path) {
    try {
      return fs.lstatSync(path).isFile;
    } catch (ex) {
      return false;
    }
  };
  docco_template = jade.compile(fs.readFileSync(__dirname + '/../resources/docco.jade').toString(), {
    filename: __dirname + '/../resources/docco.jade'
  });
  dox_template = jade.compile(fs.readFileSync(__dirname + '/../resources/dox.jade').toString(), {
    filename: __dirname + '/../resources/dox.jade'
  });
  docco_styles = fs.readFileSync(__dirname + '/../resources/docco.css').toString();
  highlight_start = '<div class="highlight"><pre>';
  highlight_end = '</pre></div>';
  parse_args = function(callback) {
    var a, args, ext, lang_filter, project_name, roots;
    args = process.ARGV;
    project_name = "";
    if (args[0] === "-name") {
      args.shift();
      project_name = args.shift();
    }
    args = args.sort();
    if (!args.length) {
      return;
    }
    roots = (function() {
      var _i, _len, _results;
      _results = [];
      for (_i = 0, _len = args.length; _i < _len; _i++) {
        a = args[_i];
        _results.push(a.replace(/\/+$/, ''));
      }
      return _results;
    })();
    roots = roots.join(" ");
    lang_filter = (function() {
      var _results;
      _results = [];
      for (ext in languages) {
        _results.push(" -name '*" + ext + "' ");
      }
      return _results;
    })();
    lang_filter = lang_filter.join(' -o ');
    return exec("find " + roots + " -type f \\( " + lang_filter + " \\)", function(err, stdout) {
      var sources;
      if (err) {
        throw err;
      }
      sources = stdout.split("\n").filter(function(file) {
        return file !== '' && path.basename(file)[0] !== '.';
      });
      console.log("docco: Recursively generating docs underneath " + roots + "/");
      return callback(sources, project_name, args);
    });
  };
  parse_args(function(sources, project_name, raw_paths) {
    var context;
    context = {
      sources: sources,
      project_name: project_name
    };
    return ensure_directory('docs', function() {
      var files, next_file;
      fs.writeFile('docs/docco.css', docco_styles);
      files = sources.slice(0, (sources.length + 1) || 9e9);
      next_file = function() {
        if (files.length) {
          return generate_documentation(files.shift(), context, next_file);
        }
      };
      next_file();
      return generate_readme(context, raw_paths);
    });
  });
}).call(this);