• annotatedDocco.js

  • ¶
    (function() {
      var Docco, _, buildMatchers, commander, configure, defaults, document, format, fs, getLanguage, highlightjs, languages, marked, parse, path, run, version, write,
        slice = [].slice;
    
      document = function(options, callback) {
  • ¶

    this is called from “run” command, when we have an argument

        var config;
        if (options == null) {
  • ¶

    default options to an empty object

          options = {};
        }
        config = configure(options);
  • ¶

    config contains template (what the html will look like) css, output, layout, sources, etc.

  • ¶

    make a folder… fs-extra is pretty awesome https://www.npmjs.com/package/fs-extra fs.mkdirs(‘anthony_rocks’, () => { // create new files in here fs.writeJson(‘anthony_rocks/rock.json’, { name: ‘Anthony’ }, err => { if (err) return console.error(err);

  • ¶
    console.log('success!');
    

    }); });

  • ¶

    this function creates the output folder it goes through all of the source files you have it parses them and creates the necessary html files it copies over the CSS and public directories

        return fs.mkdirs(config.output, function() {
          var complete, copyAsset, files, nextFile;
          callback || (callback = function(error) {
  • ¶

    console.log(‘callback was run!’);

            if (error) {
              throw error;
            }
          });
  • ¶

    default callback to an empty function that handles errors It looks like we are not using callbacks anywhere else. copy assets to OUTPUT directory

          copyAsset = function(file, callback) {
  • ¶

    basename returns the last piece of the path. In our case, this will be the file’s name

            if (!fs.existsSync(file)) {
              return callback();
            }
            return fs.copy(file, path.join(config.output, path.basename(file)), callback);
          };
  • ¶

    This copies over the CSS style sheet and public folder

          complete = function() {
  • ¶

    config.css points to the CSS we will be using to create documentation config.public points to all the resources that we will be using TODO: What is public for?

            return copyAsset(config.css, function(error) {
              if (error) {
                return callback(error);
              }
              if (fs.existsSync(config["public"])) {
                return copyAsset(config["public"], callback);
              }
              return callback();
            });
          };
  • ¶

    make a copy of the sources array

          files = config.sources.slice();
    
          nextFile = function() {
            var source;
            source = files.shift(); // get the next file on the list
            return fs.readFile(source, function(error, buffer) {
              var code, sections;
              if (error) {
                return callback(error);
              }
              code = buffer.toString();
  • ¶

    buffers handle binary data buffer corresponds to raw memory buffer.toString() gives you a readable version of the buffer

              sections = parse(source, code, config);
  • ¶

    parse breaks down the source code into the actual code and comments docsText, codeText format() adds the HTML for docs and code

              format(source, sections, config);
    
              write(source, sections, config);
              if (files.length) {
  • ¶

    recursion to go through all files

                return nextFile();
              } else {
  • ¶

    when finished going through all files, run “complete” command. This will copy over the CSS and public directory

                return complete();
              }
            });
          };
          return nextFile();
        });
      };
  • ¶

    parse through the code and create sections the section will have docsText and codeText

      parse = function(source, code, config) {
        var codeText, docsText, hasCode, i, isText, j, k, lang, len, len1, line, lines, match, maybeCode, save, sections;
        if (config == null) {
          config = {};
        }
        lines = code.split('\n');
        sections = [];
        lang = getLanguage(source, config);
  • ¶

    lang contains RegExp to get JavaScript comments

        hasCode = docsText = codeText = '';
        save = function() {
  • ¶

    save docsText, codeText into sections array

          sections.push({
            docsText: docsText,
            codeText: codeText
          });
          return hasCode = docsText = codeText = '';
        };
        if (lang.literate) {
  • ¶

    if you are a literate program language

          isText = maybeCode = true;
          for (i = j = 0, len = lines.length; j < len; i = ++j) {
            line = lines[i];
            lines[i] = maybeCode && (match = /^([ ]{4}|[ ]{0,3}\t)/.exec(line)) ? (isText = false, line.slice(match[0].length)) : (maybeCode = /^\s*$/.test(line)) ? isText ? lang.symbol : '' : (isText = true, lang.symbol + ' ' + line);
          }
        }
        for (k = 0, len1 = lines.length; k < len1; k++) {
          line = lines[k];
  • ¶

    is a comment?

          if (line.match(lang.commentMatcher) && !line.match(lang.commentFilter)) {
            if (hasCode) {
              save();
            }
            docsText += (line = line.replace(lang.commentMatcher, '')) + '\n';
  • ¶

    if comment is of “// —“ then include a line break

            if (/^(---+|===+)$/.test(line)) {
              save();
            }
          } else {
  • ¶

    we have actual code

            hasCode = true;
            codeText += line + '\n';
          }
        }
        save();
        return sections;
      };
  • ¶

    adds docsHTML and codeHTML to sections

      format = function(source, sections, config) {
        var code, i, j, language, len, markedOptions, results, section;
  • ¶

    we are calculating language in two different places

        language = getLanguage(source, config);
  • ¶

    marked is markdown parser/compiler https://www.npmjs.com/package/marked

        markedOptions = {
          smartypants: true
        };
        if (config.marked) {
          markedOptions = config.marked;
        }
        marked.setOptions(markedOptions);
        marked.setOptions({
  • ¶

    highlight markdown text

          highlight: function(code, lang) {
            lang || (lang = language.name);
            if (highlightjs.getLanguage(lang)) {
              return highlightjs.highlight(lang, code).value;
            } else {
              console.warn("docco: couldn't highlight code block with unknown language '" + lang + "' in " + source);
              return code;
            }
          }
        });
        results = [];
        for (i = j = 0, len = sections.length; j < len; i = ++j) {
          section = sections[i];
          code = highlightjs.highlight(language.name, section.codeText).value;
  • ¶

    remove whitespace characters

          code = code.replace(/\s+$/, '');
          section.codeHtml = "<div class='highlight'><pre>" + code + "</pre></div>";
  • ¶

    add docs html

          results.push(section.docsHtml = marked(section.docsText));
        }
  • ¶

    results contains all of the docsHTML

        return results;
      };
    
      write = function(source, sections, config) {
  • ¶

    source is the filename sections contains the HTML we need

        var destination, first, firstSection, hasTitle, html, title;
        destination = function(file) {
          var _d = path.join(config.output, path.basename(file, path.extname(file)) + '.html');
          return _d;
        };
        firstSection = _.find(sections, function(section) {
          return section.docsText.length > 0;
        });
        if (firstSection) {
          first = marked.lexer(firstSection.docsText)[0];
        }
        hasTitle = first && first.type === 'heading' && first.depth === 1;
        title = hasTitle ? first.text : path.basename(source);
        html = config.template({
          sources: config.sources,
          css: path.basename(config.css),
          title: title,
          hasTitle: hasTitle,
          sections: sections,
          path: path,
          destination: destination
        });
        return fs.writeFileSync(destination(source), html);
      };
    
      defaults = {
        layout: 'parallel',
        output: 'docs',
        template: null,
        css: null,
        extension: null,
        languages: {},
        marked: null
      };
    
      configure = function(options) {
        var config, dir;
        config = _.extend({}, defaults, _.pick.apply(_, [options].concat(slice.call(_.keys(defaults)))));
        config.languages = buildMatchers(config.languages);
  • ¶

    options are from the commander object

        if (options.template) {
          if (!options.css) {
            console.warn("docco: no stylesheet file specified");
          }
          config.layout = null;
        } else {
  • ¶

    get directory of the layout we are using. parallel, classic, etc.

          dir = config.layout = path.join(__dirname, 'resources', config.layout);
          if (fs.existsSync(path.join(dir, 'public'))) {
  • ¶

    our “complete” function will copy everyting from the public directory to output directory.

            config["public"] = path.join(dir, 'public');
          }
  • ¶

    get the template and css files

          config.template = path.join(dir, 'docco.jst');
          config.css = options.css || path.join(dir, 'docco.css');
        }
  • ¶

    convert the template to a usable template function

        config.template = _.template(fs.readFileSync(config.template).toString());
        if (options.marked) {
          config.marked = JSON.parse(fs.readFileSync(options.marked));
        }
  • ¶

    only grab sources that are valid files

        config.sources = options.args.filter(function(source) {
          var lang;
          lang = getLanguage(source, config);
          if (!lang) {
            console.warn("docco: skipped unknown type (" + (path.basename(source)) + ")");
          }
          return lang;
        }).sort();
  • ¶

    args contains all arguments that were passed to the function console.log(‘args’, options.args); console.log(‘sources’, config.sources);

        return config;
      };
    
      _ = require('underscore');
    
      fs = require('fs-extra');
    
      path = require('path');
    
      marked = require('marked');
    
      commander = require('commander');
    
      highlightjs = require('highlight.js');
    
      languages = JSON.parse(fs.readFileSync(path.join(__dirname, 'resources', 'languages.json')));
  • ¶

    create comments and comment filters

      buildMatchers = function(languages) {
        var ext, l;
        for (ext in languages) {
          l = languages[ext];
          l.commentMatcher = RegExp("^\\s*" + l.symbol + "\\s?");
          l.commentFilter = /(^#![\/]|^\s*#\{)/;
        }
        return languages;
      };
    
      languages = buildMatchers(languages);
    
      getLanguage = function(source, config) {
        var codeExt, codeLang, ext, lang, ref;
        ext = config.extension || path.extname(source) || path.basename(source);
        lang = ((ref = config.languages) != null ? ref[ext] : void 0) || languages[ext];
        if (lang && lang.name === 'markdown') {
          codeExt = path.extname(path.basename(source, ext));
          if (codeExt && (codeLang = languages[codeExt])) {
            lang = _.extend({}, codeLang, {
              literate: true
            });
          }
        }
        return lang;
      };
    
      version = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'))).version;
    
      run = function(args) {
  • ¶

    args variable is undefined process.argv contains an array of arguments passed in when running this command. See https://nodejs.org/docs/latest/api/process.html#process_process_argv

        var c;
        if (args == null) {
          args = process.argv;
        }
        c = defaults;
  • ¶

    c is a list of default parameters. It is defined earlier in the file

  • ¶

    commander prints out the console.output when you run docco without arguments

        commander
          .version(version)
          .usage('[options] files')
          .option('-L, --languages [file]', 'use a custom languages.json', _.compose(JSON.parse, fs.readFileSync))
          .option('-l, --layout [name]', 'choose a layout (parallel, linear or classic)', c.layout)
          .option('-o, --output [path]', 'output to a given folder', c.output)
          .option('-c, --css [file]', 'use a custom css file', c.css)
          .option('-t, --template [file]', 'use a custom .jst template', c.template)
          .option('-e, --extension [ext]', 'assume a file extension for all inputs', c.extension)
          .option('-m, --marked [file]', 'use custom marked options', c.marked)
          .parse(args).name = "docco";
  • ¶

    if we have arguments, then run the “document” command.

        if (commander.args.length) {
          return document(commander);
        } else {
  • ¶

    if we don’t have arguments, print out help information

          return console.log(commander.helpInformation());
        }
      };
    
      Docco = module.exports = {
        run: run,
        document: document,
        parse: parse,
        format: format,
        version: version
      };
    
    }).call(this);