Skip to content

Commit

Permalink
Separated ndoc parser into separate plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
ixti committed Jun 25, 2012
1 parent 6cb01e8 commit 1cc9c1a
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 268 deletions.
2 changes: 1 addition & 1 deletion .jshintignore
Expand Up @@ -4,6 +4,6 @@ node_modules/
tmp/
tests/
skins/
lib/ndoc/plugins/parsers/ndoc/
lib/ndoc/plugins/renderers/html/
lib/ndoc/parsers/javascript.js
.cache/
6 changes: 3 additions & 3 deletions Makefile
Expand Up @@ -57,14 +57,14 @@ publish:
npm publish https://github.com/${GITHUB_PROJ}/tarball/${NPM_VERSION}


lib: lib/ndoc/parsers/javascript.js
lib/ndoc/parsers/javascript.js:
lib: lib/ndoc/plugins/parsers/ndoc/parser.js
lib/ndoc/plugins/parsers/ndoc/parser.js:
@if test ! `which jison` ; then \
echo "You need 'jison' installed in order to compile parsers." >&2 ; \
echo " $ make dev-deps" >&2 ; \
exit 128 ; \
fi
jison src/js-parser.y && mv js-parser.js lib/ndoc/parsers/javascript.js
jison src/js-parser.y && mv js-parser.js lib/ndoc/plugins/parsers/ndoc/parser.js


