First code commit.

This commit is contained in:
Michael Mathews 2010-06-06 21:11:11 +01:00
parent bf9c65d03d
commit 3af2c46d72
22 changed files with 1761 additions and 0 deletions

81
LICENSE.md Normal file
View File

@ -0,0 +1,81 @@
License
=======
JSDoc Toolkit Version 3 is free software.
Copyright 2010 (c) Michael Mathews <micmath@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); you
may not use this file except in compliance with the License. You may
obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing
permissions and limitations under the License.
In Addition
===========
Third party software is included, used by or distributed
with JSDoc Toolkit Version 3. Each is provided under its own license
and/or has source available from other locations.
Rhino
-----
Rhino is open source and licensed by Mozilla under the MPL 1.1 or
later/GPL 2.0 or later licenses.
https://developer.mozilla.org/en/Rhino_License
You can obtain the source code for Rhino from the Mozilla web site at
http://www.mozilla.org/rhino/download.html
Normal Template
---------------
The source code for Normal Template is hosted at
http://github.com/gmosx/normal-template
Normal Template is Copyright (c) 2010 George Moschovitis
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
jsDump
------
jsDump is copyright (c) 2008 Ariel Flesler, aflesler(at)gmail(dot)com
Licensed under BSD http://www.opensource.org/licenses/bsd-license.php
json2xml
--------
json2xml is copyright (c) Stefan Goessner 2006
json2xml is licensed under Creative Commons GNU LGPL License,
http://creativecommons.org/licenses/LGPL/2.1/
http://goessner.net/
http://goessner.net/download/prj/jsonxml/

View File

@ -0,0 +1,23 @@
JSDoc Toolkit Version 3
=======================
This is pre-release software, under active development. It is not intended for
general use.
Usage
-----
$ java -jar jsdoc.jar -d jsdoc.json myscript.js
See
---
Project Wiki: <http://code.google.com/p/jsdoc-toolkit-3/w/list>
License
-------
JSDoc Toolkit Version 3 is copyright 2010
(c) Michael Mathews <micmath@gmail.com>
See file "LICENSE.md" in this distribution for more details about terms of use.

6
about.json Normal file
View File

@ -0,0 +1,6 @@
{
"app": {
"name": "jsdoc-toolkit-3",
"version": "0.0.0+2010-06-06-2109"
}
}

22
build.xml Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<project name="JSDoc Toolkit" default="about">
<property file="build/build.properties" />
<property name="build.templates" location="build/templates" />
<tstamp>
<format property="NOW" pattern="yyyy-MM-dd-HHmm" locale="en,UK"/>
</tstamp>
<target name="about">
<delete file="about.json" quiet="true"/>
<copy file="${build.templates}/about.json" tofile="about.json" >
<filterchain>
<replacetokens>
<token key="app.name" value="${app.name}" />
<token key="app.version" value="${app.version}" />
<token key="timestamp" value="${NOW}" />
</replacetokens>
</filterchain>
</copy>
</target>
</project>

2
build/build.properties Normal file
View File

@ -0,0 +1,2 @@
app.name=jsdoc-toolkit-3
app.version=0.0.0

View File

@ -0,0 +1,6 @@
{
"app": {
"name": "@app.name@",
"version": "@app.version@+@timestamp@"
}
}

25
java/build.xml Normal file
View File

@ -0,0 +1,25 @@
<project default="clean-build">
<target name="clean">
<delete dir="build"/>
</target>
<target name="compile">
<mkdir dir="build/classes"/>
<javac
srcdir="src" destdir="build/classes"
classpath="./classes/js.jar"
/>
</target>
<target name="jar">
<mkdir dir="build/jar"/>
<jar destfile="build/jar/jsdoc.jar" basedir="build/classes">
<manifest>
<attribute name="Main-Class" value="Run"/>
<attribute name="Class-Path" value="./java/classes/js.jar"/>
</manifest>
</jar>
</target>
<target name="clean-build" depends="clean,compile,jar"/>
</project>

BIN
java/classes/js.jar Normal file

Binary file not shown.

56
java/src/Run.java Normal file
View File

@ -0,0 +1,56 @@
/*
OVERVIEW:
A bootstrap tool for running main.js. Assumes main.js is in
the same directory as the run.jar file.
Its duty is simply to add the absolute path for main.js as
the first argument to the main.js script itself. This enables
the script to know it's own directory, useful for accessing
resources via relative filepaths.
AUTHOR: Michael Mathews <micmath@gmail.com>
LICENSE: Apache License 2.0 - See file 'LICENSE.markdown' in this project.
USAGE: java -jar run.jar <args>
*/
import java.io.File;
import java.net.URL;
import java.util.*;
public class Run {
// requires java.io.File, java.net.URL
public static void main(String[] args) throws java.io.IOException {
// get the absolute file path to the jar file containing this class
ClassLoader loader = Run.class.getClassLoader();
// url is like "file:/Users/michael/WorkArea/jsdoc/run.jar!/Run.class"
String jarUrl = loader.getResource("Run.class").getPath();
// parse the filepath out of the URL
String delims = "[:!]";
String[] tokens = jarUrl.split(delims);
String jarPath = tokens[1];
// the base directory, assumed to contain main.js
String jarDir = new File(jarPath).getParent();
String mainPath = jarDir + "/main.js";
// Rhino eats the first arg (the path to the script file it is running)
// so we add it twice: one for Rhino the next for us
String[] mainArgs = {mainPath, mainPath};
String[] allArgs = concat(mainArgs, args);
// main.js will now get arguments like:
// ["/abs/path/to/main.js", "-a", "aval", "-b", "bval"]
org.mozilla.javascript.tools.shell.Main.main(allArgs);
}
// requires java.util
public static String[] concat(String[] a, String[] b) {
List<String> ab = new ArrayList<String>(a.length + b.length);
Collections.addAll(ab, a);
Collections.addAll(ab, b);
return ab.toArray(new String[] {});
}
}

