/*
Copyright (c) 2011, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://yuilibrary.com/license/
*/
var MD = require("node-markdown").Markdown,
fs = require('graceful-fs'),
noop = function() {},
path = require('path'),
DEFAULT_RETURN_TAGS = 'code|em|strong|span|a|pre|dl|dd|dt|ul|li|ol',
TEMPLATE,
/**
* Takes a type string and converts it to a "First letter upper cased" type. e.g. `(string -> String, object -> Object)`
* @method fixType
* @private
* @param {String} t The type string to convert
* @return {String} The fixed string
*/
fixType = function(t) {
var firstChar = t.charAt(0),
upperFirstChar = firstChar.toUpperCase();
if (firstChar !== upperFirstChar) {
return upperFirstChar + t.substring(1);
}
return t;
},
/**
* Wrapper around the Markdown parser so it can be normalized or even side stepped
* @method markdown
* @private
* @param {String} md The Markdown string to parse
* @param {Boolean} def Only allow default set of HTML tags
* @param {Array} tags An array of tags to allow
* @return {HTML} The rendered HTML
*/
markdown = function(md, def, tags) {
html = MD(md, def, tags);
return html;
};
/**
* Takes the `JSON` data from the `DocParser` class, creates and parses markdown and handlebars
based templates to generate static HTML content
* @class DocBuilder
* @module yuidoc
*/
YUI.add('doc-builder', function(Y) {
var print = function(items) {
var out = '<ul>';
Y.each(items, function(i, k) {
out += '<li>';
if (Y.Lang.isObject(i)) {
if (!i.path) {
out += k + '/' + print(i);
} else {
out += '<a href="../files/' + i.name + '.html">' + k + '</a>';
}
}
out += '</li>';
});
out += '</ul>';
return out;
};
Y.Handlebars.registerHelper('buildFileTree', function(items, fn) {
return print(items);
});
var DEFAULT_THEME = themeDir = path.join(__dirname, '../', 'themes', 'default');
Y.DocBuilder = function(options, data) {
this.options = options;
if (options.themedir) {
themeDir = options.themedir;
}
this.data = data;
Y.log('Building..', 'info', 'builder');
this.files = 0;
var self = this;
Y.Handlebars.registerHelper('crossLink', function(item, fn) {
var str = '';
if (!item) {
item = '';
}
if (item.indexOf('|') > 0) {
var parts = item.split('|'),
p = [];
Y.each(parts, function(i) {
p.push(self._parseCrossLink.call(self, i));
});
str = p.join(' | ');
} else {
str = self._parseCrossLink.call(self, item);
}
return str;
});
Y.Handlebars.registerHelper('crossLinkModule', function(item, fn) {
var str = item;
if (self.data.modules[item]) {
str = '<a href="../modules/' + item + '.html">' + item + '</a>';
}
return str;
});
Y.Handlebars.registerHelper('crossLinkRaw', function(item, fn) {
var str = '';
if (!item) {
item = '';
}
if (item.indexOf('|') > 0) {
var parts = item.split('|'),
p = [];
Y.each(parts, function(i) {
p.push(self._parseCrossLink.call(self, i, true));
});
str = p.join(' | ');
} else {
str = self._parseCrossLink.call(self, item, true);
}
return str;
});
this.cacheTemplates = true;
if (options.cacheTemplates === false) {
this.cacheTemplates = false;
}
};
Y.DocBuilder.prototype = {
_parseCrossLink: function(item, raw) {
item = fixType(item);
var self = this;
var base = '../',
baseName = item,
newWin = false,
className = 'crosslink';
item = baseItem = Y.Lang.trim(item.replace('{', '').replace('}', ''));
//Remove Cruft
item = item.replace('*', '').replace('[', '').replace(']', '');
var link = false,
href;
if (self.data.classes[item]) {
link = true;
} else {
if (self.data.classes[item.replace('.', '')]) {
link = true;
item = item.replace('.', '');
}
}
if (self.options.externalData) {
if (self.data.classes[item]) {
if (self.data.classes[item].external) {
href = self.data.classes[item].path;
base = self.options.externalData.base;
className += ' external';
newWin = true;
link = true;
}
}
}
/*
if (!link && self.options.externalData) {
var d = self.options.externalData;
if (d && d.classes[item]) {
href = d.classes[item].path;
base = d.base;
className += ' external';
newWin = true;
link = true;
}
}
*/
if (item === 'Object' || item === 'Array') {
link = false;
}
if (!href) {
href = path.join(base, 'classes', item + '.html');
if (base.match(/^https?:\/\//)) {
href = base + path.join('classes', item + '.html');
}
}
if (!link && self.options.linkNatives) {
if (self.NATIVES && self.NATIVES[item]) {
href = self.NATIVES_LINKER(item);
if (href) {
className += ' external';
newWin = true;
link = true;
}
}
}
if (link) {
item = '<a href="' + href + '" class="' + className + '"' + ((newWin) ? ' target="_blank"' : '') + '>' + baseItem + '</a>';
}
return (raw) ? href : item;
},
NATIVES: {
'Array': 1,
'Boolean': 1,
'Date': 1,
'decodeURI': 1,
'decodeURIComponent': 1,
'encodeURI': 1,
'encodeURIComponent': 1,
'eval': 1,
'Error': 1,
'EvalError': 1,
'Function': 1,
'Infinity': 1,
'isFinite': 1,
'isNaN': 1,
'Math': 1,
'NaN': 1,
'Number': 1,
'Object': 1,
'parseFloat': 1,
'parseInt': 1,
'RangeError': 1,
'ReferenceError': 1,
'RegExp': 1,
'String': 1,
'SyntaxError': 1,
'TypeError': 1,
'undefined': 1,
'URIError': 1,
'HTMLElement': 'https:/'+'/developer.mozilla.org/en/Document_Object_Model_(DOM)/',
'HTMLCollection': 'https:/'+'/developer.mozilla.org/en/Document_Object_Model_(DOM)/',
'DocumentFragment': 'https:/'+'/developer.mozilla.org/en/Document_Object_Model_(DOM)/',
'HTMLDocument': 'https:/'+'/developer.mozilla.org/en/Document_Object_Model_(DOM)/'
},
NATIVES_LINKER: function(name) {
var url = 'https:/'+'/developer.mozilla.org/en/JavaScript/Reference/Global_Objects/';
if (this.NATIVES[name] !== 1) {
url = this.NATIVES[name];
}
return url + name;
},
_mixExternal: function() {
var self = this;
Y.log('External data received, mixing', 'info', 'builder');
var exData = self.options.externalData;
['files', 'classes', 'modules'].forEach(function(k) {
Y.each(exData[k], function(item, key) {
item.external = true;
var file = item.name;
if (!item.file) {
file = self.filterFileName(item.name);
}
item.path = exData.base + path.join(k, file + '.html');
self.data[k][key] = item;
});
});
Y.each(exData.classitems, function(item) {
item.external = true;
item.path = exData.base + path.join('files', self.filterFileName(item.file) + '.html');
self.data.classitems.push(item);
});
},
mixExternal: function(cb) {
var self = this,
info = self.options.external;
if (!info) {
cb();
return;
}
if (!info.merge) {
info.merge = 'mix';
}
if (!info.data) {
Y.log('External config found but no data path defined, skipping import.', 'warn', 'builder');
cb();
return;
}
if (!Y.Lang.isArray(info.data)) {
info.data = [ info.data ];
}
Y.log('Importing external documentation data.', 'info', 'builder');
var stack = new Y.Parallel();
info.data.forEach(function(i) {
var base;
if (i.match(/^https?:\/\//)) {
base = i.replace('data.json', '');
Y.use('io-base', stack.add(function() {
Y.io(i, {
on: {
complete: stack.add(function(id, e) {
var data = JSON.parse(e.responseText);
data.base = base;
self.options.externalData = Y.mix(self.options.externalData || {}, data);
})
}
});
}));
} else {
base = path.dirname(path.resolve(i));
var data = Y.Files.getJSON(i);
data.base = base;
self.options.externalData = Y.mix(self.options.externalData || {}, data);
}
});
stack.done(function() {
Y.log('Finished fetching remote data', 'info', 'builder');
self._mixExternal();
cb();
});
},
/**
* The default tags to use in params descriptions (for Markdown).
* @property defaultTags
* @type String
*/
defaultReturnTags: DEFAULT_RETURN_TAGS,
defaultTags: 'p|' + DEFAULT_RETURN_TAGS,
/**
* File counter
* @property files
* @type Number
*/
files: null,
/**
* Prep the meta data to be fed to Selleck
* @method getProjectMeta
* @return {Object} The project metadata
*/
_meta: null,
getProjectMeta: function() {
var obj = {
meta: {
yuiSeedUrl: 'http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js',
yuiGridsUrl: 'http://yui.yahooapis.com/3.5.0/build/cssgrids/cssgrids-min.css'
}
};
if (!this._meta) {
try {
var meta;
if (fs.existsSync(path.join(themeDir, 'theme.json'))) {
meta = Y.Files.getJSON(path.join(themeDir, 'theme.json'));
} else if (DEFAULT_THEME !== themeDir) {
if (fs.existsSync(path.join(DEFAULT_THEME))) {
meta = Y.Files.getJSON(path.join(DEFAULT_THEME, 'theme.json'));
}
}
if (meta) {
obj.meta = meta;
this._meta = meta;
}
} catch (e) {}
} else {
obj.meta = this._meta;
}
Y.each(this.data.project, function(v, k) {
var key = k.substring(0, 1).toUpperCase() + k.substring(1, k.length);
obj.meta['project' + key] = v;
});
return obj
},
/**
* Populate the meta data for classes
* @method populateClasses
* @param {Object} opts The original options
* @return {Object} The modified options
*/
populateClasses: function(opts) {
opts.meta.classes = [];
Y.each(this.data.classes, function(v) {
if (v.external) { return }
opts.meta.classes.push({ displayName: v.name, name: v.name});
});
opts.meta.classes.sort(this.nameSort);
return opts;
},
/**
* Populate the meta data for modules
* @method populateModules
* @param {Object} opts The original options
* @return {Object} The modified options
*/
populateModules: function(opts) {
var self = this;
opts.meta.modules = [];
opts.meta.allModules = [];
Y.each(this.data.modules, function(v) {
if (v.external) { return }
opts.meta.allModules.push({ displayName: v.displayName || v.name, name: self.filterFileName(v.name), description: v.description });
if (!v.is_submodule) {
var o = { displayName: v.displayName || v.name, name: self.filterFileName(v.name) };
if (v.submodules) {
o.submodules = [];
Y.each(v.submodules, function(i, k) {
moddef = self.data.modules[k];
if (moddef) {
o.submodules.push({
displayName: k,
description: moddef.description
});
} else {
//Y.log('Submodule data missing: ' + k + ' for ' + v.name, 'warn', 'builder');
}
});
o.submodules.sort(self.nameSort);
}
opts.meta.modules.push(o);
}
});
opts.meta.modules.sort(this.nameSort);
opts.meta.allModules.sort(this.nameSort);
return opts;
},
/**
* Populate the meta data for files
* @method populateFiles
* @param {Object} opts The original options
* @return {Object} The modified options
*/
populateFiles: function(opts) {
var self = this;
opts.meta.files = [];
Y.each(this.data.files, function(v) {
if (v.external) { return }
opts.meta.files.push({ displayName: v.name, name: self.filterFileName(v.name), path: v.path || v.name });
});
var tree = {};
var files = [];
Y.each(this.data.files, function(v) {
if (v.external) { return }
files.push(v.name);
});
files.sort();
Y.each(files, function(v) {
var p = v.split('/'),
par;
p.forEach(function(i, k) {
if (!par) {
if (!tree[i]) {
tree[i] = {};
}
par = tree[i];
} else {
if (!par[i]) {
par[i] = {};
}
if (k + 1 === p.length) {
par[i] = {
path: v,
name: self.filterFileName(v)
};
}
par = par[i];
}
});
});
opts.meta.fileTree = tree;
return opts;
},
addFoundAt: function(a) {
var self = this;
if (a.file && a.line && !self.options.nocode) {
a.foundAt = '../files/' + self.filterFileName(a.file) + '.html#l' + a.line;
if (a.path) {
a.foundAt = a.path + '#l' + a.line;
}
}
return a;
},
/**
* Augments the **DocParser** meta data to provide default values for certain keys as well as parses all descriptions
* with the `Markdown Parser`
* @method augmentData
* @param {Object} o The object to recurse and augment
* @return {Object} The augmented object
*/
augmentData: function(o) {
var self = this;
o = self.addFoundAt(o);
Y.each(o, function(i, k1) {
if (i && i.forEach) {
Y.each(i, function(a, k) {
if (!(a instanceof Object)) {
return;
}
if (!a.type) {
a.type = 'Object'; //Default type is Object
}
if (a.final === '') {
a.final = true;
}
if (!a.description) {
a.description = ' ';
} else {
a.description = markdown(a.description, true, self.defaultTags);
}
if (a.example) {
a.example = markdown(a.example, true, self.defaultTags);
}
a = self.addFoundAt(a);
Y.each(a, function(c, d) {
if (c.forEach || (c instanceof Object)) {
c = self.augmentData(c);
a[d] = c;
}
});
o[k1][k] = a;
});
} else if (i instanceof Object) {
i = self.addFoundAt(i);
Y.each(i, function(v, k) {
if (k === 'final') {
o[k1][k] = true;
}
if (k === 'description' || k === 'example') {
if (k1 === 'return') {
o[k1][k] = markdown(v, true, self.defaultReturnTags);
} else if (v.forEach || (v instanceof Object)) {
o[k1][k] = self.augmentData(v);
} else {
o[k1][k] = markdown(v, true, self.defaultTags);
}
}
});
} else if (k1 === 'description' || k1 === 'example') {
o[k1] = markdown(i, true, self.defaultTags);
}
});
return o;
},
/**
* Makes the default directories needed
* @method makeDirs
* @param {Callback} cb The callback to execute after it's completed
*/
makeDirs: function(cb) {
var self = this;
var dirs = ['classes', 'modules', 'files'];
if (self.options.dumpview) {
dirs.push('json');
}
var writeRedirect = function(dir, file, cb) {
path.exists(file, function(x) {
if (x) {
var out = path.join(dir, 'index.html');
fs.createReadStream(file).pipe(fs.createWriteStream(out));
}
cb();
});
};
var defaultIndex = path.join(themeDir, 'assets', 'index.html');
var stack = new Y.Parallel();
Y.log('Making default directories: ' + dirs.join(','), 'info', 'builder');
dirs.forEach(function(d) {
var dir = path.join(self.options.outdir, d);
path.exists(dir, stack.add(function(x) {
if (!x) {
fs.mkdir(dir, 0777, stack.add(function() {
writeRedirect(dir, defaultIndex, stack.add(noop));
}));
} else {
writeRedirect(dir, defaultIndex, stack.add(noop));
}
}));
});
stack.done(function() {
if (cb) {
cb();
}
});
},
/**
* Parses `<pre><code>` tags and adds the __prettyprint__ `className` to them
* @method _parseCode
* @private
* @param {HTML} html The HTML to parse
* @return {HTML} The parsed HTML
*/
_parseCode: function (html) {
html = html || '';
html = html.replace(/<pre><code>/g, '<pre class="code"><code class="prettyprint">');
return html;
},
/**
* Ported from [Selleck](https://github.com/rgrove/selleck), this handles ```'s in fields
that are not parsed by the **Markdown** parser.
* @method _inlineCode
* @private
* @param {HTML} html The HTML to parse
* @return {HTML} The parsed HTML
*/
_inlineCode: function(html) {
html = html.replace(/\\`/g, '`');
html = html.replace(/`(.+?)`/g, function (match, code) {
return '<code>' + Y.escapeHTML(code) + '</code>';
});
html = html.replace(/__\{\{SELLECK_BACKTICK\}\}__/g, '`');
return html;
},
/**
* Ported from [Selleck](https://github.com/rgrove/selleck)
Renders the handlebars templates with the default View class.
* @method render
* @param {HTML} source The default template to parse
* @param {Class} view The default view handler
* @param {HTML} [layout=null] The HTML from the layout to use.
* @param {Object} [partials=object] List of partials to include in this template
* @param {Callback} callback
* @param {Error} callback.err
* @param {HTML} callback.html The assembled template markup
*/
render: function(source, view, layout, partials, callback) {
var html = [];
function buffer(line) {
html.push(line);
}
// Allow callback as third or fourth param.
if (typeof partials === 'function') {
callback = partials;
partials = {};
} else if (typeof layout === 'function') {
callback = layout;
layout = null;
}
var parts = Y.merge(partials || {}, { layout_content: source });
Y.each(parts, function(source, name) {
Y.Handlebars.registerPartial(name, source);
});
if (!TEMPLATE || !this.cacheTemplates) {
TEMPLATE = Y.Handlebars.compile(layout);
}
var _v = {};
for (var k in view) {
if (Y.Lang.isFunction(view[k])) {
_v[k] = view[k]();
} else {
_v[k] = view[k];
}
};
html = TEMPLATE(_v);
html = this._inlineCode(html);
callback(null, html);
},
/**
* Render the index file
* @method renderIndex
* @param {Function} cb The callback fired when complete
* @param {String} cb.html The HTML to render this view
* @param {Object} cv.view The View Data
*/
renderIndex: function(cb) {
var self = this;
Y.prepare([DEFAULT_THEME, themeDir], self.getProjectMeta(), function(err, opts) {
opts.meta.title = self.data.project.name;
opts.meta.projectRoot = './';
opts.meta.projectAssets = './assets';
opts = self.populateClasses(opts);
opts = self.populateModules(opts);
var view = new Y.DocView(opts.meta);
self.render('{{>index}}', view, opts.layouts.main, opts.partials, function(err, html) {
self.files++;
cb(html, view);
});
});
},
/**
* Generates the index.html file
* @method writeIndex
* @param {Callback} cb The callback to execute after it's completed
*/
writeIndex: function(cb) {
var self = this,
stack = new Y.Parallel();
Y.log('Preparing index.html', 'info', 'builder');
self.renderIndex(stack.add(function(html, view) {
stack.html = html;
stack.view = view;
if (self.options.dumpview) {
Y.Files.writeFile(path.join(self.options.outdir, 'json', 'index.json'), JSON.stringify(view), stack.add(noop));
}
Y.Files.writeFile(path.join(self.options.outdir, 'index.html'), html, stack.add(noop));
}));
stack.done(function(html, view) {
Y.log('Writing index.html', 'info', 'builder');
cb(stack.html, stack.view);
});
},
renderModule: function(cb, data, layout) {
var self = this;
var stack = new Y.Parallel();
data.displayName = data.name;
data.name = self.filterFileName(data.name);
Y.prepare([DEFAULT_THEME, themeDir], self.getProjectMeta(), function(err, opts) {
opts.meta = Y.merge(opts.meta, data);
//opts.meta.htmlTitle = v.name + ': ' + self.data.project.name;
opts.meta.title = self.data.project.name;
opts.meta.moduleName = data.displayName || data.name;
opts.meta.moduleDescription = self._parseCode(markdown(data.description || ' '));
opts.meta.file = data.file;
opts.meta.line = data.line;
opts.meta = self.addFoundAt(opts.meta);
opts.meta.projectRoot = '../';
opts.meta.projectAssets = '../assets';
opts = self.populateClasses(opts);
opts = self.populateModules(opts);
opts = self.populateFiles(opts);
if (data.classes && Object.keys(data.classes).length) {
opts.meta.moduleClasses = [];
Y.each(Object.keys(data.classes), function(name) {
var i = self.data.classes[name];
if (i) {
opts.meta.moduleClasses.push({ name: i.name, displayName: i.name });
}
});
opts.meta.moduleClasses.sort(self.nameSort);
}
if (data.submodules && Object.keys(data.submodules).length) {
opts.meta.subModules = [];
Y.each(Object.keys(data.submodules), function(name) {
var i = self.data.modules[name];
if (i) {
opts.meta.subModules.push({ name: i.name, displayName: i.name, description: i.description });
}
});
opts.meta.subModules.sort(self.nameSort);
}
var view = new Y.DocView(opts.meta);
var mainLayout = opts.layouts[layout];
self.render('{{>module}}', view, mainLayout, opts.partials, stack.add(function(err, html) {
self.files++;
stack.html = html;
stack.view = view;
}));
});
stack.done(function() {
cb(stack.html, stack.view);
});
},
/**
* Generates the module files under "out"/modules/
* @method writeModules
* @param {Callback} cb The callback to execute after it's completed
*/
writeModules: function(cb, layout) {
layout = layout || 'main';
var self = this,
stack = new Y.Parallel();
stack.html = [];
stack.view = [];
Y.log('Rendering and writing ' + Object.keys(self.data.modules).length + ' modules pages.', 'info', 'builder');
Y.each(self.data.modules, function(v) {
if (v.external) { return }
self.renderModule(function(html, view) {
stack.html.push(html);
stack.view.push(view);
if (self.options.dumpview) {
Y.Files.writeFile(path.join(self.options.outdir, 'json', 'module_' + v.name + '.json'), JSON.stringify(view), stack.add(noop));
}
Y.Files.writeFile(path.join(self.options.outdir, 'modules', v.name + '.html'), html, stack.add(noop));
}, v, layout);
});
stack.done(function() {
Y.log('Finished writing module files', 'info', 'builder');
cb(stack.html, stack.view);
});
},
hasProperty: function(a, b) {
var other;
var h = Y.some(a, function(i, k) {
if ((i.itemtype === b.itemtype) && (i.name === b.name)) {
other = k;
return true;
}
});
return other;
},
mergeExtends: function(info, classItems) {
var self = this;
if (info.extends || info.uses) {
var hasItems = {};
hasItems[info.extends] = 1;
if (info.uses) {
info.uses.forEach(function(v) {
hasItems[v] = 1;
});
}
self.data.classitems.forEach(function(v) {
//console.error(v.class, '==', info.extends);
if (hasItems[v.class]) {
if (!v.static) {
var override = self.hasProperty(classItems, v);
if (!override) {
//This method was extended from the parent class but not over written
//console.error('Merging extends from', v.class, 'onto', info.name);
var q = Y.merge({}, v);
q.extended_from = v.class;
classItems.push(q);
} else {
//This method was extended from the parent and overwritten in this class
var q = Y.merge({}, v);
q = self.augmentData(q);
classItems[override].overwritten_from = q;
}
}
}
});
if (self.data.classes[info.extends]) {
if (self.data.classes[info.extends].extends || self.data.classes[info.extends].uses) {
//console.error('Stepping down to:', self.data.classes[info.extends]);
classItems = self.mergeExtends(self.data.classes[info.extends], classItems);
}
}
}
return classItems;
},
renderClass: function(cb, data, layout) {
var self = this;
var stack = new Y.Parallel();
Y.prepare([DEFAULT_THEME, themeDir], self.getProjectMeta(), function(err, opts) {
//console.log(opts);
if (err) {
console.log(err);
}
opts.meta = Y.merge(opts.meta, data);
opts.meta.title = self.data.project.name;
opts.meta.moduleName = data.name;
opts.meta.file = data.file;
opts.meta.line = data.line;
opts.meta = self.addFoundAt(opts.meta);
opts.meta.projectRoot = '../';
opts.meta.projectAssets = '../assets';
opts = self.populateClasses(opts);
opts = self.populateModules(opts);
opts = self.populateFiles(opts);
opts.meta.classDescription = self._parseCode(markdown(data.description || ' '));
opts.meta.methods = [];
opts.meta.properties = [];
opts.meta.attrs = [];
opts.meta.events = [];
opts.meta.extension_for = null;
if (data.uses) {
opts.meta.uses = data.uses;
}
if (data.entension_for && data.extension_for.length) {
opts.meta.extension_for = data.extension_for;
}
if (data.extends) {
opts.meta.extends = data.extends;
}
var classItems = [];
self.data.classitems.forEach(function(i) {
if (i.class === data.name) {
classItems.push(i);
}
});
classItems = self.mergeExtends(data, classItems);
if (data.is_constructor) {
var i = Y.mix({}, data);
i = self.augmentData(i);
i.paramsList = [];
if (i.params) {
i.params.forEach(function(p, v) {
var name = p.name;
if (p.optional) {
name = '[' + name + ((p.optdefault) ? '=' + p.optdefault : '') + ']'
}
i.paramsList.push(name);
});
}
//i.methodDescription = self._parseCode(markdown(i.description));
i.hasAccessType = i.access;
i.hasParams = i.paramsList.length;
if (i.paramsList.length) {
i.paramsList = i.paramsList.join(', ');
} else {
i.paramsList = ' ';
}
i.returnType = ' ';
if (i.return) {
i.hasReturn = true;
i.returnType = i.return.type;
}
//console.error(i);
opts.meta.is_constructor = [i];
}
classItems.forEach(function(i) {
switch (i.itemtype) {
case 'method':
i = self.augmentData(i);
i.paramsList = [];
if (i.params) {
i.params.forEach(function(p, v) {
var name = p.name;
if (p.optional) {
name = '[' + name + ((p.optdefault) ? '=' + p.optdefault : '') + ']'
}
i.paramsList.push(name);
});
}
//i.methodDescription = self._parseCode(markdown(i.description || ''));
i.methodDescription = self._parseCode(i.description);
if (i.example && i.example.length) {
if (i.example.forEach) {
var e = '';
i.example.forEach(function(v) {
e += self._parseCode(markdown(v));
});
i.example = e;
} else {
i.example = self._parseCode(markdown(i.example));
}
}
i.hasAccessType = i.access;
i.hasParams = i.paramsList.length;
if (i.paramsList.length) {
i.paramsList = i.paramsList.join(', ');
} else {
i.paramsList = ' ';
}
i.returnType = ' ';
if (i.return) {
i.hasReturn = true;
i.returnType = i.return.type;
}
// If this item is provided by a module other
// than the module that provided the original
// class, add the original module name to the
// item's `providedBy` property so we can
// indicate the relationship.
if ((i.submodule || i.module) !== (data.submodule || data.module)) {
i.providedBy = (i.submodule || i.module);
}
opts.meta.methods.push(i);
break;
case 'property':
i = self.augmentData(i);
//i.propertyDescription = self._parseCode(markdown(i.description || ''));
i.propertyDescription = self._parseCode(i.description);
if (!i.type) {
i.type = 'unknown';
}
if (i.final === '') {
i.final = true;
}
if (i.example && i.example.length) {
if (i.example.forEach) {
var e = '';
i.example.forEach(function(v) {
e += self._parseCode(markdown(v));
});
i.example = e;
} else {
i.example = self._parseCode(markdown(i.example));
}
}
// If this item is provided by a module other
// than the module that provided the original
// class, add the original module name to the
// item's `providedBy` property so we can
// indicate the relationship.
if ((i.submodule || i.module) !== (data.submodule || data.module)) {
i.providedBy = (i.submodule || i.module);
}
opts.meta.properties.push(i);
break;
case 'attribute': // fallthru
case 'config':
i = self.augmentData(i);
//i.attrDescription = self._parseCode(markdown(i.description || ''));
i.attrDescription = self._parseCode(i.description);
if (i.itemtype === 'config') {
i.config = true;
} else {
i.emit = self.options.attributesEmit;
}
if (i.example && i.example.length) {
if (i.example.forEach) {
var e = '';
i.example.forEach(function(v) {
e += self._parseCode(markdown(v));
});
i.example = e;
} else {
i.example = self._parseCode(markdown(i.example));
}
}
// If this item is provided by a module other
// than the module that provided the original
// class, add the original module name to the
// item's `providedBy` property so we can
// indicate the relationship.
if ((i.submodule || i.module) !== (data.submodule || data.module)) {
i.providedBy = (i.submodule || i.module);
}
opts.meta.attrs.push(i);
break;
case 'event':
i = self.augmentData(i);
//i.eventDescription = self._parseCode(markdown(i.description || ''));
i.eventDescription = self._parseCode(i.description);
if (i.example && i.example.length) {
if (i.example.forEach) {
var e = '';
i.example.forEach(function(v) {
e += self._parseCode(markdown(v));
});
i.example = e;
} else {
i.example = self._parseCode(markdown(i.example));
}
}
// If this item is provided by a module other
// than the module that provided the original
// class, add the original module name to the
// item's `providedBy` property so we can
// indicate the relationship.
if ((i.submodule || i.module) !== (data.submodule || data.module)) {
i.providedBy = (i.submodule || i.module);
}
opts.meta.events.push(i);
break;
}
});
opts.meta.attrs.sort(self.nameSort);
opts.meta.events.sort(self.nameSort);
opts.meta.methods.sort(self.nameSort);
opts.meta.properties.sort(self.nameSort);
if (!opts.meta.methods.length) {
delete opts.meta.methods;
}
if (!opts.meta.properties.length) {
delete opts.meta.properties;
}
if (!opts.meta.attrs.length) {
delete opts.meta.attrs;
}
if (!opts.meta.events.length) {
delete opts.meta.events;
}
var view = new Y.DocView(opts.meta);
var mainLayout = opts.layouts[layout];
self.render('{{>classes}}', view, mainLayout, opts.partials, stack.add(function(err, html) {
self.files++;
stack.html = html;
stack.view = view;
}));
});
stack.done(function() {
cb(stack.html, stack.view);
});
},
/**
* Generates the class files under "out"/classes/
* @method writeClasses
* @param {Callback} cb The callback to execute after it's completed
*/
writeClasses: function(cb, layout) {
layout = layout || 'main';
var self = this,
stack = new Y.Parallel();
stack.html = [];
stack.view = [];
Y.log('Rendering and writing ' + Object.keys(self.data.classes).length + ' class pages.', 'info', 'builder');
Y.each(self.data.classes, function(v) {
if (v.external) { return }
self.renderClass(stack.add(function(html, view) {
stack.html.push(html);
stack.view.push(view);
if (self.options.dumpview) {
Y.Files.writeFile(path.join(self.options.outdir, 'json', 'classes_' + v.name + '.json'), JSON.stringify(view), stack.add(noop));
}
Y.Files.writeFile(path.join(self.options.outdir, 'classes', v.name + '.html'), html, stack.add(noop));
}), v, layout);
});
stack.done(function() {
Y.log('Finished writing class files', 'info', 'builder');
cb(stack.html, stack.view);
});
},
/**
* Sort method of array of objects with a property called __name__
* @method nameSort
* @param {Object} a First object to compare
* @param {Object} b Second object to compare
* @return {Number} 1, -1 or 0 for sorting.
*/
nameSort: function(a, b) {
if (!a.name || !b.name) {
return 0;
}
var an = a.name.toLowerCase(),
bn = b.name.toLowerCase(),
ret = 0;
if (an < bn) {
ret = -1;
}
if (an > bn) {
ret = 1
}
return ret;
},
/**
* Generates the syntax files under `"out"/files/`
* @method writeFiles
* @param {Callback} cb The callback to execute after it's completed
*/
writeFiles: function(cb, layout) {
layout = layout || 'main';
var self = this,
stack = new Y.Parallel();
stack.html = [];
stack.view = [];
Y.log('Rendering and writing ' + Object.keys(self.data.files).length + ' source files.', 'info', 'builder');
Y.each(self.data.files, function(v) {
if (v.external) { return }
self.renderFile(stack.add(function(html, view, data) {
if (!view || !data) {
return;
}
stack.html.push(html);
stack.view.push(view);
if (self.options.dumpview) {
Y.Files.writeFile(path.join(self.options.outdir, 'json', 'files_' + self.filterFileName(data.name) + '.json'), JSON.stringify(view), stack.add(noop));
}
Y.Files.writeFile(path.join(self.options.outdir, 'files', self.filterFileName(data.name) + '.html'), html, stack.add(noop));
}), v, layout);
});
stack.done(function() {
Y.log('Finished writing source files', 'info', 'builder');
cb(stack.html, stack.view);
});
},
renderFile: function(cb, data, layout) {
var self = this;
Y.prepare([DEFAULT_THEME, themeDir], self.getProjectMeta(), function(err, opts) {
if (err) {
console.log(err);
}
if (!data.name) {
return;
}
opts.meta = Y.merge(opts.meta, data);
opts.meta.title = self.data.project.name;
opts.meta.moduleName = data.name;
opts.meta.projectRoot = '../';
opts.meta.projectAssets = '../assets';
opts = self.populateClasses(opts);
opts = self.populateModules(opts);
opts = self.populateFiles(opts);
opts.meta.fileName = data.name;
fs.readFile(opts.meta.fileName, 'utf8', Y.rbind(function(err, str, opts, data) {
if (err) {
Y.log(err, 'error', 'builder');
cb(err);
return;
}
opts.meta.fileData = str;
var view = new Y.DocView(opts.meta, 'index');
var mainLayout = opts.layouts[layout];
self.render('{{>files}}', view, mainLayout, opts.partials, function(err, html) {
self.files++;
cb(html, view, data);
});
}, this, opts, data));
});
},
writeAPIMeta: function(cb) {
Y.log('Writing API Meta Data', 'info', 'builder');
var self = this;
this.renderAPIMeta(function(js) {
fs.writeFile(path.join(self.options.outdir, 'api.js'), js, 'utf8', cb);
});
},
renderAPIMeta: function(cb) {
var opts = { meta: {} }, self = this;
opts = this.populateClasses(opts);
opts = this.populateModules(opts);
['classes', 'modules'].forEach(function(id) {
opts.meta[id].forEach(function(v, k) {
opts.meta[id][k] = v.name;
if (v.submodules) {
v.submodules.forEach(function(s) {
opts.meta[id].push(s.displayName);
});
}
});
opts.meta[id].sort();
});
var apijs = 'YUI.add("yuidoc-meta", function(Y) {\n' +
' Y.YUIDoc = { meta: ' + JSON.stringify(opts.meta, null, 4) + ' };\n' +
'});';
cb(apijs);
},
/**
* Normalizes a file path to a writable filename:
*
* var path = 'lib/file.js';
* returns 'lib_file.js';
*
* @method filterFileName
* @param {String} f The filename to normalize
* @return {String} The filtered file path
*/
filterFileName: function(f) {
return f.replace(/[\/\\]/g, '_');
},
/**
* Compiles the templates from the meta-data provided by DocParser
* @method compile
* @param {Callback} cb The callback to execute after it's completed
*/
compile: function(cb) {
var self = this;
var starttime = (new Date()).getTime();
Y.log('Compiling Templates', 'info', 'builder');
this.mixExternal(function() {
self.makeDirs(function() {
Y.log('Copying Assets', 'info', 'builder');
if (!Y.Files.isDirectory(path.join(self.options.outdir, 'assets'))) {
fs.mkdirSync(path.join(self.options.outdir, 'assets'), 0777);
}
Y.Files.copyAssets([path.join(DEFAULT_THEME, 'assets'), path.join(themeDir, 'assets')], path.join(self.options.outdir, 'assets'), false, function() {
var cstack = new Y.Parallel();
self.writeModules(cstack.add(function() {
self.writeClasses(cstack.add(function() {
if (!self.options.nocode) {
self.writeFiles(cstack.add(noop));
}
}));
}));
self.writeIndex(cstack.add(noop));
self.writeAPIMeta(cstack.add(noop));
cstack.done(function() {
var endtime = (new Date()).getTime();
var timer = ((endtime - starttime) / 1000) + ' seconds';
Y.log('Finished writing ' + self.files + ' files in ' + timer, 'info', 'builder');
if (cb) { cb(); }
});
});
});
});
}
}
});