/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Norris Boyd
* Roland Pennings
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* Process a JavaScript source file and process special comments
* to produce an HTML file of documentation, similar to javadoc.
* @author Norris Boyd
* @see rhinotip.jar
* @lastmodified xx
* @version 1.2 Roland Pennings: Allow multiple files for a function.
* @version 1.3 Roland Pennings: Removes ../.. from the input directory name
*/
defineClass("File")
var functionDocArray = [];
var inputDirName = "";
var indexFileArray = [];
var indexFile = "";
var indexFileName = "index_files";
var indexFunctionArray = [];
var indexFunction = "";
var indexFunctionName = "index_functions";
var FileList = [];
var DirList = [];
var outputdir = null;
var debug = 0;
/**
* Process JavaScript source file <code>f</code>, writing jsdoc to
* file <code>out</code>.
* @param f input file
* @param fname name of the input file (without the path)
* @param inputdir directory of the input file
* @param out output file
*/
function processFile(f, fname, inputdir, out) {
var s;
var firstLine = true;
indexFileArray[fname] = "";
// write the header of the output file
out.writeLine('<HTML><HEADER><TITLE>' + fname + '</TITLE><BODY>');
if (inputdir != null) {
outstr = '<a name=\"_top_\"></a><pre><a href=\"' + indexFile + '\">Index Files</a> ';
outstr += '<a href=\"' + indexFunction + '\">Index Functions</a></pre><hr>';
out.writeLine(outstr);
}
// process the input file
var comment = "";
while ((s = f.readLine()) != null) {
var m = s.match(/\/\*\*(.*)/);
if (m != null) {
// Found a comment start.
s = "*" + m[1];
do {
m = s.match(/(.*)\*\//);
if (m != null) {
// Found end of comment.
comment += m[1];
break;
}
// Strip leading whitespace and "*".
comment += s.replace(/^\s*\*/, "");
s = f.readLine();
} while (s != null);
if (debug)
print("Found comment " + comment);
if (firstLine) {
// We have a comment for the whole file.
out.writeLine('<H1>File ' + fname + '</H1>');
out.writeLine(processComment(comment,firstLine,fname));
out.writeLine('<HR>');
firstLine = false;
comment = "";
continue;
}
}
// match the beginning of the function
// NB we also match functions without a comment!
// if we have two comments one after another only the last one will be taken
m = s.match(/^\s*function\s+((\w+)|(\w+)(\s+))\(([^)]*)\)/);
if (m != null)
{
// Found a function start
var htmlText = processFunction(m[1], m[5], comment); // sjm changed from 2nd to 5th arg
// Save the text in a global variable, so we
// can write out a table of contents first.
functionDocArray[functionDocArray.length] = {name:m[1], text:htmlText};
// Store the function also in the indexFunctionArray
// so we can have a separate file with the function table of contents
if (indexFunctionArray[m[1]]) {
// print("ERROR: function: " + m[1] + " is defined more than once!");
// Allow multiple files for a function
with (indexFunctionArray[m[1]]) {
filename = filename + "|" + fname;
// print("filename = " + filename);
}
}
else {
indexFunctionArray[m[1]] = {filename:fname};
}
//reset comment
comment = "";
}
// match a method being bound to a prototype
m = s.match(/^\s*(\w*)\.prototype\.(\w*)\s*=\s*function\s*\(([^)]*)\)/);
if (m != null)
{
// Found a method being bound to a prototype.
var htmlText = processPrototypeMethod(m[1], m[2], m[3], comment);
// Save the text in a global variable, so we
// can write out a table of contents first.
functionDocArray[functionDocArray.length] = {name:m[1]+".prototype."+m[2], text:htmlText};
// Store the function also in the indexFunctionArray
// so we can have a separate file with the function table of contents
if (indexFunctionArray[m[1]]) {
// print("ERROR: function: " + m[1] + " is defined more than once!");
// Allow multiple files for a function
with (indexFunctionArray[m[1]]) {
filename = filename + "|" + fname;
// print("filename = " + filename);
}
}
else {
indexFunctionArray[m[1]] = {filename:fname};
}
//reset comment
comment = "";
}
firstLine = false;
}
// Write table of contents.
for (var i=0; i < functionDocArray.length; i++) {
with (functionDocArray[i]) {
out.writeLine('function <A HREF=#' + name +
'>' + name + '</A><BR>');
}
}
out.writeLine('<HR>');
// Now write the saved function documentation.
for (i=0; i < functionDocArray.length; i++) {
with (functionDocArray[i]) {
out.writeLine('<A NAME=' + name + '>');
out.writeLine(text);
}
}
out.writeLine('</BODY></HTML>');
// Now clean up the doc array
functionDocArray = [];
}
/**
* Process function and associated comment.
* @param name the name of the function
* @param args the args of the function as a single string
* @param comment the text of the comment
* @return a string for the HTML text of the documentation
*/
function processFunction(name, args, comment) {
if (debug)
print("Processing " + name + " " + args + " " + comment);
return "<H2>Function " + name + "</H2>" +
"<PRE>" +
"function " + name + "(" + args + ")" +
"</PRE>" +
processComment(comment,0,name) +
"<P><BR><BR>";
}
/**
* Process a method being bound to a prototype.
* @param proto the name of the prototype
* @param name the name of the function
* @param args the args of the function as a single string
* @param comment the text of the comment
* @return a string for the HTML text of the documentation
*/
function processPrototypeMethod(proto, name, args, comment) {
if (debug)
print("Processing " + proto + ".prototype." + name + " " + args + " " + comment);
return "<H2> Method " + proto + ".prototype." + name + "</H2>" +
"<PRE>" +
proto + ".prototype." + name + " = function(" + args + ")" +
"</PRE>" +
processComment(comment,0,name) +
"<P><BR><BR>";
}
/**
* Process comment.
* @param comment the text of the comment
* @param firstLine shows if comment is at the beginning of the file
* @param fname name of the file (without path)
* @return a string for the HTML text of the documentation
*/
function processComment(comment,firstLine,fname) {
var tags = {};
// Use the "lambda" form of regular expression replace,
// where the replacement object is a function rather
// than a string. The function is called with the
// matched text and any parenthetical matches as
// arguments, and the result of the function used as the
// replacement text.
// Here we use the function to build up the "tags" object,
// which has a property for each "@" tag that is the name
// of the tag, and whose value is an array of the
// text following that tag.
comment = comment.replace(/@(\w+)\s+([^@]*)/g,
function (s, name, text) {
var a = tags[name] || [];
a.push(text);
tags[name] = a;
return "";
});
// if we have a comment at the beginning of a file
// store the comment for the index file
if (firstLine) {
indexFileArray[fname] = comment;
}
var out = comment + '<P>';
if (tags["param"]) {
// Create a table of parameters and their descriptions.
var array = tags["param"];
var params = "";
for (var i=0; i < array.length; i++) {
var m = array[i].match(/(\w+)\s+(.*)/);
params += '<TR><TD><I>'+m[1]+'</I></TD>' +
'<TD>'+m[2]+'</TD></TR>';
}
out += '<TABLE WIDTH="90%" BORDER=1>';
out += '<TR BGCOLOR=0xdddddddd>';
out += '<TD><B>Parameter</B></TD>';
out += '<TD><B>Description</B></TD></TR>';
out += params;
out += '</TABLE><P>';
}
if (tags["return"]) {
out += "<DT><B>Returns:</B><DD>";
out += tags["return"][0] + "</DL><P>";
}
if (tags["author"]) {
// List the authors together, separated by commas.
out += '<DT><B>Author:</B><DD>';
var array = tags["author"];
for (var i=0; i < array.length; i++) {
out += array[i];
if (i+1 < array.length)
out += ", ";
}
out += '</DL><P>';
}
if (tags["version"]) {
// Show the version.
out += '<DT><B>Version:</B><DD>';
var array = tags["version"];
for (var i=0; i < array.length; i++) {
out += array[i];
if (i+1 < array.length)
out += "<BR><DD>";
}
out += '</DL><P>';
}
if (tags["see"]) {
// List the see modules together, separated by <BR>.
out += '<DT><B>Dependencies:</B><DD>';
var array = tags["see"];
for (var i=0; i < array.length; i++) {
out += array[i];
if (i+1 < array.length)
out += "<BR><DD>";
}
out += '</DL><P>';
}
if (tags["lastmodified"]) {
// Shows a last modified description with client-side js.
out += '<DT><B>Last modified:</B><DD>';
out += '<script><!--\n';
out += 'document.writeln(document.lastModified);\n';
out += '// ---></script>\n';
out += '</DL><P>';
}
// additional tags can be added here (i.e., "if (tags["see"])...")
return out;
}
/**
* Create an html output file
* @param outputdir directory to put the file
* @param htmlfile name of the file
*/
function CreateOutputFile(outputdir,htmlfile)
{
if (outputdir==null)
{
var outname = htmlfile;
}
else
{
var separator = Packages.java.io.File.separator;
var outname = outputdir + separator + htmlfile.substring(htmlfile.lastIndexOf(separator),htmlfile.length);
}
print("output file: " + outname);
return new File(outname);
}
/**
* Process a javascript file. Puts the generated HTML file in the outdir
* @param filename name of the javascript file
* @inputdir input directory of the file (default null)
*/
function processJSFile(filename,inputdir)
{
if (debug) print("filename = " + filename + " inputdir = " + inputdir);
if (!filename.match(/\.js$/)) {
print("Expected filename to end in '.js'; had instead " +
filename + ". I don't treat the file.");
} else {
if (inputdir==null)
{
var inname = filename;
}
else
{
var separator = Packages.java.io.File.separator;
var inname = inputdir + separator + filename;
}
print("Processing file " + inname);
var f = new File(inname);
// create the output file
var htmlfile = filename.replace(/\.js$/, ".html");
var out = CreateOutputFile(outputdir,htmlfile);
processFile(f, filename, inputdir, out);
out.close();
}
}
/**
* Generate index files containing links to the processed javascript files
* and the generated functions
*/
function GenerateIndex(dirname)
{
// construct the files index file
var out = CreateOutputFile(outputdir,indexFile);
// write the beginning of the file
out.writeLine('<HTML><HEADER><TITLE>File Index - directory: ' + dirname + '</TITLE><BODY>');
out.writeLine('<H1>File Index - directory: ' + dirname + '</H1>\n');
out.writeLine('<TABLE WIDTH="90%" BORDER=1>');
out.writeLine('<TR BGCOLOR=0xdddddddd>');
out.writeLine('<TD><B>File</B></TD>');
out.writeLine('<TD><B>Description</B></TD></TR>');
var separator = Packages.java.io.File.separator;
// sort the index file array
var SortedFileArray = [];
for (var fname in indexFileArray)
SortedFileArray.push(fname);
SortedFileArray.sort();
for (var i=0; i < SortedFileArray.length; i++) {
var fname = SortedFileArray[i];
var htmlfile = fname.replace(/\.js$/, ".html");
out.writeLine('<TR><TD><A HREF=\"' + htmlfile + '\">' + fname + '</A></TD></TD><TD>');
if (indexFileArray[fname])
out.writeLine(indexFileArray[fname]);
else
out.writeLine('No comments');
out.writeLine('</TD></TR>\n');
}
out.writeLine('</TABLE></BODY></HTML>');
out.close();
// construct the functions index file
var out = CreateOutputFile(outputdir,indexFunction);
// write the beginning of the file
out.writeLine('<HTML><HEADER><TITLE>Function Index - directory: ' + dirname + '</TITLE><BODY>');
out.writeLine('<H1>Function Index - directory: ' + dirname + '</H1>\n');
out.writeLine('<TABLE WIDTH="90%" BORDER=1>');
out.writeLine('<TR BGCOLOR=0xdddddddd>');
out.writeLine('<TD><B>Function</B></TD>');
out.writeLine('<TD><B>Files</B></TD></TR>');
// sort the function array
var SortedFunctionArray = [];
for (var functionname in indexFunctionArray)
SortedFunctionArray.push(functionname);
SortedFunctionArray.sort();
for (var j=0; j < SortedFunctionArray.length; j++) {
var funcname = SortedFunctionArray[j];
with (indexFunctionArray[funcname]) {
var outstr = '<TR><TD>' + funcname + '</TD><TD>';
var filelst = filename.split("|");
for (var i in filelst) {
var htmlfile = filelst[i].replace(/\.js$/, ".html");
outstr += '<A HREF=\"' + htmlfile + '#' + funcname + '\">' + filelst[i] + '</A> ';
}
outstr += '</TD></TR>';
out.writeLine(outstr);
}
}
out.writeLine('</TABLE></BODY></HTML>');
out.close();
}
/**
* prints the options for JSDoc
*/
function PrintOptions()
{
print("You can use the following options:\n");
print("-d: specify an output directory for the generated html files\n");
print("-i: processes all files in an input directory (you can specify several directories)\n");
quit();
}
// Main Script
// first read the arguments
if (! arguments)
PrintOptions();
for (var i=0; i < arguments.length; i++) {
if (debug) print("argument: + \'" + arguments[i] + "\'");
if (arguments[i].match(/^\-/)) {
if (String(arguments[i])=="-d"){
// output directory for the generated html files
outputdir = String(arguments[i+1]);
if (debug) print("outputdir: + \'" + outputdir + "\'");
i++;
}
else if (String(arguments[i])=="-i"){
// process all files in an input directory
DirList.push(String(arguments[i+1]));
if (debug) print("inputdir: + \'" + arguments[i+1] + "\'");
i++;
}
else {
print("Unknown option: " + arguments[i] + "\n");
PrintOptions();
}
}
else
{
// we have a single file
if (debug) print("file: + \'" + arguments[i] + "\'");
FileList.push(String(arguments[i]));
}
}
// first handle the single files
for (var i in FileList)
processJSFile(FileList[i],null);
// then handle the input directories
for (var j in DirList) {
var inputdir = String(DirList[j]);
print("Process input directory: " + inputdir);
// clean up index arrays
var indexFileArray = [];
var indexFunctionArray = [];
// for the directory name get rid of ../../ or ..\..\
inputDirName = inputdir.replace(/\.\.\/|\.\.\\/g,"");
indexFile = indexFileName + "_" + inputDirName + ".html";
indexFunction = indexFunctionName + "_" + inputDirName + ".html";
print("indexFile = " + indexFile);
print("indexFunction = " + indexFunction);
// read the files in the directory
var DirFile = new java.io.File(inputdir);
var lst = DirFile.list();
var separator = Packages.java.io.File.separator;
for (var i=0; i < lst.length; i++)
{
processJSFile(String(lst[i]),inputdir);
}
// generate the index files for the input directory
GenerateIndex(inputDirName);
}