BIN
jsdoc.jar Normal file

Binary file not shown.

49
main.js Normal file
View File

@ -0,0 +1,49 @@
//// bootstrap
function require(id) {
var path = require.base + id + '.js',
source = '';
try {
var file = new java.io.File(path),
scanner = new java.util.Scanner(file).useDelimiter('\Z'),
source = String( scanner.next() );
}
catch (e) { print(e); }
var f = new Function('require', 'exports', 'module', source),
exports = require.cache[path] || {},
module = { id: id, uri: path };
require.cache[path] = exports;
f.call({}, require, exports, module);
return exports;
}
require.base = 'modules/';
require.cache = {};
function print(msg) {
java.lang.System.out.println(msg);
}
const BASE = arguments[0]; // path to application base folder
var args = arguments.slice(1);
////
(function() {
var jsdoc = {
parser: require('jsdoc/parser'),
opts: require('jsdoc/opts'),
src: require('jsdoc/src')
},
opts,
sourceFiles,
fs = require('common/fs');
opts = jsdoc.opts.set(args);
sourceFiles = jsdoc.src.getFilePaths(opts._);
jsdoc.parser.parseFiles(sourceFiles);
print( jsdoc.parser.result.asString(opts.destination) );
})();

144
modules/common/args.js Normal file
View File

@ -0,0 +1,144 @@
/**
@overview Parse command line options.
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.markdown' in this project.
*/
/**
@module common/args
*/
(function() {
/**
Create an instance of the parser.
@constructor
*/
exports.Parser = function() {
this._options = [];
}
exports.Parser.prototype._getOptionByShortName = function(name) {
for (var i = this._options.length; i--;) {
if (this._options[i].shortName === name) { return this._options[i]; }
}
return null;
}
exports.Parser.prototype._getOptionByLongName = function(name) {
for (var i = this._options.length; i--;) {
if (this._options[i].longName === name) { return this._options[i]; }
}
return null;
}
/**
* Provide information about a legal option.
* @method Parser#addOption
* @param shortName
* @param longName
* @param hasValue
* @param helpText
* @example
* myParser.addOption('t', 'template', true, 'The path to the template.');
* myParser.addOption('h', 'help', false, 'Show the help message.');
*/
exports.Parser.prototype.addOption = function(shortName, longName, hasValue, helpText) {
this._options.push({shortName: shortName, longName: longName, hasValue: hasValue, helpText: helpText});
};
/**
Generate a summary of all the options with corresponding help text.
@method Parser#help
@returns {string}
*/
exports.Parser.prototype.help = function() {
var help = 'OPTIONS:\n',
option;
for (var i = this._options.length; i--;) {
option = this._options[i];
if (option.shortName) {
help += '-' + option.shortName + (option.longName? ' or ' : '');
}
if (option.longName) {
help += '--' + option.longName;
}
if (option.hasValue) {
help += ' <value>';
}
help += ' ' + option.helpText + '\n';
}
return help;
};
/**
Get the options.
@method Parser#parse
@param args An array, like ['-x', 'hello']
@param defaults An optional collection of default values.
@returns {Object} The keys will be the longNames, or the shortName if
no longName is defined for that option. The values will be the values
provided, or `true` if the option accepts no value.
*/
exports.Parser.prototype.parse = function(args, defaults) {
var result = defaults || {};
result._ = [];
for (var i = 0, leni = args.length; i < leni; i++) {
var arg = '' + args[i],
next = (i < leni-1)? '' + args[i+1] : null,
option,
shortName,
longName,
name,
value = null;
// like -t
if (arg.charAt(0) === '-') {
// like: --template
if (arg.charAt(1) === '-') {
name = longName = arg.slice(2);
option = this._getOptionByLongName(longName);
}
else {
name = shortName = arg.slice(1);
option = this._getOptionByShortName(shortName);
}
if (option === null) {
throw new Error( 'Unknown command line option found: ' + name );
}
if (option.hasValue) {
value = next;
i++;
if (value === null || value.charAt(0) === '-') {
throw new Error( 'Command line option requires a value: ' + name );
}
}
else {
value = true;
}
if (option.longName && shortName) {
name = option.longName;
}
result[name] = value;
}
else {
result._.push(arg);
}
}
return result;
}
})();

109
modules/common/fs.js Normal file
View File

