/*
* Copyright 2009, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @fileoverview The jsdoctoolkit loads this file and calls publish()
*/
var g_symbolSet; // so we can look stuff up below.
var g_symbolArray;
var g_filePrefix;
var g_skipRE;
var g_validJSDOCTypes = {
'boolean': true,
'Event': true,
'Element': true,
'null': true,
'number': true,
'Number': true,
'object': true,
'Object': true,
'*': true,
'...': true,
'RegExp': true,
'string': true,
'String': true,
'XMLHttpRequest': true,
'void': true,
'undefined': true};
var g_unknownTypes = { };
var g_numErrors = 0;
var g_o3djsMode = false;
var g_outputMode;
var g_baseURL;
var g_topURL;
var g_templates = [];
var g_o3dPropertyRE = /^(\w+)\s+(\w+)\s+/;
var g_startContainerRE = /^(?:function\(|!function\(|[\(<{[])/;
var g_containerRE = /function\(|!function\(|[\(<{[]/;
var g_openCloseMap = {
'function(': ')',
'!function(': ')',
'(': ')',
'<': '>',
'[': ']',
'{': '}'};
var g_closeMap = {
')': true,
'>': true,
']': true,
'}': true};
/**
* Called automatically by JsDoc Toolkit.
* @param {SymbolSet} symbolSet Set of all symbols in all files.
*/
function publish(symbolSet) {
try {
publishInternal(symbolSet);
} catch (e) {
generateError(e);
}
if (g_numErrors > 0) {
print('Num Errors: ' + g_numErrors);
System.exit(1);
}
}
/**
* Called by us to catch errors.
* @param {SymbolSet} symbolSet Set of all symbols in all files.
*/
function publishInternal(symbolSet) {
publish.conf = { // trailing slash expected for dirs
ext: '.ezt',
outDir: JSDOC.opt.d,
templatesDir: JSDOC.opt.t,
symbolsDir: '',
exportsFile: JSDOC.opt.D.exportsFile,
prefix: JSDOC.opt.D.prefix,
mode: JSDOC.opt.D.mode};
publish.conf.srcDir = publish.conf.outDir + 'src/';
publish.conf.htmlDir = JSDOC.opt.D.htmlOutDir;
g_baseURL = JSDOC.opt.D.baseURL;
g_topURL = JSDOC.opt.D.topURL;
g_outputMode = JSDOC.opt.D.mode;
for (var key in publish.conf) {
print ("publish.conf." + key + ": " + publish.conf[key])
}
if (publish.conf.mode == 'o3djs') {
g_o3djsMode = true;
}
// In o3djs mode, don't generate docs for these.
g_skipRE = new RegExp('^(o3d$|o3d\\.|Vectormath)');
// Symbols we should always skip.
var alwaysSkipRE = new RegExp('^(_global_|VectorMath|Aos)');
// is source output is suppressed, just display the links to the source file
if (JSDOC.opt.s && defined(Link) && Link.prototype._makeSrcLink) {
Link.prototype._makeSrcLink = function(srcFilePath) {
return '<' + srcFilePath + '>';
}
}
// create the folders and subfolders to hold the output
IO.mkPath((publish.conf.outDir).split('/'));
IO.mkPath((publish.conf.htmlDir).split('/'));
// used to allow Link to check the details of things being linked to
Link.symbolSet = symbolSet;
Link.base = '../';
// used to allow other parts of this module to access database of symbols
// and the file prefix.
g_symbolSet = symbolSet;
g_filePrefix = publish.conf.prefix;
// create the required templates
try {
var templatesDir = publish.conf.templatesDir;
var classTemplate = new JSDOC.JsPlate(templatesDir + 'class.tmpl');
var exportsTemplate = new JSDOC.JsPlate(templatesDir + 'exports.tmpl');
var membersTemplate = new JSDOC.JsPlate(templatesDir + 'members.tmpl');
var classTreeTemplate = new JSDOC.JsPlate(templatesDir + 'classtree.tmpl');
var fileListTemplate = new JSDOC.JsPlate(templatesDir + 'filelist.tmpl');
var annotatedTemplate = new JSDOC.JsPlate(templatesDir + 'annotated.tmpl');
var namespacesTemplate = new JSDOC.JsPlate(templatesDir +
'namespaces.tmpl');
var dotTemplate = new JSDOC.JsPlate(templatesDir + 'dot.tmpl');
} catch(e) {
generateError('Couldn\'t create the required templates: ' + e);
System.exit(1);
}
// some utility filters
function hasNoParent($) {return ($.memberOf == '')}
function isaFile($) {return ($.is('FILE'))}
function isaClass($) {return ($.is('CONSTRUCTOR') || $.isNamespace)}
// get an array version of the symbolset, useful for filtering
var symbols = symbolSet.toArray();
g_symbolArray = symbols;
// create the hilited source code files
if (false) {
var files = JSDOC.opt.srcFiles;
for (var i = 0, l = files.length; i < l; i++) {
var file = files[i];
makeSrcFile(file, publish.conf.srcDir);
}
}
// Comment this lines in to see all symbol information.
//for (var ii = 0; ii < symbols.length; ++ii) {
// var symbol = symbols[ii];
// print('------[' + symbol.name + ']-------------------------------------');
// dumpObject(symbol, 5);
//}
// get a list of all the classes in the symbolset
var classes = symbols.filter(isaClass).sort(makeSortby('alias'));
var filteredClasses = [];
var exports = '';
// create each of the class pages
for (var i = 0, l = classes.length; i < l; i++) {
var symbol = classes[i];
g_unknownTypes = { };
symbol.events = symbol.getEvents(); // 1 order matters
symbol.methods = symbol.getMethods(); // 2
if ((g_o3djsMode && g_skipRE.test(symbol.alias)) ||
alwaysSkipRE.test(symbol.alias)) {
print('Skipping docs for : ' + symbol.alias);
continue;
}
print('Generating docs for: ' + symbol.alias);
filteredClasses.push(symbol);
// Comment these lines in to see what data is available to the templates.
//if (symbol.name == 'Canvas') {
// print('------[' + symbol.name + ']-----------------------------------');
// dumpObject(symbol, 5);
//}
// symbol.filename
symbol.source = symbol.srcFile; // This is used as a link to the source
symbol.filename = symbol.srcFile; // This is display as the link.
var output = '';
output = classTemplate.process(symbol);
IO.saveFile(publish.conf.outDir,
(publish.conf.prefix + symbol.alias +
'_ref' + publish.conf.ext).toLowerCase(),
output);
IO.saveFile(publish.conf.htmlDir,
(publish.conf.prefix + symbol.alias +
'_ref.html').toLowerCase(),
output);
var output = '';
output = membersTemplate.process(symbol);
IO.saveFile(publish.conf.outDir,
(publish.conf.prefix + symbol.alias +
'_members' + publish.conf.ext).toLowerCase(),
output);
IO.saveFile(publish.conf.htmlDir,
(publish.conf.prefix + symbol.alias +
'_members.html').toLowerCase(),
output);
if (publish.conf.exportsFile) {
exports += exportsTemplate.process(symbol);
}
}
var classTree = classTreeTemplate.process(filteredClasses);
IO.saveFile(publish.conf.outDir, 'classtree.html', classTree);
IO.saveFile(publish.conf.htmlDir, 'classtree.html', classTree);
var fileList = fileListTemplate.process(symbols);
IO.saveFile(publish.conf.outDir, 'filelist.html', fileList);
IO.saveFile(publish.conf.htmlDir, 'filelist.html', fileList);
var annotated = annotatedTemplate.process(filteredClasses);
IO.saveFile(publish.conf.outDir, 'annotated' + publish.conf.ext, annotated);
IO.saveFile(publish.conf.htmlDir, 'annotated.html', annotated);
var namespaces = namespacesTemplate.process(filteredClasses);
IO.saveFile(publish.conf.outDir, 'namespaces' + publish.conf.ext, namespaces);
IO.saveFile(publish.conf.htmlDir, 'namespaces.html', namespaces);
var dot = dotTemplate.process(filteredClasses);
IO.saveFile(publish.conf.htmlDir, 'class_hierarchy.dot', dot);
if (publish.conf.exportsFile) {
print("Writing exports: " + publish.conf.exportsFile);
var blankLineRE = /\n *\n/gm;
while (blankLineRE.test(exports)) {
exports = exports.replace(blankLineRE, '\n');
}
var parts = publish.conf.exportsFile.replace('\\', '/').
match(/(.*?)\/([^\/]+)$/);
IO.saveFile(parts[1], parts[2], exports);
}
}
/**
* Gets just the first sentence (up to a full stop).
* Should not break on dotted variable names.
* @param {string} desc Description to extract summary from.
* @return {string} summary.
*/
function summarize(desc) {
if (typeof desc != 'undefined')
return desc.match(/([\w\W]+?\.)[^a-z0-9_$]/i) ? RegExp.$1 : desc;
}
/**
* Makes a symbol sorter by some attribute.
* @param {string} attribute to sort by.
* @return {number} sorter result.
*/
function makeSortby(attribute) {
return function(a, b) {
if (a[attribute] != undefined && b[attribute] != undefined) {
a = a[attribute].toLowerCase();
b = b[attribute].toLowerCase();
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
}
}
/**
* Pull in the contents of an external file at the given path.
* @param {string} path Path of file relative to template directory.
* @return {string} contents of file.
*/
function include(path) {
var template = g_templates[path];
if (!template) {
try {
template = new JSDOC.JsPlate(JSDOC.opt.t + path);
} catch (e) {
generateError('Could not include: ' + path + '\n' + e);
template = '';
}
g_templates[path] = template;
}
var output = template.process({});
return output;
}
/**
* Turns a raw source file into a code-hilited page in the docs.
* @param {string} path Path to source.
* @param {string} srcDir path to place to store hilited page.
* @param {string} opt_name to name output file.
*
*/
function makeSrcFile(path, srcDir, opt_name) {
if (JSDOC.opt.s) return;
if (!opt_name) {
opt_name = path.replace(/\.\.?[\\\/]/g, '').replace(/[\\\/]/g, '_');
opt_name = opt_name.replace(/\:/g, '_');
}
var src = {path: path, name: opt_name, charset: IO.encoding, hilited: ''};
if (defined(JSDOC.PluginManager)) {
JSDOC.PluginManager.run('onPublishSrc', src);
}
if (src.hilited) {
IO.saveFile(srcDir, opt_name + publish.conf.ext, src.hilited);
}
}
/**
* Builds output for displaying function parameters.
* @param {Array} params Array of function params.
* @return {string} string in format '(param1, param2)'.
*/
function makeSignature(params) {
if (!params) return '()';
var signature = '(' +
params.filter(
function($) {
return $.name.indexOf('.') == -1; // don't show config params in signature
}
).map(
function($) {
return $.name;
}
).join(', ') + ')';
return signature;
}
/**
* Find symbol {@link ...} strings in text and turn into html links.
* @param {string} str String to modify.
* @return {string} modifed string.
*/
function resolveLinks(str) {
str = str.replace(/\{@link ([^} ]+) ?\}/gi,
function(match, symbolName) {
return new Link().toSymbol(symbolName);
}
);
return str;
}
/**
* Makes a link for a symbol.
*
* @param {string} symbolName Name of symbol
* @param {string} extra extra
* @param {string} opt_bookmark Optional bookmark.
*/
function makeSymbolLink(symbolName, extra, opt_bookmark) {
var prefix = g_filePrefix;
if (g_o3djsMode && g_skipRE.test(symbolName)) {
prefix = '../classo3d_1_1_';
}
return (prefix + symbolName + extra +
'.html').toLowerCase() +
(opt_bookmark ? '#' + opt_bookmark : '');
}
/**
* Make link from symbol.
* @param {Object} symbol Symbol from class database.
* @param {string} opt_extra extra suffix to add before '.html'.
* @return {string} url to symbol.
*/
function getLinkToSymbol(symbol, opt_extra) {
opt_extra = opt_extra || '_ref';
if (symbol.is('CONSTRUCTOR') || symbol.isNamespace) {
return makeSymbolLink(symbol.alias, opt_extra);
} else {
var parentSymbol = getSymbol(symbol.memberOf);
return makeSymbolLink(parentSymbol.alias, opt_extra, symbol.name);
}
}
/**
* Given a class alias, returns a link to its reference page
* @param {string} classAlias Fully qualified name of class.
* @return {string} url to class.
*/
function getLinkToClassByAlias(classAlias) {
var symbol = getSymbol(classAlias);
if (!symbol) {
throw Error('no documentation for "' + classAlias + '"');
}
return getLinkToSymbol(symbol);
}
/**
* Given a class alias, returns a link to its member reference page
* @param {string} classAlias Fully qualified name of class.
* @return {string} url to class in members file.
*/
function getLinkToClassMembersByAlias(classAlias) {
var symbol = getSymbol(classAlias);
return getLinkToSymbol(symbol, '_members');
}
/**
* Given a class alias like o3djs.namespace.function returns an HTML string
* with a link to each part (o3djs, namespace, function)
* @param {string} classAlias Fully qualified alias of class.
* @param {string} opt_cssClassId css class Id to put in class="" instead links.
* @return {string} html with links to each class and parent.
*/
function getHierarchicalLinksToClassByAlias(classAlias, opt_cssClassId) {
var parts = classAlias.split('.');
var name = '';
var html = '';
var delim = '';
var classId = '';
if (opt_cssClassId) {
classId = ' class="' + opt_cssClassId + '"';
}
for (var pp = 0; pp < parts.length; ++pp) {
var part = parts[pp];
name = name + delim + part;
link = getLinkToClassByAlias(name);
html = html + delim + '' + part + '';
delim = '.';
}
return html;
}
/**
* Dumps a javascript object.
*
* @param {Object} obj Object to dump.
* @param {number} depth Depth to dump (0 = forever).
* @param {string} opt_prefix Prefix to put before each line.
*/
function dumpObject(obj, depth, opt_prefix) {
opt_prefix = opt_prefix || '';
--depth;
for (var prop in obj) {
if (typeof obj[prop] != 'function') {
dumpWithPrefix(prop + ' : ' + obj[prop], opt_prefix);
if (depth != 0) {
dumpObject(obj[prop], depth, opt_prefix + ' ');
}
}
}
}
/**
* Dumps a string, putting a prefix before each line
* @param {string} str String to dump.
* @param {string} prefix Prefix to put before each line.
*/
function dumpWithPrefix(str, prefix) {
var parts = str.split('\n');
for (var pp = 0; pp < parts.length; ++pp) {
print(prefix + parts[pp]);
}
}
/**
* gets the type of a property.
* @param {!object} property Property object.
* @return {string} type of property.
*/
function getPropertyType(property) {
if (property.type.length > 0) {
return property.type;
} else {
var tag = property.comment.getTag('type');
if (tag.length > 0) {
return tag[0].type;
} else {
return 'undefined';
}
}
}
/**
* Gets the parameters for a class.
* Parameters are an o3d specific thing. We have to look for tags that
* start with @o3dparameter
* @param {!Symbol} symbol
*/
function getParameters(symbol) {
var params = [];
if (symbol.inheritsFrom.length) {
params = getParameters(getSymbol(symbol.inheritsFrom[0]));
}
var tags = symbol.comment.getTag('o3dparameter');
for (var ii = 0; ii < tags.length; ++ii) {
var tag = tags[ii];
var tagString = tag.toString();
var parts = tagString.match(g_o3dPropertyRE);
if (!parts) {
generateError('Malformed o3dparameter specification for ' + symbol.alias +
' : "' + tag + '"');
} else {
var descString = tagString.substr(parts[0].length);
var param = {
name: parts[1],
type: 'o3d.' + parts[2],
desc: descString,
parent: symbol.alias,
};
params.push(param);
}
}
return params;
}
/**
* Returns whether or not the symbol is deprecated. Apparently jsdoctoolkit is
* supposed to extract this info for us but it's not so we have to do it
* manually.
* @param {!Symbol} symbol The symbol to check.
* @return {boolean} True if the symbol is deprecated.
*/
function isDeprecated(symbol) {
var tags = symbol.comment.getTag('deprecated');
return tags.length > 0;
}
/**
* Converts [ to [[] for ezt files.
* Also converts '\n\n' to
* @param {string} str to sanitize.
* @return {string} Sanitized string.
*/
function sanitizeForEZT(str) {
str = str.replace(/
/g, ''); return str.replace(/\[/g, '[[]').replace(/\n\n/g, '
'); } /** * Check if string starts with another string. * @param {string} str String to check. * @param {string} prefix Prefix to check for. * @return {boolean} True if str starts with prefix. */ function startsWith(str, prefix) { return str.substring(0, prefix.length) === prefix; } /** * Check if string ends with another string. * @param {string} str String to check. * @param {string} suffix Suffix to check for. * @return {boolean} True if str ends with suffix. */ function endsWith(str, suffix) { return str.substring(str.length - suffix.length) === suffix; } /** * Returns a string stripped of leading and trailing whitespace. * @param {string} str String to strip. * @return {string} stripped string. */ function stripWhitespace(str) { return str.replace(/^\s+/, '').replace(/\s+$/, ''); } /** * Converts a camelCase name to underscore as in TypeOfFruit becomes * type_of_fruit. * @param {string} str CamelCase string. * @return {string} underscorified str. */ function camelCaseToUnderscore(str) { function toUnderscore(match) { return '_' + match.toLowerCase(); } return str[0].toLowerCase() + str.substring(1).replace(/[A-Z]/g, toUnderscore); } /** * Prints a warning about an unknown type only once. * @param {string} place Use to print error message if type not found. * @param {string} type Type specification. */ function reportUnknownType(place, type) { if (!g_unknownTypes[type]) { g_unknownTypes[type] = true; generatePlaceError (place, 'reference to unknown type: "' + type + '"'); } } /** * Gets index of closing character. * @param {string} str string to search. * @param {number} startIndex index to start searching at. Must be an opening * character. * @return {number} Index of closing character or (-1) if not found. */ function getIndexOfClosingCharacter(str, startIndex) { var stack = []; if (!g_openCloseMap[str[startIndex]]) { throw 'startIndex does not point to opening character.'; } var endIndex = str.length; while (startIndex < endIndex) { var c = str[startIndex]; var closing = g_openCloseMap[c]; if (closing) { stack.unshift(closing); } else { closing = g_closeMap[c]; if (closing) { var expect = stack.shift() if (c != expect) { return -1; } if (stack.length == 0) { return startIndex; } } } ++startIndex; } return -1; } /** * Make's a name by concatenating strings. * @param {...[string]} strings to concatenate. * @return {string} Concatenated string. */ function makeName() { var str = ''; for (var ii = 0; ii < arguments.length; ++ii) { if (str) { str += '.'; } str += arguments[ii]; } return str; } /** * Generates an error msg. * @param {string} msg. */ function generateError(msg) { ++g_numErrors; print('ERROR: ' + msg); } /** * Generates an error msg. * @param {string} place Use to print error message. * @param {string} msg. */ function generatePlaceError(place, msg) { generateError(place + ': ' + msg); } /** * Converts a reference to a single JSDOC type specification to an html link. * @param {string} place Use to print error message if type not found. * @param {string} str to linkify. * @return {string} linkified string. */ function linkifySingleType(place, type) { if (!type) { generatePlaceError(place, 'type is empty'); return ''; } type = stripWhitespace(type); var not = ''; var equals = ''; // Remove ! if it exists. if (type[0] == '!') { not = '!' type = type.substring(1); } if (endsWith(type, '=')) { equals = '='; type = type.substring(0, type.length - 1); } var link = type; // Check for array wrapper. if (startsWith(type, 'Array.<')) { var closingAngle = getIndexOfClosingCharacter(type, 6); if (closingAngle < 0) { generatePlaceError(place, 'Unmatched "<" in Array type : ' + type); } else { link = 'Array.<' + linkifySingleType(place, type.substring(7, closingAngle)) + '>'; } } else if (startsWith(type, 'Object.<')) { var closingAngle = getIndexOfClosingCharacter(type, 6); if (closingAngle < 0) { generatePlaceError(place, 'Unmatched "<" in Object type : ' + type); } else { var objectSpec = type.substring(8, closingAngle); var elements = objectSpec.split(/\s*,\s*/); if (elements.length != 2) { generatePlaceError(place, 'An Object spec must have exactly 2 types'); } link = 'Object.<' + linkifySingleType(place, elements[0]) + ', ' + linkifySingleType(place, elements[1]) + '>'; } } else if (startsWith(type, 'function(')) { var closingParen = getIndexOfClosingCharacter(type, 8); if (closingParen < 0) { generatePlaceError(place, 'Unmatched "(" in function type : ' + type); } else { var end = type.substring(closingParen + 1); if (!startsWith(end, ': ')) { generatePlaceError(place, 'Malformed return specification on function. Must be' + ' "function(args): type" including the space after the colon.'); } else { var output = ''; var argsStr = type.substring(9, closingParen); if (argsStr) { // TODO(gman): This needs to split taking parens, brackets and angle // brackets into account. var args = argsStr.split(/ *, */); for (var ii = 0; ii < args.length; ++ii) { if (ii > 0) { output += ', '; } output += linkifyTypeSpec(place, args[ii]); } } link = 'function(' + output + '): ' + linkifyTypeSpec(place, end.substring(2)); } } } else if (startsWith(type, '{') && endsWith(type, '}')) { // found a record. var elements = type.substr(1, type.length - 2).split(/\s*,\s*/); var output = '{'; for (var ii = 0; ii < elements.length; ++ii) { if (ii > 0) { output += ', '; } var element = elements[ii]; var colon = element.indexOf(': '); if (colon < 0) { generatePlaceError(place, 'Malformed record specification. Format must be ' + '{id1: type1, id2: type2, ...}.'); output += element; } else { var name = element.substring(0, colon); var subType = element.substring(colon + 2); output += name + ': ' + linkifyTypeSpec(place, subType); } } link = output + '}'; } else { var symbol = getSymbol(type); if (symbol) { link = '' + type + ''; } else { // See if the symbol is a property or field. var found = false; var period = type.lastIndexOf('.'); if (period >= 0 && type != '...') { var subType = type.substring(0, period); var member = type.substring(period + 1); symbol = getSymbol(subType); if (symbol && symbol.hasMember(member)) { var field = type.substring(period + 1); link = '' + type + ''; found = true; } } if (!found) { if (type[0] == '?') { type = type.substring(1); } if (!g_validJSDOCTypes[type]) { reportUnknownType(place, type); } } } } return not + link + equals; } /** * Splits a string by containers. A string like "ab(cd,ef)gh" will * be returned as ['ab', '(cd,ef)', 'gh'] * @param {string} str String to split. * @return {!Array.} The split string parts. */ function splitByContainers(str) { var parts = []; for (;;) { var match = str.match(g_containerRE); if (!match) { if (str.length > 0) { parts.push(str); } return parts; } var startIndex = str.indexOf(match); if (startIndex != 0) { parts.push(str.substring(0, startIndex)); } var endIndex = getIndexOfClosingCharacter( str, startIndex + match.toString().length - 1); if (endIndex < 0) { throw 'no closing character for "' + str[startIndex] + '" in "' + str + '"'; } var piece = str.substring(startIndex, endIndex + 1); parts.push(piece); str = str.substring(endIndex + 1); } } /** * Fix function specs. * The jsdoctoolkit wrongly converts ',' to | as in 'function(a, b)' to * 'function(a|b)' and '{id1: type1, id2: type2}' to '{id1: type1|id2: type2}' * so we need to put those back here (or fix jsdoctoolkit, the proper solution). * Worse, we have to distinguish between 'function(a|b)' and '(a|b)'. The former * needs to become 'function(a, b)' while the later needs to stay the same. * * @param {string} str JSDOC type specification string . * @param {boolean} opt_paren True if we are inside a paren. * @return {string} str with '|' converted back to ', ' unless the specification * starts with '(' and ends with ')'. That's not a very good check beacuse * you could 'function((string|number)) and this would fail to do the right * thing. */ function fixSpecCommas(str, opt_paren) { var start = str.match(g_startContainerRE); if (start) { var startStr = start.toString(); var closing = g_openCloseMap[startStr]; if (closing) { var lastChar = str[str.length - 1]; if (lastChar == closing) { var middle = str.substring(startStr.length, str.length - 1); return startStr + fixSpecCommas(middle, startStr === '(') + lastChar; } } } if (str.match(g_containerRE)) { var parts = splitByContainers(str); for (var ii = 0; ii < parts.length; ++ii) { parts[ii] = fixSpecCommas(parts[ii], opt_paren); } return parts.join(''); } else { if (opt_paren) { return str; } return str.replace(/\|/g, ', '); } } /** * Converts a JSDOC type specification into html links. For example * '(!o3djs.math.Vector3|!o3djs.math.Vector4)' would change to * '(!o3djs.math.Vector3 * |!o3djs.math.Vector4)'. * @param {string} place Use to print error message if type not found. * @param {string} str to linkify. * @return {string} linkified string. */ function linkifyTypeSpec(place, str) { var output = ''; if (str) { try { var fixed = fixSpecCommas(str); } catch (e) { generatePlaceError(place, e); } // TODO: needs to split outside of parens and angle brackets. if (startsWith(fixed, '(') && endsWith(fixed, ')')) { var types = fixed.substring(1, fixed.length - 1).split('|'); output += '('; for (var tt = 0; tt < types.length; ++tt) { if (tt > 0) { output += '|'; } output += linkifySingleType(place, types[tt]); } output += ')'; } else { output += linkifySingleType(place, fixed); } } else { generatePlaceError(place, 'missing type specification (' + str + ')'); } return output; } /** * Same as linkifyTypeSpec but allows str to be undefined or ''. * @param {string} place Use to print error message if type not found. * @param {string} str to linkify. * @return {string} linkified string. */ function linkifyTypeSpecForReturn(place, str) { if (str) { return linkifyTypeSpec(place, str); } return ''; } /** * Check if a symbol is an enum. * @param {!Symbol} symbol Symbol to check. * @return {boolean} true if symbol is an enum */ function isEnum(symbol) { if (symbol.isStatic && !symbol.isNamespace) { if (symbol.comment && symbol.comment.tagTexts) { for (var ii = 0; ii < symbol.comment.tagTexts.length; ++ii) { var tag = symbol.comment.tagTexts[ii]; if (startsWith(tag.toString(), 'enum')) { return true; } } } } return false; } /** * Gets the public properties of a symbol. * @param {!Symbol} symbol The symbol to get properties from. * @return {!Array.} The public properties of the symbol. */ function getPublicProperties(symbol) { var publicProperties = []; for (var ii = 0; ii < symbol.properties.length; ++ii) { var property = symbol.properties[ii]; if (!property.isPrivate && !property.isNamespace && !isEnum(property)) { publicProperties.push(property); } } return publicProperties; } /** * Gets the public types of a symbol. * @param {!Symbol} symbol The symbol to get properties from. * @return {!Array.} The public types of the symbol. */ function getPublicTypes(symbol) { var publicTypes = []; for (var ii = 0; ii < symbol.properties.length; ++ii) { var type = symbol.properties[ii]; if (!type.isPrivate && !type.isNamespace && isEnum(type)) { publicTypes.push(type); } } for (var ii = 0; ii < g_symbolArray.length; ++ii) { var type = g_symbolArray[ii]; if (type.memberOf == symbol.alias && !type.isPrivate && type.is('CONSTRUCTOR')) { publicTypes.push(type); } } return publicTypes; } /** * Gets a symbol for a type. * This is here mostly for debugging so you can insert a print before or after * each call to g_symbolSet.getSymbol. * @param {string} type fully qualified type. * @return {Symbol} The symbol object for the type or null if not found. */ function getSymbol(type) { return g_symbolSet.getSymbol(type); } /** * Gets the source path for a symbol starting from 'o3djs/' * @param {!Symbol} symbol The symbol to get the source name for. * @return {string} The name of the source file. */ function getSourcePath(symbol) { var path = symbol.srcFile.replace(/\\/g, '/'); var index = path.indexOf('/o3djs/'); return path.substring(index + 1); } /** * Gets the name of the parent of a symbol. * @param {!Symbol} symbol The symbol to get the parent of * @return {string} The parent's name. */ function getParentName(symbol) { var parent = getSymbol(symbol.memberOf); return parent.isNamespace ? symbol.memberOf : parent.alias; } /** * Gets a qualified name. Used for members. For namespaces will return the fully * qualified name. For objects will return just ObjectName.method * @param {!Symbol} method The method or property to get a qualified name for. * @return {string} The qualified name for the method or property. */ function getQualifiedName(method) { return getParentName(method) + '.' + method.name; } /** * Gets a Documentation name. For members of a namespace returns the fully * qualified name. For members of a class returns ClassName.name * @param {!Symbol} parent Symbol that we are making docs for. * @param {!Symbol} child Method or property of parent to get doc name for. * @return {string} Doc name for child. */ function getDocName(parent, child) { if (parent.isNamespace) { return child.memberOf + "." + child.name; } return parent.name + "." + child.name; } /** * Gets a symbol name for export. If the method or property is static returns * "Namespace.Class.method". If not returns "Namespace.Class.prototype.method". * @param {!Symbol} symbol Symbol to get name for. * @return {string} Name of symbol. */ function getSymbolNameForExport(symbol) { if (!symbol.memberOf) { return symbol.name; } return symbol.memberOf + ((symbol.isStatic || symbol.isNamespace) ? '.' : '.prototype.') + symbol.name; } /** * Gets the base URL for links. */ function getBaseURL() { return g_baseURL; } /** * Gets the top URL for links. */ function getTopURL() { return g_topURL; } /** * Returns the output mode. * @return {string} The output mode. */ function getOutputMode() { return g_outputMode; } /** * Returns true if we should write constructor docs. * @param {!Symbol} symbol The symbol that might have a constructor. * @return {boolean} true if we should write a constructor. */ function shouldWriteConstructor(symbol) { return g_outputMode != 'o3d' && !symbol.isPrivate && !symbol.isBuiltin() && !symbol.isNamespace && symbol.is('CONSTRUCTOR'); } /** * Splits a camelCase word into an array of word parts. * @param {string} word camelCase word. * @return {!Array. } The word split into word parts. */ function splitCamelCase(word) { var spacesAdded = word.replace(/([A-Z])/g, ' $1'); return spacesAdded.split(' '); } /** * Breaks a word at max_length with hyphens. Assumes word is camelCase * @param {string} word Word to break. * @param {number} maxLength word will continue to be split until no part is * this longer than this if possible. * @param {string} string to use to join split parts. * @return {string} The word split then joined with joinString. */ function hyphenateWord(word, maxLength, joinString) { var words = splitCamelCase(word); var hyphenated = ''; var newWord = ''; for (var ii = 0; ii < words.length; ++ii) { var part = words[ii]; var temp = newWord + part; if (temp.length > maxLength) { hyphenated += newWord + joinString; newWord = ''; } newWord += part; } hyphenated += newWord; return hyphenated; }