/* * 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_filePrefix; var g_validJSDOCTypes = { 'number': true, 'Number': true, 'object': true, 'Object': true, '*': true, '...': true, 'string': true, 'String': true, 'void': true, 'undefined': true}; var g_unknownTypes = { }; /** * Called automatically by JsDoc Toolkit. * @param {SymbolSet} symbolSet Set of all symbols in all files. */ function publish(symbolSet) { publish.conf = { // trailing slash expected for dirs ext: '.ezt', outDir: JSDOC.opt.d || SYS.pwd + '../out/jsdoc/', templatesDir: JSDOC.opt.t || SYS.pwd + '../templates/jsdoc/', symbolsDir: '', prefix: 'js_1_0_'}; publish.conf.srcDir = publish.conf.outDir + 'src/' publish.conf.htmlDir = publish.conf.outDir + 'original_html/' // 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.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 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'); } catch(e) { print('Couldn\'t create the required templates: ' + e); quit(); } // some ustility 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(); // 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); } } // get a list of all the classes in the symbolset var classes = symbols.filter(isaClass).sort(makeSortby('alias')); // 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 print('Generating docs for: ' + symbol.alias); // Comment these lines in to see what data is available to the templates. //print('----------------------------------------------------------------'); //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); } var classTree = classTreeTemplate.process(classes); IO.saveFile(publish.conf.outDir, 'classtree.html', classTree); var fileList = fileListTemplate.process(symbols); IO.saveFile(publish.conf.outDir, 'filelist.html', fileList); var annotated = annotatedTemplate.process(classes); IO.saveFile(publish.conf.outDir, 'annotated' + publish.conf.ext, annotated); IO.saveFile(publish.conf.htmlDir, 'annotated.html', annotated); var namespaces = namespacesTemplate.process(classes); IO.saveFile(publish.conf.outDir, 'namespaces' + publish.conf.ext, namespaces); IO.saveFile(publish.conf.htmlDir, 'namespaces.html', namespaces); } /** * 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 path = publish.conf.templatesDir + path; return IO.readFile(path); } /** * 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; } /** * 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) { if (symbol.is('CONSTRUCTOR') || symbol.isNamespace) { return (g_filePrefix + symbol.alias + (opt_extra || '_ref') + '.html').toLowerCase(); } else { var parentSymbol = getSymbol(symbol.memberOf); return (g_filePrefix + parentSymbol.alias + (opt_extra || '_ref') + '.html').toLowerCase() + '#' + 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'; } } } /** * Converts [ to [[] for ezt files. * Also converts '\n\n' to

* @param {string} str to sanitize. * @return {string} Sanitized string. */ function sanitizeForEZT(str) { 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; } /** * 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} type Type specification. */ function reportUnknownType(type) { if (!g_unknownTypes[type]) { g_unknownTypes[type] = true; print ('WARNING: 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 openCloseMap = { '(': ')', '<': '>', '[': ']', '{': '}'}; var closeMap = { ')': true, '>': true, ']': true, '}': true, }; var stack = []; if (!openCloseMap[str[startIndex]]) { throw 'startIndex does not point to opening character.'; } var endIndex = str.length; while (startIndex < endIndex) { var c = str[startIndex]; var closing = openCloseMap[c]; if (closing) { stack.unshift(closing); } else { closing = closeMap[c]; if (closing) { var expect = stack.shift() if (c != expect) { return -1; } if (stack.length == 0) { return startIndex; } } } ++startIndex; } return -1; } /** * Converts a reference to a single JSDOC type specification to an html link. * @param {string} str to linkify. * @return {string} linkified string. */ function linkifySingleType(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) { print ('WARNING: Unmatched "<" in Array type : ' + type); } else { link = 'Array.<' + linkifySingleType(type.substring(7, closingAngle)) + '>'; } } else if (startsWith(type, 'function(')) { var closingParen = getIndexOfClosingCharacter(type, 8); if (closingParen < 0) { print ('WARNING: Unmatched "(" in function type : ' + type); } else { var end = type.substring(closingParen + 1); if (!startsWith(end, ': ')) { print ('WARNING: Malformed return specification on function. Must be' + ' "function(args): type" including the space after the colon.'); } else { var args = type.substring(9, closingParen).split(/ *, */); var output = ''; for (var ii = 0; ii < args.length; ++ii) { if (ii > 0) { output += ', '; } output += linkifyTypeSpec(args[ii]); } link = 'function(' + output + '): ' + linkifyTypeSpec(end.substring(2)); } } } else if (type.indexOf(':') >= 0) { // check for records. var elements = type.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) { print ("WARNING: 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(subType) } } link = output + '}'; } else { var symbol = getSymbol(type); if (symbol) { link = '' + type + ''; } else if (startsWith(type, 'o3d.')) { // TODO: remove this hack, make nixysa generate JSDOC js // files instead of C++ headers and pass those into // jsdoctoolkit. reportUnknownType(type); link = '' + type + ''; } else { // See if the symbol is a property or field. var period = type.lastIndexOf('.'); if (period >= 0 && type != '...') { var subType = type.substring(0, period); symbol = getSymbol(subType); if (symbol) { var field = type.substring(period + 1); link = '' + type + ''; } else { if (subType[0] == '?') { subType = subType.substring(1); } if (!g_validJSDOCTypes[subType]) { reportUnknownType(type); } } } } } return not + link + equals; } /** * 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). * @param {string} str JSDOC type specification string . * @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) { // TODO: This is really a complete hack and we should fix the // jsdoctoolkit. if (startsWith(str, '(') && endsWith(str, ')')) { return str; } else { return str.replace(/\|/g, ', '); } } /** * Converts a JSDOC type specification into html links. For example * '(!o3djs.math.Vector3|!O3D.math.Vector4)' would change to * '(!o3djs.math.Vector3 * |!o3djs.math.Vector4)'. * @param {string} str to linkify. * @return {string} linkified string. */ function linkifyTypeSpec(str) { var output = ''; if (str) { var fixed = fixSpecCommas(str); // 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(types[tt]); } output += ')'; } else { output += linkifySingleType(fixed); } } return output; } /** * 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); }