@ -0,0 +1,109 @@
/**
@overview File system stuff.
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.markdown' in this project.
*/
(function() {
var slash = java.lang.System.getProperty('file.separator') || '/',
File = Packages.java.io.File,
defaultEncoding = java.lang.System.getProperty('file.encoding');
exports.read = function(path, options) {
var options = options || {},
encoding = options.encoding || defaultEncoding;
return readFile(path, encoding);
}
exports.write = function(path, content, options) {
var options = options || {},
encoding = options.encoding || defaultEncoding,
out;
out = new Packages.java.io.PrintWriter(
new Packages.java.io.OutputStreamWriter(
new Packages.java.io.FileOutputStream(path),
encoding
)
);
out.write(content);
out.flush();
out.close();
}
/**
* Check if a file exists.
* @param {string} path The file to check.
* @returns {boolean}
*/
exports.exists = function(path) {
var file = new File(path);
if (file.isDirectory()){
return true;
}
if (!file.exists()){
return false;
}
if (!file.canRead()){
return false;
}
return true;
}
/**
* Get a list of all files in a given directory. Will not include files that
* start with a dot.
* @type string[]
* @param {string} dir The starting directory to look in.
* @param {number} [recurse=1] How many levels deep to scan.
* @returns {string[]} An array of {string} paths to the files in the given directory.
*/
exports.ls = function(dir, recurse, _allFiles, _path) {
var files,
file;
if (typeof _path === 'undefined') { // initially
_allFiles = [];
_path = [dir];
}
if (_path.length === 0) { return _allFiles; }
if (typeof recurse === 'undefined') { recurse = 1; }
dir = new File(dir);
if (!dir.directory) { return [String(dir)]; }
files = dir.list();
for (var f = 0, lenf = files.length; f < lenf; f++) {
file = String(files[f]);
if (file.match(/^\.[^\.\/\\]/)) { continue; } // skip dot files
if ((new File(_path.join(slash) + slash + file)).list()) { // it's a directory
_path.push(file);
if (_path.length - 1 < recurse) {
exports.ls(_path.join(slash), recurse, _allFiles, _path);
}
_path.pop();
}
else { // it's a file
_allFiles.push(
fixSlash( (_path.join(slash) + slash + file) )
);
}
}
return _allFiles;
}
// fix multiple slashes, like one//two
function fixSlash(path) {
return path.replace(/[\/\\]+/g, slash);
}
})();

168
modules/flesler/jsdump.js Normal file
View File