compile-parsers:
Expand Down
21 changes: 13 additions & 8 deletions bin/ndoc.js
Expand Up @@ -164,14 +164,19 @@ walk_many(opts.paths, extensionPattern, function (filename, stat, cb) {
}

// build tree
ndoc = new NDoc(files, {
// given package URL, file name and line in the file, format link to source file.
// do so only if `packageUrl` is set or `linkFormat` is set
formatLink: (opts.linkFormat || opts.package.url) && function (file, line) {
// '\' -> '/' for windows
return interpolate(opts.linkFormat, file.replace(/\\/g, '/'), line);
}
});
try {
ndoc = new NDoc(files, _.extend({
// given package URL, file name and line in the file, format link to source file.
// do so only if `packageUrl` is set or `linkFormat` is set
formatLink: (opts.linkFormat || opts.package.url) && function (file, line) {
// '\' -> '/' for windows
return interpolate(opts.linkFormat, file.replace(/\\/g, '/'), line);
}
}, opts));
} catch (err) {
console.error('FATAL:', err.stack || err.message || err);
process.exit(1);
}

// output tree
ndoc.render(opts.render, opts, function (err) {
Expand Down
275 changes: 19 additions & 256 deletions lib/ndoc.js
Expand Up @@ -19,8 +19,8 @@ var _ = require('underscore');

// internal
var extend = require('./ndoc/common').extend;
var parser = require('./ndoc/parsers/javascript');
var renderers = require('./ndoc/renderers');
var parsers = require('./ndoc/parsers');


////////////////////////////////////////////////////////////////////////////////
Expand All @@ -40,262 +40,12 @@ var renderers = require('./ndoc/renderers');
* Called with `(file, line)` arguments.
**/
var NDoc = module.exports = function NDoc(files, options) {
var list, tree, parted, sections;
// TODO: cleanup NDoc constructor
this.options = extend({}, options);
this.list = {}; // flat representation of ast
this.tree = {}; // ast
this.files = files;

// options
this.options = extend({}, options);

// documentation tree consists of sections, which are populated with documents
list = {
'': {
id: '',
type: 'section',
children: [],
description: '',
short_description: ''
}
};

// parse specified source files
files.forEach(function (file) {
console.log('Compiling file', file);

try {
var text, nodes;

text = Fs.readFileSync(file, 'utf8');

// TODO: consider amending failing document inplace.
// Say, if it doesn't parse, insert a fake '*' line at failing `line` and retry

nodes = parser.parse(text);

// do pre-distribute early work
_.each(nodes, function (node, id) {
var clone;

// assign hierarchy helpers
node.aliases = [];
node.children = [];

if ('class' === node.type) {
node.subclasses = [];
}

// compose links to source files
if (options.formatLink) {
node.href = options.formatLink(file, node.line);
}

// collect sections
if ('section' === node.type) {
list[node.id] = node;
return;
}

// elements with undefined section get '' section,
// and will be resolved later, when we'll have full
// element list
list[(node.section || '') + '.' + node.id] = node;

// bound methods produce two methods with the same description but different signatures
// E.g. Element.foo(@element, a, b) becomes
// Element.foo(element, a, b) and Element#foo(a, b)
if ('method' === node.type && node.bound) {
clone = extend(node);
clone.id = node.id.replace(/(.+)\.(.+)/, '$1#$2');

// link to methods
node.bound = clone.id;
clone.bound = node.id;

// insert bound method clone
list[(node.section || '') + '.' + clone.id] = clone;
}
});
} catch (err) {
console.error('FATAL:', file, err.stack || err.message || err);
process.exit(1);
}
});

// TODO: section.related_to should mark related element as belonging to the section
//_.each(list, function (node, id) {
// var ref_id = '.' + node.related_to, ref;
// if ('section' === node.type && node.related_to && list[ref_id]) {
// ref = list[ref_id];
// ref.id = node.id + '.' + node.related_to;
// delete list[ref_id];
// list[ref.id] = ref;
// }
//});


//
// for each element with undefined section try to guess the section
// E.g. for ".Ajax.Updater" we try to find "SECTION.Ajax" element.
// If found, rename ".Ajax.Updater" to "SECTION.Ajax.Updater"
//


// prepare list of sections
// N.B. starting with 1 we skip "" section
parted = _.keys(list).sort().slice(1).map(function (id) {
return {id: id, parted: id.split(/[.#@]/), node: list[id]};
});

_.each(parted, function (data) {
var found;

// leave only ids without defined section
if ('' !== data.parted[0]) {
return;
}

found = _.find(parted, function (other) {
return !!other.parted[0] && other.parted[1] === data.parted[1];
});

if (found) {
delete list[data.id];

data.node.id = found.parted[0] + data.id;
data.parted[0] = found.parted[0];

list[data.node.id] = data.node;
}
});

// sort elements in case-insensitive manner
tree = {};

sections = _.keys(list).sort(function (a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
return a === b ? 0 : a < b ? -1 : 1;
});

sections.forEach(function (id) {
tree[id] = list[id];
});

// rebuild the tree from the end to beginning.
// N.B. since the list we iterate over is sorted, we can determine precisely
// the parent of any element.
_.each(sections.slice(0).reverse(), function (id) {
var // parent name is this element's name without portion after
// the last '.' for class member, '#' for instance member,
// or '@' for events
idx = Math.max(id.lastIndexOf('.'), id.lastIndexOf('#'), id.lastIndexOf('@')),
// get parent element
parent = tree[id.substring(0, idx)];

// no '.' or '#' found? this is top level section. just skip it
// no parent? skip it as well
if (-1 === idx || !parent) {
return;
}

// parent element found. move this element to parent's children list,
// maintaing order
parent.children.unshift(tree[id]);
delete tree[id];
});

// cleanup list, reassign right ids after we resolved
// to which sections every element belongs
_.each(list, function (node, id) {
delete list[id];

// compose new id
node.id = id.replace(/^[^.]*\./, '');
node.name = node.id.replace(/^.*[.#@]/, '');

// sections have lowercased ids, to not clash with other elements
if ('section' === node.type) {
node.id = node.id.toLowerCase();
}

// prototype members have different paths
// events have different paths as well
node.path = node.id.replace(/#/g, '.prototype.').replace(/@/g, '.event.');
delete node.section;

// prune sections from list
if ('section' !== node.type) {
list[node.id] = node;
}
});

// assign aliases, subclasses, constructors
// correct method types (class or entity)
_.each(list, function (node, id) {
// aliases
if (node.alias_of && list[node.alias_of]) {
list[node.alias_of].aliases.push(node.id);
}

// classes hierarchy
if ('class' === node.type) {
//if (d.superclass) console.log('SUPER', id, d.superclass)
if (node.superclass && list[node.superclass]) {
list[node.superclass].subclasses.push(node.id);
}

return;
}

if ('constructor' === node.type) {
node.id = 'new ' + node.id.replace(/\.new$/, '');
return;
}

// methods and properties
if ('method' === node.type || 'prototype' === node.type) {
// FIXME: shouldn't it be assigned by parser?

if (node.id.match(/^\$/)) {
node.type = 'utility';
return;
}

if (node.id.indexOf('#') >= 0) {
node.type = 'instance ' + node.type;
return;
}

if (node.id.indexOf('.') >= 0) {
node.type = 'class ' + node.type;
return;
}

if (node.id.indexOf('@') >= 0) {
node.type = 'event';
return;
}
}
});

// tree is hash of sections.
// convert sections to uniform children array of tree top level
var children = [];

_.each(tree, function (node, id) {
if (id === '') {
children = children.concat(node.children);
} else {
children.push(node);
}

delete tree[id];
});

tree.children = children;

// store tree and flat list
this.list = list;
this.tree = tree;
parsers[options.parser](this, options);
};


Expand Down Expand Up @@ -359,10 +109,23 @@ NDoc.registerRenderer = function (name, func) {
};


/**
* NDoc.registerParser(name, func) -> Void
* - name (String): Name of the parser, e.g. `'ndoc'`
* - func (Function): Parser function `func(ndocInstance, options, callback)`
*
* Registers given function as `name` renderer.
**/
NDoc.registerParser = function (name, func) {
parsers[name] = func;
};


//
// require base plugins
//


NDoc.use(require(__dirname + '/ndoc/plugins/parsers/ndoc'));
NDoc.use(require(__dirname + '/ndoc/plugins/renderers/html'));
NDoc.use(require(__dirname + '/ndoc/plugins/renderers/json'));
10 changes: 10 additions & 0 deletions lib/ndoc/cli.js
Expand Up @@ -3,6 +3,7 @@

// internal
var renderers = require('./renderers');
var parsers = require('./parsers');


// 3rd-party
Expand Down Expand Up @@ -101,6 +102,15 @@ cli.addArgument(['-r', '--render'], {
});


cli.addArgument(['--parser'], {
help: 'Documentation parser',
choices: function () { return _.keys(parsers).join(','); },
metavar: 'PARSER',
action: 'store+lazyChoices',
defaultValue: 'ndoc'
});


cli.addArgument(['-l', '--link-format'], {
dest: 'linkFormat',
help: 'Format for link to source file',
Expand Down
5 changes: 5 additions & 0 deletions lib/ndoc/parsers.js
@@ -0,0 +1,5 @@
'use strict';

// Simple "shared" parsers object.
// Used to avoid circular dependencies (between cli and ndoc).
module.exports = {};
7 changes: 7 additions & 0 deletions lib/ndoc/plugins.js
Expand Up @@ -10,3 +10,10 @@
*
* Built-in renderer plugins.
**/


/** section: Plugins
* Parsers
*
* Built-in parser plugins.
**/

0 comments on commit 1cc9c1a

Please sign in to comment.