@ -0,0 +1,168 @@
// Ported by Tom Robinson
/**
* jsDump
* Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
* Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
* Date: 5/15/2008
* @projectDescription Advanced and extensible data dumping for Javascript.
* @version 1.0.0
* @author Ariel Flesler
*/
var jsDump;
(function(){
function quote( str ){
return '"' + str.toString().replace(/"/g, '\\"') + '"';
};
function literal( o ){
return o + '';
};
function join( pre, arr, post ){
var s = jsDump.separator(),
base = jsDump.indent(),
inner = jsDump.indent(1);
if( arr.join )
arr = arr.join( ',' + s + inner );
if( !arr )
return pre + post;
return [ pre, inner + arr, base + post ].join(s);
};
function array( arr ){
var i = arr.length, ret = Array(i);
this.up();
while( i-- )
ret[i] = this.parse( arr[i] );
this.down();
return join( '[', ret, ']' );
};
var reName = /^function (\w+)/;
jsDump = {
parse:function( obj, type ){//type is used mostly internally, you can fix a (custom)type in advance
var parser = this.parsers[ type || this.typeOf(obj) ];
type = typeof parser;
return type == 'function' ? parser.call( this, obj ) :
type == 'string' ? parser :
this.parsers.error;
},
typeOf:function( obj ){
var type = typeof obj,
f = 'function';//we'll use it 3 times, save it
return type != 'object' && type != f ? type :
!obj ? 'null' :
obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions
obj.getHours ? 'date' :
obj.scrollBy ? 'window' :
obj.nodeName == '#document' ? 'document' :
obj.nodeName ? 'node' :
obj.item ? 'nodelist' : // Safari reports nodelists as functions
obj.callee ? 'arguments' :
obj.call || obj.constructor != Array && //an array would also fall on this hack
(obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects
'length' in obj ? 'array' :
type;
},
separator:function(){
return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
},
indent:function( extra ){// extra can be a number, shortcut for increasing-calling-decreasing
if( !this.multiline )
return '';
var chr = this.indentChar;
if( this.HTML )
chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
return Array( this._depth_ + (extra||0) ).join(chr);
},
up:function( a ){
this._depth_ += a || 1;
},
down:function( a ){
this._depth_ -= a || 1;
},
setParser:function( name, parser ){
this.parsers[name] = parser;
},
// The next 3 are exposed so you can use them
quote:quote,
literal:literal,
join:join,
//
_depth_: 1,
// This is the list of parsers, to modify them, use jsDump.setParser
parsers:{
window: '[Window]',
document: '[Document]',
error:'[ERROR]', //when no parser is found, shouldn't happen
unknown: '[Unknown]',
'null':'null',
undefined:'undefined',
'function':function( fn ){
var ret = 'function',
name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
if( name )
ret += ' ' + name;
ret += '(';
ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
return join( ret, this.parse(fn,'functionCode'), '}' );
},
array: array,
nodelist: array,
arguments: array,
object:function( map ){
var ret = [ ];
this.up();
for( var key in map )
ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
this.down();
return join( '{', ret, '}' );
},
node:function( node ){
var open = this.HTML ? '&lt;' : '<',
close = this.HTML ? '&gt;' : '>';
var tag = node.nodeName.toLowerCase(),
ret = open + tag;
for( var a in this.DOMAttrs ){
var val = node[this.DOMAttrs[a]];
if( val )
ret += ' ' + a + '=' + this.parse( val, 'attribute' );
}
return ret + close + open + '/' + tag + close;
},
functionArgs:function( fn ){//function calls it internally, it's the arguments part of the function
var l = fn.length;
if( !l ) return '';
var args = Array(l);
while( l-- )
args[l] = String.fromCharCode(97+l);//97 is 'a'
return ' ' + args.join(', ') + ' ';
},
key:quote, //object calls it internally, the key part of an item in a map
functionCode:'[code]', //function calls it internally, it's the content of the function
attribute:quote, //onode calls it internally, it's an html attribute value
string:quote,
date:quote,
regexp:literal, //regex
number:literal,
'boolean':literal
},
DOMAttrs:{//attributes to dump from nodes, name=>realName
id:'id',
name:'name',
'class':'className'
},
HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
indentChar:' ',//indentation unit
multiline:true //if true, items in a collection, are separated by a \n, else just a space.
};
})();
exports.jsDump = jsDump;

View File

@ -0,0 +1,56 @@
/* This work is licensed under Creative Commons GNU LGPL License.
License: http://creativecommons.org/licenses/LGPL/2.1/
Version: 0.9/modified to conform to commonjs modules pattern
Author: Stefan Goessner/2006
Web: http://goessner.net/
*/
var json2xml = (typeof exports === 'undefined')? {} : exports; // like commonjs
(function() {
json2xml.convert = function(o) {
var toXml = function(v, name, ind) {
var xml = "";
if (v instanceof Array) {
for (var i=0, n=v.length; i<n; i++)
xml += toXml(v[i], name, ind+"");
}
else if (typeof(v) == "object") {
var hasChild = false;
xml += ind + "<" + name;
for (var m in v) {
if (m.charAt(0) == "@")
xml += " " + m.substr(1) + "=\"" + v[m].toString() + "\"";
else
hasChild = true;
}
xml += hasChild ? ">\n" : "/>";
if (hasChild) {
for (var m in v) {
if (m == "#text")
xml += v[m];
else if (m == "#cdata")
xml += "<![CDATA[" + v[m] + "]]>";
else if (m.charAt(0) != "@")
xml += toXml(v[m], m, ind+"\t");
}
xml += (xml.charAt(xml.length-1)=="\n"?ind:"") + "</" + name + ">\n";
}
}
else {
xml += ind + "<" + name + ">" + v.toString().replace(/</g, '&lt;') + "</" + name + ">\n";
}
return xml;
},
xml="";
for (var m in o) {
xml += toXml(o[m], m, "");
}
return xml;
}
})();

346
modules/jsdoc/doclet.js Normal file
View File

@ -0,0 +1,346 @@
/**
@overview
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.markdown' in this project.
*/
/**
Functionality relating to jsdoc comments and their tags.
@module jsdoc/doclet
*/
(function() {
var name = require('jsdoc/name'),
tag = require('jsdoc/tag');
/**
Factory that builds a Doclet object.
@param {string} commentSrc
@param {ASTNode} node
@param {string} sourceName
@returns {Doclet}
*/
exports.makeDoclet = function(commentSrc, node, sourceName) {
var tags = [],
doclet;
commentSrc = unwrapComment(commentSrc);
commentSrc = fixDesc(commentSrc);
tags = parseTags(commentSrc);
preprocess(tags);
doclet = new Doclet(tags);
postprocess(doclet);
name.resolve(doclet);
doclet.meta = {
line: node.getLineno(),
file: sourceName
};
return doclet
}
/**
@private
@constructor Doclet
@param {Array.<Object>} tags
*/
function Doclet(tags) {
/**
An array of Objects representing tags.
@type Array.<Tag>
@property Doclet#tags
*/
this.tags = tags;
}
/**
Set the name of the Doclet.
@method Doclet#setName
@param {string name
*/
Doclet.prototype.setName = function(nameToSet) {
this.tagText('name', nameToSet);
nameToSet = name.resolve(this);
}
/**
Return the text of the first tag with the given name.
@method Doclet#tagText
@param {String} tagName
@returns {String} The text of the found tag.
*/
Doclet.prototype.tagText = function(tagName, text) {
var i = this.tags.length;
while(i--) {
if (this.tags[i].name === tagName) {
if (text) { this.tags[i].text = text; }
return this.tags[i].text;
}
}
// still here?
if (text) {
this.tags.push( tag.fromTagText(tagName + ' ' + text) );
return text;
}
return '';
}
/**
Does a tag with the given name exist in this doclet?
@method Doclet#hasTag
@param {String} tagName
@returns {boolean} True if the tag is found, false otherwise.
*/
Doclet.prototype.hasTag = function(tagName) {
var i = this.tags.length;
while(i--) {
if (this.tags[i].name === tagName) {
return true;
}
}
return false;
}
// safe to export to JSON
var exportTags = ['name', 'path', 'kind', 'desc', 'type', 'param', 'returns', 'exports', 'requires', 'memberof', 'access', 'attribute'];
/**
Get a JSON-compatible object representing this Doclet.
@method Doclet#toObject
@returns {Object}
*/
Doclet.prototype.toObject = function() {
var tag, tagName, tagValue,
o = {};
for (var i = 0, leni = this.tags.length; i < leni; i++) {
if (exportTags.indexOf(this.tags[i].name) === -1) { continue; }
tag = this.tags[i];
tagName = tag.name;
tagValue = {};
if (tag.type) {
tagValue.type = tag.type;
// not a long tag
if (!tag.pname && tag.text) { tagValue.text = tag.text; }
}
// a long tag
if (tag.pname) { tagValue.name = tag.pname; }
if (tag.pdesc) { tagValue.desc = tag.pdesc; }
// tag value is not an object, it's just a simple string
if (!tag.pname && !tag.type) { tagValue = tag.text; }
if (!o[tagName]) { o[tagName] = tagValue; }
else if (o[tagName].push) { o[tagName].push(tagValue); }
else {
o[tagName] = [ o[tagName] ];
o[tagName].push(tagValue);
}
o.meta = this.meta;
}
return o;
}
/**
Remove JsDoc comment slash-stars. Trims white space.
@private
@function unwrapComment
@param {string} commentSrc
@return {string} Coment wit stars and slashes removed.
*/
function unwrapComment(commentSrc) {
if (!commentSrc) { return ''; }
// TODO keep leading white space for @examples
return commentSrc ? commentSrc.replace(/(^\/\*\*+\s*|\s*\**\*\/$)/g, "").replace(/^\s*\* ?/gm, "") : "";
}
/**
Add a @desc tag if none exists on untagged text at start of comment.
@private
@function fixDesc
@param {string} commentSrc
@return {string} With needed @desc tag added.
*/
function fixDesc(commentSrc) {
if (!/^\s*@/.test(commentSrc)) {
commentSrc = '@desc ' + commentSrc;
}
return commentSrc;
}
/**
Given the source of a jsdoc comment, finds the tags.
@private
@function parseTags
@param {string} commentSrc Unwrapped.
@returns Array.<Object>
*/
function parseTags(commentSrc) {
var tags = [];
// split out the basic tags
commentSrc
.split(/(^|[\r\n])\s*@/)
.filter( function($){ return $.match(/\S/); } )
.forEach(function($) {
var newTag = tag.fromTagText($);
if (newTag.name) {
tags.push(newTag);
}
});
return tags;
}
// other tags that can provide the memberof
var memberofs = {methodof: 'method', propertyof: 'property', eventof: 'event'};
// other tags that can provide the symbol name
var nameables = ['constructor', 'module', 'event', 'namespace', 'method', 'property', 'function', 'variable', 'enum'];
/**
Expand some shortcut tags. Modifies the tags argument in-place.
@private
@method preprocess
@param {Array.<Object>} tags
@returns undefined
*/
function preprocess(tags) {
var name = '',
taggedName = '',
kind = '',
taggedKind = '',
memberof = '',
taggedMemberof = '';
var i = tags.length;
while(i--) {
if (tags[i].name === 'private') {
tags[tags.length] = tag.fromTagText('access private');
}
else if (tags[i].name === 'protected') {
tags[tags.length] = tag.fromTagText('access protected');
}
else if (tags[i].name === 'const') {
tags[tags.length] = tag.fromTagText('attribute constant');
}
else if (tags[i].name === 'readonly') {
tags[tags.length] = tag.fromTagText('attribute readonly');
}
else if (tags[i].name === 'name') {
if (name && name !== tags[i].text) { tooManyNames(name, tags[i].text); }
taggedName = name = tags[i].text;
}
else if (tags[i].name === 'kind') {
if (kind && kind !== tags[i].text) { tooManyKinds(kind, tags[i].text); }
taggedKind = kind = tags[i].text;
}
else if (tags[i].name === 'memberof') {
if (memberof) { tooManyTags('memberof'); }
taggedMemberof = memberof = tags[i].text;
}
if ( nameables.indexOf(tags[i].name) > -1 ) {
if (tags[i].text) {
if (name && name !== tags[i].text) { tooManyNames(name, tags[i].text); }
name = tags[i].text;
}
if (tags[i].type) {
tags[tags.length] = tag.fromTagText('type ' + tags[i].type);
}
if (kind && kind !== tags[i].name) { tooManyKinds(kind, tags[i].name); }
kind = tags[i].name;
}
if ( memberofs.hasOwnProperty(tags[i].name) ) {
if (tags[i].text) {
if (memberof) { tooManyTags(tags[i].name); }
memberof = tags[i].text;
}
if (kind && kind !== memberofs[tags[i].name]) { tooManyKinds(kind, memberofs[tags[i].name]); }
kind = memberofs[tags[i].name];
}
}
if (name && !taggedName) {
tags[tags.length] = tag.fromTagText('name ' + name);
}
if (kind && !taggedKind) {
tags[tags.length] = tag.fromTagText('kind ' + kind);
}
if (memberof && !taggedMemberof) {
tags[tags.length] = tag.fromTagText('memberof ' + memberof);
}
}
function postprocess(doclet) {
if ( doclet.hasTag('class') && !doclet.hasTag('constructor') ) {
doclet.tags[doclet.tags.length] = tag.fromTagText('kind constructor');
}
if ( doclet.hasTag('enum')) {
if (!doclet.hasTag('type')) {
doclet.tags[doclet.tags.length] = tag.fromTagText('type number');
}
if (!doclet.hasTag('readonly') && !doclet.hasTag('const')) {
doclet.tags[doclet.tags.length] = tag.fromTagText('attribute constant');
}
}
if ( doclet.hasTag('const')) {
if (!doclet.hasTag('kind')) {
doclet.tags[doclet.tags.length] = tag.fromTagText('kind property');
}
if (!doclet.hasTag('readonly') && !doclet.hasTag('const')) {
doclet.tags[doclet.tags.length] = tag.fromTagText('attribute constant');
}
}
}
/**
Throw error when two conflicting names are defined in the same doc.
@private
@function tooManyNames
*/
function tooManyNames(name1, name2) {
throw new Error('Conflicting names in documentation: '+name1+', '+name2);
}
/**
Throw error when two conflicting kinds are defined in the same doc.
@private
@function tooManyKinds
*/
function tooManyKinds(kind1, kind2) {
throw new Error('Conflicting kinds in documentation: '+kind1+', '+kind2);
}
/**
Throw error when conflicting tags are found.
@private
@function tooManyTags
*/
function tooManyTags(tagName) {
throw new Error('Symbol has too many tags of type: @'+tagName);
}
})();

61
modules/jsdoc/docset.js Normal file
View File

@ -0,0 +1,61 @@
(function() {
var dumper = require('flesler/jsdump'),
xml = require('goessner/json2xml'),
doclets = exports.doclets = [];
doclets.getDocsByName = function getDocsByName(docName) {
var foundDocs = [],
i = doclets.length;
while (i--) {
if (doclets[i].tagText('path') === docName) {
foundDocs.unshift( doclets[i] );
}
}
return foundDocs;
}
doclets.toObject = function toObject() {
var docsObjects = [],
i = doclets.length;
while (i--) {
docsObjects.unshift( doclets[i].toObject() );
}
return { doc: docsObjects };
}
doclets.asString = function asString(destinationName) {
if ( /xml$/i.test(destinationName) ) {
return doclets.toXML();
}
else { // default
return doclets.toJSON();
}
}
doclets.toJSON = function toJSON() {
return dumper.jsDump.parse( doclets.toObject() );
}
doclets.toXML = function toXML() {
var o = doclets.toObject();
// make `id` an attribute of the doc tag
for (var i = 0, leni = o.doc.length; i < leni; i++) {
for (var p in o.doc[i]) {
if (p === 'id') {
o.doc[i]['@id'] = o.doc[i].id;
delete o.doc[i].id;
}
}
}
return xml.convert(
{ jsdoc: o }
);
}
})();

163
modules/jsdoc/name.js Normal file
View File

@ -0,0 +1,163 @@
/**
@overview
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.markdown' in this project.
*/
/**
Functionality relating to symbol name manipulation.
@module jsdoc/name
*/
(function() {
var Token = Packages.org.mozilla.javascript.Token,
currentModule = '';
exports.setCurrentModule = function(moduleName) {
currentModule = moduleName;
}
/**
@method resolve
@param {Doclet} doclet
*/
exports.resolve = function(doclet) {
var kind = doclet.tagText('kind'),
name = doclet.tagText('name'),
memberof = doclet.tagText('memberof'),
path,
shortname,
prefix,
supportedNamespaces = ['module', 'event'];
// only keep the first word of the tagged name
name = doclet.tagText('name', name.split(/\s+/g)[0]);
if (currentModule) {
name = name.replace(/^exports\.(?=.+$)/, currentModule + '.');
}
name = name.replace(/\.prototype\.?/g, '#');
// if name doesn't already have a doc-namespace and needs one
if (!/^[a-z_$-]+:\S+/i.test(name) && supportedNamespaces.indexOf(kind) > -1) {
// add doc-namespace to path
name = kind + '(' + name + ')';
}
path = shortname = name;
doclet.tagText('name', shortname);
if (memberof) {
if (name.indexOf(memberof) === 0) {
path = name;
[prefix, shortname] = exports.shorten(name);
doclet.tagText('name', shortname);
}
}
else {
[prefix, shortname] = exports.shorten(name);
doclet.tagText('memberof', prefix);
doclet.tagText('name', shortname);
}
// overlapping member of, like @name foo.Bar, @memberof foo
if (memberof && name.indexOf(memberof) !== 0) {
path = memberof + (/#$/.test(memberof)? '' : '.') + name;
}
if (path) doclet.tagText('path', path);
return path;
}
exports.shorten = function(path) {
var shortname = path.split(/([#.-])/).pop(),
splitOn = RegExp.$1,
splitAt = path.lastIndexOf(splitOn),
prefix = (splitAt === -1)? '' : path.slice(0, splitAt);
if (splitOn === '#') { prefix = prefix + splitOn; }
return [prefix, shortname];
}
/**
Resolve how to document the `this.` portion of a symbol name.
*/
exports.resolveThis = function(name, node, doclet) {
var enclosing,
enclosingDoc,
memberof = (doclet.tagText('memberof') || '').replace(/\.prototype\.?/g, '#');
if (node.parent && node.parent.type === Token.OBJECTLIT) {
if (enclosing = node.parent) {
enclosingDoc = exports.docFromNode(enclosing) || {};
memberof = (enclosingDoc.tagText('path') || '').replace(/\.prototype\.?/g, '#');
if (!memberof) {
memberof = enclosingDoc.path;
memberof = memberof || '[[anonymousObject]]';
}
if (memberof) {
name = memberof + (memberof[memberof.length-1] === '#'?'':'.') + name;
}
}
}
else if (name.indexOf('this.') === 0) { // assume `this` refers to innermost constructor
if (!memberof || memberof === 'this') {
enclosing = node.getEnclosingFunction()
enclosingDoc = exports.docFromNode(enclosing);
memberof = enclosingDoc? enclosingDoc.tagText('path') : '';
if (enclosing && !memberof) {
memberof = ''; //[[anonymousFunction]]
name = name.slice(5); // remove `this.`
}
else if (!enclosing) {
memberof = ''; // [[globalObject]]
}
if (memberof || !enclosing) {
// `this` refers to nearest instance in the name path
if (enclosingDoc && enclosingDoc.tagText('kind') !== 'constructor') {
var parts = memberof.split('#');
parts.pop();
memberof = parts.join('#');
}
name = memberof + (memberof? '#':'') + name.slice(5); // replace `this.` with memberof
}
}
else {
name = name.slice(5);
}
}
return name;
}
/**
Keep track of anonymous functions that have been assigned to documented symbols.
@private
@method docFromNode
@param {org.mozilla.javascript.ast.AstNode} node
@return {Object} The associated doclet.
*/
exports.docFromNode = function(node) {
var i = exports.refs.length;
while (i--) {
if (exports.refs[i][0] === node) {
return exports.refs[i][1];
}
}
return null;
}
// tuples, like [ [noderef, doclet], [noderef, doclet] ]
exports.refs = [];
function getTypeName(node) {
return node ? ''+org.mozilla.javascript.Token.typeToName(node.getType()) : '' ;
}
})();

66
modules/jsdoc/opts.js Normal file
View File

@ -0,0 +1,66 @@
/**
@overview Get or set options for this app.
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.markdown' in this project.
*/
/**
@module jsdoc/opts
@requires common/args
*/
(function() {
var args = args || require('common/args');
var argsParser = new args.Parser(),
ourOptions,
defaults = {
template: 'default',
destination: 'jsdoc.xml'
};
argsParser.addOption('t', 'template', true, 'The name of the template to use.');
argsParser.addOption('T', 'test', false, 'Run unit tests and quit.');
argsParser.addOption('d', 'destination', true, 'The path to output folder.');
argsParser.addOption('h', 'help', false, 'Print help message and quit.');
argsParser.addOption('V', 'validate', false, 'Validate the results produced by parsing the source code.');
/**
Set the options for this app.
@method set
@throws {Error} Illegal arguments will throw errors.
@param {string|String[]} args The command line arguments for this app.
*/
exports.set = function(args) {
args = args || [];
if (typeof args === 'string' || args.constructor === String) {
args = (''+args).split(/\s+/g);
}
ourOptions = argsParser.parse(args, defaults);
return ourOptions;
}
/**
Display help message for options.
@method help
*/
exports.help = function() { return argsParser.help(); }
/**
Get a single option or all the options for this app.
@method get
@param {String} [name] The name of the option.
@return {String|Object} Either the value associated with the given name,
or a collection of key/values representing all the options.
*/
exports.get = function(name) {
if (typeof name === 'undefined') {
return ourOptions;
}
else {
return ourOptions[name];
}
}
})();

203
modules/jsdoc/parser.js Normal file
View File

@ -0,0 +1,203 @@
(function() {
var name = require('jsdoc/name'),
doclet = require('jsdoc/doclet'),
doclets = require('jsdoc/docset').doclets,
Token = Packages.org.mozilla.javascript.Token;
exports.result = doclets;
/**
*/
function visitNode(node) {
var commentSrc = '',
thisDoclet = null,
thisDocletName = '';
// look for all comments that have names provided
if (node.type === Token.SCRIPT && node.comments) {
for each (var comment in node.comments.toArray()) {
if (comment.commentType === Token.CommentType.JSDOC) {
commentSrc = '' + comment.toSource();
if (commentSrc) {
thisDoclet = doclet.makeDoclet(commentSrc, node, currentSourceName);
if ( thisDoclet.hasTag('name') ) {
doclets.push(thisDoclet);
if (thisDoclet.tagText('kind') === 'module') {
name.setCurrentModule( thisDoclet.tagText('path') );
}
}
}
}
}
}
// like function foo() {}
if (node.type == Token.FUNCTION) {
if (node.jsDoc) {
commentSrc = '' + node.jsDoc;
if (commentSrc) {
thisDoclet = doclet.makeDoclet(commentSrc, node, currentSourceName);
thisDocletName = thisDoclet.tagText('path');
if (!thisDocletName) {
thisDoclet.setName('' + node.name);
doclets.push(thisDoclet);
}
name.refs.push([node, thisDoclet]);
}
}
return true;
}
// like foo = function(){} or foo: function(){}
if (node.type === Token.ASSIGN || node.type === Token.COLON) {
var nodeName = nodeToString(node.left),
nodeKind = '';
commentSrc = node.jsDoc || node.left.jsDoc;
if (commentSrc) {
commentSrc = '' + commentSrc;
thisDoclet = doclet.makeDoclet(commentSrc, node, currentSourceName);
thisDocletName = thisDoclet.tagText('name');
nodeKind = thisDoclet.tagText('kind');
if (!thisDocletName) {
nodeName = name.resolveThis( nodeName, node, thisDoclet );
thisDoclet.setName(nodeName);
doclets.push(thisDoclet);
}
name.refs.push([node.right, thisDoclet]);
}
return true;
}
// like var foo = function(){} or var bar = {}
if (node.type == Token.VAR || node.type == Token.LET || node.type == Token.CONST) {
var counter = 0,
nodeKind;
if (node.variables) for each (var n in node.variables.toArray()) {
if (n.target.type === Token.NAME && n.initializer) {
commentSrc = (counter++ === 0 && !n.jsDoc)? node.jsDoc : n.jsDoc;
if (commentSrc) {
thisDoclet = doclet.makeDoclet('' + commentSrc, node, currentSourceName);
thisDocletName = thisDoclet.tagText('path');
nodeKind = thisDoclet.tagText('kind');
if ( !thisDocletName ) {
thisDocletName = n.target.string;
thisDoclet.setName(thisDocletName);
doclets.push(thisDoclet);
}
}
}
name.refs.push([n.initializer, thisDoclet]);
}
return true;
}
return true;
}
currentSourceName = '';
/**
*/
exports.parseSource = function(source, sourceName) {
currentSourceName = sourceName;
var ast = getParser().parse(source, sourceName, 1);
ast.visit(
new Packages.org.mozilla.javascript.ast.NodeVisitor({
visit: visitNode
})
);
currentSourceName = '';
}
/**
*/
exports.parseFiles = function(sourceFiles) {
var ast = getParser(),
fs = require('common/fs'),
source = '';
for (i = 0, leni = sourceFiles.length; i < leni; i++) {
try {
source = fs.read(sourceFiles[i]);
}
catch(e) {
print('ERROR: ' + e);
continue;
}
exports.parseSource(source, sourceFiles[i]);
}
}
/**
@private
@function getParser
*/
function getParser() {
var cx = Packages.org.mozilla.javascript.Context.getCurrentContext();
var ce = new Packages.org.mozilla.javascript.CompilerEnvirons();
ce.setRecordingComments(true);
ce.setRecordingLocalJsDocComments(true);
ce.initFromContext(cx);
return new Packages.org.mozilla.javascript.Parser(ce, ce.getErrorReporter());
}
/**
@private
@function nodeToString
@param {org.mozilla.javascript.ast.AstNode} node
@returns {string}
*/
// credit: ringojs ninjas
function nodeToString(node) {
var str;
if (node.type === Token.GETPROP) {
str = [nodeToString(node.target), node.property.string].join('.');
}
else if (node.type === Token.NAME) {
str = node.string;
}
else if (node.type === Token.STRING) {
str = node.value;
}
else if (node.type === Token.THIS) {
str = 'this';
}
else if (node.type === Token.GETELEM) {
str = node.toSource(); // like: Foo['Bar']
}
else {
str = getTypeName(node);
}
return '' + str;
};
/**
@private
@function getTypeName
@param {org.mozilla.javascript.ast.AstNode} node
@returns {string}
*/
// credit: ringojs ninjas
function getTypeName(node) {
return node ? ''+Packages.org.mozilla.javascript.Token.typeToName(node.getType()) : '' ;
}
})();

42
modules/jsdoc/src.js Normal file
View File

@ -0,0 +1,42 @@
/**
@overview Find source files to be parsed for docs.
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.markdown' in this project.
*/
/**
@module jsdoc/src
@namespace jsdoc.src
@requires common/fs
*/
var jsdoc = jsdoc || {};
jsdoc.src = (typeof exports === 'undefined')? {} : exports; // like commonjs
(function() {
var fs = fs || require('common/fs');
/**
Recursively searches the given searchPaths for js files.
@method getFilePaths
@param {Array.<string>} searchPaths
@param {number} [depth=1]
*/
jsdoc.src.getFilePaths = function(searchPaths, depth) {
var filePaths = [];
searchPaths = searchPaths || [];
depth = depth || 1;
searchPaths.forEach(function($) {
filePaths = filePaths.concat(fs.ls($, depth));
});
// TODO: allow user-defined filtering of files
filePaths = filePaths.filter(function($) {
return /.+\.js(doc)?$/i.test($);
});
return filePaths;
}
})();

133
modules/jsdoc/tag.js Normal file
View File

@ -0,0 +1,133 @@
/**
@overview
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.markdown' in this project.
*/
/**
Create tag objects.
@module jsdoc/tag
*/
(function() {
exports.fromCommentText = function(commentText) {
var tag,
tags = [];
// split out the basic tags
commentText
.split(/(^|[\r\n])\s*@/)
.filter( function($){ return $.match(/\S/); } )
.forEach(function($) {
tag = fromTagText($);
if (tag.name) {
tags.push(tag);
}
else {
// TODO: warn about tag with no name?
}
});
return tags;
}
exports.fromTagText = function(tagText) {
return new Tag(tagText);
}
/**
@private
@constructor Tag
@param {string} tagText
*/
function Tag(tagText) {
this.name = '';
this.type = '';
this.text = '';
this.pname = '';
this.pdesc = '';
// tagText is like: "tagname tag text"
var bits = tagText.match(/^(\S+)(?:\s+([\s\S]*))?$/);
if (bits) {
this.name = (bits[1] || '').toLowerCase();
this.text = bits[2] || '';
var typeText = splitType(this.text);
// @type tags are the only tag that is not allowed to have a {type}!
if (this.name === 'type') {
typeText.text = typeText.text || typeText.type;
delete typeText.type;
}
this.type = typeText.type;
this.text = trim(typeText.text);
if (this.name === 'param') { // is a parameter w/ long format
var [pname, pdesc] = splitPname(this.text);
this.pname = pname;
this.pdesc = pdesc;
}
}
}
/**
Split the parameter name and parameter desc from the tag text.
@private
@method splitPname
@param {string} tagText
@returns Array.<string> The pname and the pdesc.
*/
function splitPname(tagText) {
tagText.match(/^(\S+)(\s+(\S.*))?$/);
return [RegExp.$1, RegExp.$3];
}
/**
Split the tag type and remaining tag text from the tag text.
@private
@method splitType
@param {string} tagText
@returns Object Like {type: tagType, text: tagText}
*/
function splitType(tagText) {
var type = '',
text = tagText,
count = 0;
// I reserve the right to use {@whatever ...} for something unrelated to type
if (tagText[0] === '{' && tagText[1] !== '@') {
count++;
for (var i = 1, leni = tagText.length; i < leni; i++) {
if (tagText[i] === '{') { count++; }
if (tagText[i] === '}') { count--; }
if (count === 0) {
type = trim(tagText.slice(1, i));
text = trim(tagText.slice(i+1));
break;
}
}
}
return { type: type, text: text };
}
/**
Remove leading and trailing whitespace.
@private
@method trim
@param {string} text
@returns {string}
*/
function trim(text) {
return text.replace(/^\s+|\s+$/g, '');
}
})();