/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* 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 Business Objects 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.
*/
/*
* CALDocTool.java
* Creation date: Oct 7, 2005.
* By: Joseph Wong
*/
package org.openquark.cal.caldoc;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openquark.cal.compiler.CompilerMessage;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.filter.CompositeModuleFilter;
import org.openquark.cal.filter.CompositeScopedEntityFilter;
import org.openquark.cal.filter.ExcludeTestModulesFilter;
import org.openquark.cal.filter.ModuleFilter;
import org.openquark.cal.filter.PublicEntitiesOnlyFilter;
import org.openquark.cal.filter.QualifiedNameBasedScopedEntityFilter;
import org.openquark.cal.filter.RegExpBasedModuleFilter;
import org.openquark.cal.filter.RegExpBasedQualifiedNameFilter;
import org.openquark.cal.filter.ScopedEntityFilter;
import org.openquark.cal.machine.StatusListener;
import org.openquark.cal.services.BasicCALServices;
import org.openquark.cal.services.LocaleUtilities;
import org.openquark.cal.services.WorkspaceManager;
import org.openquark.util.SimpleConsoleHandler;
/**
* This class represents the public interface to the HTML documentation generator. It provides facilities
* for parsing command line arguments into a configuration object for the generator.
*
* This class is not meant to be instantiated or subclassed.
*
* @author Joseph Wong
*/
public final class CALDocTool {
/** The name, version and copyright string to be displayed on each invocation. */
private static final String TOOL_NAME_VERSION_AND_COPYRIGHT = CALDocMessages.getString("CALDocToolNameVersionAndCopyright");
/**
* This class encapsulates a running instance of the CALDocTool command line interface.
*
* @author Joseph Wong
*/
public static final class Launcher {
/** Run the CALDoc tool with the specified command line arguments and the specified workspace. */
public void runWithCommandLine(String[] args, String launchCommand, String workspaceName) {
////
/// Set up a logger for printing status messages to standard output
//
final Logger logger = Logger.getLogger(getClass().getName());
logger.setLevel(Level.FINEST);
logger.setUseParentHandlers(false);
SimpleConsoleHandler handler = new SimpleConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);
////
/// Create and compile a workspace through a BasicCALServices
//
BasicCALServices calServices = BasicCALServices.make(workspaceName);
WorkspaceManager workspaceManager = calServices.getWorkspaceManager();
try {
HTMLDocumentationGeneratorConfiguration config = CALDocTool.makeConfiguration(workspaceManager, args, logger);
MessageLogger msgLogger = new MessageLogger();
StatusListener.StatusListenerAdapter statusListener = new StatusListener.StatusListenerAdapter() {
/**
* {@inheritDoc}
*/
@Override
public void setModuleStatus(StatusListener.Status.Module moduleStatus, ModuleName moduleName) {
if (moduleStatus == StatusListener.SM_GENCODE) {
logger.fine("Compiling " + moduleName);
}
}
};
logger.info("Compiling CAL workspace..");
calServices.compileWorkspace(statusListener, msgLogger);
if (msgLogger.getNErrors() == 0) {
logger.info("Compilation successful");
////
/// We only generate the documentation if everything compiled properly without errors
//
CALDocTool.run(workspaceManager, config);
} else {
logger.severe("Compilation failed:");
List<CompilerMessage> messages = msgLogger.getCompilerMessages();
for (int i = 0, n = messages.size(); i < n; i++) {
logger.info(" " + messages.get(i).toString());
}
}
} catch (CommandLineException.HelpRequested e) {
logger.info(CALDocTool.getUsage(launchCommand));
} catch (CommandLineException.MissingOptionArgument e) {
CALDocTool.logMissingOptionArgumentMessage(logger, e);
logger.info(CALDocTool.getUsage(launchCommand));
} catch (CommandLineException e) {
CALDocTool.logInvalidOptionMessage(logger, e);
logger.info(CALDocTool.getUsage(launchCommand));
}
}
}
/**
* This exception class represents a problem parsing the command line arguments.
*
* @author Joseph Wong
*/
private static class CommandLineException extends Exception {
private static final long serialVersionUID = -2211496055848942039L;
/**
* Package-scoped constructor.
* This exception is only meant to be created and thrown by the documentation generator.
*/
CommandLineException() {
super();
}
/**
* Package-scoped constructor.
* This exception is only meant to be created and thrown by the documentation generator.
* @param string the message string.
*/
CommandLineException(String string) {
super(string);
}
/**
* This exception class represents the user having specified the "-help" option to request
* the tool to just display the usage page and exit.
*
* @author Joseph Wong
*/
private static final class HelpRequested extends CommandLineException {
private static final long serialVersionUID = -4292078944760976595L;
}
/**
* This exception class represents the user forgetting to specify enough additional arguments
* to an option that requires one or more additional arguments.
*
* @author Joseph Wong
*/
private static final class MissingOptionArgument extends CommandLineException {
private static final long serialVersionUID = -7574178785736364836L;
/**
* Package-scoped constructor.
* This exception is only meant to be created and thrown by the documentation generator.
* @param string the message string.
*/
MissingOptionArgument(String string) {
super(string);
}
}
}
/** Private constructor. */
private CALDocTool() {}
/**
* The command line interface to the CALDocTool.
* @param args the command line arguments.
*/
public static void main(String args[]) {
String launchCommand = "java " + CALDocTool.class.getName() + " <workspace name>";
if (args.length == 0) {
System.out.println(CALDocTool.getUsage(launchCommand));
} else {
String workspaceName = args[0];
String[] remainder = new String[args.length - 1];
System.arraycopy(args, 1, remainder, 0, remainder.length);
new Launcher().runWithCommandLine(remainder, launchCommand, workspaceName);
}
}
/**
* Runs the documentation generator with the given workspace manager and configuration.
* @param workspaceManager the workspace manager to be used during documentation generation.
* @param config the configuration object.
*/
public static void run(WorkspaceManager workspaceManager, HTMLDocumentationGeneratorConfiguration config) {
showToolName(config.logger);
HTMLDocumentationGenerator docGenerator = new HTMLDocumentationGenerator(workspaceManager, config);
docGenerator.generateDoc();
}
/**
* Runs the documentation generator with the given workspace manager and configuration as specified by command line arguments.
* If parsing of the command line arguments fails, then the command line usage info is logged to the logger.
*
* @param workspaceManager the workspace manager to be used during documentation generation.
* @param cmdLine the command line arguments.
* @param logger the logger to use for logging status messages.
* @param launchCommand the command to launch the tool.
*/
public static void runWithCommandLine(WorkspaceManager workspaceManager, String cmdLine, Logger logger, String launchCommand) {
try {
run(workspaceManager, makeConfiguration(workspaceManager, cmdLine, logger));
} catch (CommandLineException.HelpRequested e) {
logger.info(getUsage(launchCommand));
} catch (CommandLineException.MissingOptionArgument e) {
logMissingOptionArgumentMessage(logger, e);
logger.info(getUsage(launchCommand));
} catch (CommandLineException e) {
logInvalidOptionMessage(logger, e);
logger.info(getUsage(launchCommand));
}
}
/**
* Runs the documentation generator with the given workspace manager and configuration as specified by command line arguments.
* If parsing of the command line arguments fails, then the command line usage info is logged to the logger.
*
* @param workspaceManager the workspace manager to be used during documentation generation.
* @param args the command line arguments.
* @param logger the logger to use for logging status messages.
* @param launchCommand the command to launch the tool.
*/
public static void runWithCommandLine(WorkspaceManager workspaceManager, String[] args, Logger logger, String launchCommand) {
try {
run(workspaceManager, makeConfiguration(workspaceManager, args, logger));
} catch (CommandLineException.HelpRequested e) {
logger.info(getUsage(launchCommand));
} catch (CommandLineException.MissingOptionArgument e) {
logMissingOptionArgumentMessage(logger, e);
logger.info(getUsage(launchCommand));
} catch (CommandLineException e) {
logInvalidOptionMessage(logger, e);
logger.info(getUsage(launchCommand));
}
}
/**
* Logs a message about a missing option argument to the given logger.
* @param logger the logger.
* @param e the exception about the missing option argument.
*/
private static void logMissingOptionArgumentMessage(Logger logger, CommandLineException.MissingOptionArgument e) {
logger.info(CALDocMessages.getString("STATUS.missingArgumentForOption", e.getMessage()));
}
/**
* Logs a message about an invalid option to the given logger.
* @param logger the logger.
* @param e the exception about the invalid option.
*/
private static void logInvalidOptionMessage(Logger logger, CommandLineException e) {
logger.info(CALDocMessages.getString("STATUS.invalidOption", e.getMessage()));
}
/**
* Logs the tool name and copyright to the given logger.
* @param logger the logger.
*/
private static void showToolName(Logger logger) {
logger.info(TOOL_NAME_VERSION_AND_COPYRIGHT);
}
/**
* @param launchCommand the command to launch the tool.
* @return the usage string.
*/
private static String getUsage(String launchCommand) {
String usage =
TOOL_NAME_VERSION_AND_COPYRIGHT + "\n" +
"Usage: " + launchCommand + " [options]\n" +
"\n" +
"Options:\n" +
"-d <directory> Destination directory for output files\n" +
"\n" +
"-public Show only public entities (default)\n" +
"-private Show all entities - public, protected and private\n" +
"\n" +
"-modules <regexp> Include only the modules matched by the regular expression\n" +
"-excludeModules <regexp> Exclude the modules matched by the regular expression\n" +
"-entities <regexp> Include only the entities (functions, types and data constructors, classes and class methods) matched by the regular expression\n" +
"-excludeEntities <regexp> Exclude the entities (functions, types and data constructors, classes and class methods) matched by the regular expression\n" +
"\n" +
"-metadata [override] Include metadata in documentation (use the \'override\' option to hide CALDoc that is superceded by metadata)\n" +
"-author Include author information\n" +
"-version Include version information\n" +
"-qualifyPreludeNames Qualify the names of Prelude entities outside the Prelude module\n" +
"\n" +
"-use Create usage indices\n" +
"\n" +
"-doNotSeparateInstanceDoc Do not separate instance documentation from the main documentation pages\n" +
"\n" +
"-locale <locale> Locale to be used, e.g. en_US or fr\n" +
"\n" +
"-windowTitle <text> Browser window title for documentation\n" +
"-docTitle <html-code> Include title for overview page\n" +
"-header <html-code> Include header text for each page\n" +
"-footer <html-code> Include footer text for each page\n" +
"-bottom <html-code> Include bottom text for each page\n" +
"\n" +
"-verbose Output detailed messages about what the tool is doing\n" +
"-quiet Do not display status messages to screen\n" +
"\n" +
"-help Display command line options and exit\n" +
"\n" +
"Non-standard -X options:\n" +
"-XexcludeTestModules Exclude 'test' modules\n" +
"\n";
return usage;
}
/**
* Parses the given command line arguments and constructs a configuration object from them.
* @param workspaceManager the workspace manager to be used during documentation generation.
* @param cmdLine the command line arguments.
* @param logger the logger to use for logging status messages.
* @return the configuration object
* @throws CommandLineException if the command line arguments cannot be parsed properly.
*/
public static HTMLDocumentationGeneratorConfiguration makeConfiguration(WorkspaceManager workspaceManager, String cmdLine, Logger logger) throws CommandLineException {
// split the command line with the quote character, so that we can handle quoted strings differently
String[] quoteSeparatedChunks = cmdLine.split("\"");
List<String> argList = new ArrayList<String>();
// we start out being outside the quotes
boolean inQuotes = false;
for (final String quoteSeparatedChunk : quoteSeparatedChunks) {
if (inQuotes) {
// if inside quotes, then add the quoted chunk as one argument
argList.add(quoteSeparatedChunk);
} else {
// if outside quotes, then split the chunk by whitespace, and add each bit as an argument
String[] bits = quoteSeparatedChunk.split("\\s");
for (final String bit : bits) {
if (bit.length() > 0) {
argList.add(bit);
}
}
}
// each time we cross a delimiter (quote) boundary, we flip between inside quotes and outside quotes
inQuotes = !inQuotes;
}
String[] args = argList.toArray(new String[argList.size()]);
return makeConfiguration(workspaceManager, args, logger);
}
/**
* Parses the given command line arguments and constructs a configuration object from them.
* @param workspaceManager the workspace manager to be used during documentation generation.
* @param args the command line arguments.
* @param logger the logger to use for logging status messages.
* @return the configuration object
* @throws CommandLineException if the command line arguments cannot be parsed properly.
*/
public static HTMLDocumentationGeneratorConfiguration makeConfiguration(WorkspaceManager workspaceManager, String[] args, Logger logger) throws CommandLineException {
File baseDirectory = new File(".");
boolean publicEntitiesOnly = false;
boolean shouldGenerateFromMetadata = false;
boolean shouldAlwaysGenerateFromCALDoc = true;
boolean shouldGenerateAuthorInfo = false;
boolean shouldGenerateVersionInfo = false;
boolean shouldDisplayPreludeNamesAsUnqualified = true;
boolean shouldGenerateUsageIndices = false;
boolean shouldSeparateInstanceDoc = true;
String windowTitle = "";
String docTitle = "";
String header = "";
String footer = "";
String bottom = "";
Locale locale = LocaleUtilities.INVARIANT_LOCALE;
List<ModuleFilter> moduleFilterList = new ArrayList<ModuleFilter>();
List<ScopedEntityFilter> scopedEntityFilterList = new ArrayList<ScopedEntityFilter>();
////
/// Loop through each argument, checking for the option prefix '-'.
/// If the option requires additional arguments, the cursor (curPos) is advanced appropriately.
//
for (int curPos = 0; curPos < args.length; curPos++) {
String curArg = args[curPos];
if (curArg.startsWith("-")) {
/// the actual option is after the '-'
//
String option = curArg.substring(1);
////
/// Check again each supported option
//
if (option.equalsIgnoreCase("help")) {
// -help was specified, so no generation should actually occur.
// we throw the exception here so that the caller can act on it
// and terminate after displaying the usage.
throw new CommandLineException.HelpRequested();
} else if (option.equalsIgnoreCase("d")) {
// -d <directory>
// specifies the output directory
// 1 additional argument required
curPos++;
if (curPos < args.length) {
baseDirectory = new File(args[curPos]);
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("windowTitle")) {
// -windowTitle <text>
// specifies the window title
// 1 additional argument required
curPos++;
if (curPos < args.length) {
windowTitle = args[curPos];
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("docTitle")) {
// -docTitle <html-code>
// specifies the documentation title
// 1 additional argument required
curPos++;
if (curPos < args.length) {
docTitle = args[curPos];
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("header")) {
// -header <html-code>
// specifies the header
// 1 additional argument required
curPos++;
if (curPos < args.length) {
header = args[curPos];
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("footer")) {
// -footer <html-code>
// specifies the footer
// 1 additional argument required
curPos++;
if (curPos < args.length) {
footer = args[curPos];
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("bottom")) {
// -bottom <html-code>
// specifies the fine print at the bottom of a page
// 1 additional argument required
curPos++;
if (curPos < args.length) {
bottom = args[curPos];
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("modules")) {
// -modules <regexp>
// include only the modules matched by the regular expression
// 1 additional argument required
curPos++;
if (curPos < args.length) {
String regexp = args[curPos];
moduleFilterList.add(new RegExpBasedModuleFilter(regexp, false));
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("excludeModules")) {
// -excludeModules <regexp>
// exclude the modules matched by the regular expression
// 1 additional argument required
curPos++;
if (curPos < args.length) {
String regexp = args[curPos];
moduleFilterList.add(new RegExpBasedModuleFilter(regexp, true));
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("entities")) {
// -entities <regexp>
// include only the scoped entities matched by the regular expression
// 1 additional argument required
curPos++;
if (curPos < args.length) {
String regexp = args[curPos];
scopedEntityFilterList.add(
new QualifiedNameBasedScopedEntityFilter(
new RegExpBasedQualifiedNameFilter(regexp, false)));
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("excludeEntities")) {
// -excludeEntities <regexp>
// exclude the scoped entities matched by the regular expression
// 1 additional argument required
curPos++;
if (curPos < args.length) {
String regexp = args[curPos];
scopedEntityFilterList.add(
new QualifiedNameBasedScopedEntityFilter(
new RegExpBasedQualifiedNameFilter(regexp, true)));
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("locale")) {
// -locale <locale>
// locale to be used for generating documentation
// 1 additional argument required
curPos++;
if (curPos < args.length) {
locale = LocaleUtilities.localeFromCanonicalString(args[curPos]);
} else {
throw new CommandLineException.MissingOptionArgument(curArg);
}
} else if (option.equalsIgnoreCase("metadata")) {
// -metadata [override]
// include metadata in documentation
// 1 optional argument: override - hide CALDoc that is superceded by metadata
shouldGenerateFromMetadata = true;
int nextPos = curPos + 1;
if (nextPos < args.length) {
String optionalFlag = args[nextPos];
if (optionalFlag.equals("override")) {
shouldAlwaysGenerateFromCALDoc = false;
curPos = nextPos;
}
}
} else if (option.equalsIgnoreCase("public")) {
// -public
// include public entries only
// no additional arguments
publicEntitiesOnly = true;
} else if (option.equalsIgnoreCase("private")) {
// -private
// include public, protected and private entries
// no additional arguments
publicEntitiesOnly = false;
} else if (option.equalsIgnoreCase("author")) {
// -author
// include author info
// no additional arguments
shouldGenerateAuthorInfo = true;
} else if (option.equalsIgnoreCase("version")) {
// -version
// include version info
// no additional arguments
shouldGenerateVersionInfo = true;
} else if (option.equalsIgnoreCase("qualifyPreludeNames")) {
// -qualifyPreludeNames
// qualify the names of Prelude entities outside the Prelude module
// no additional arguments
shouldDisplayPreludeNamesAsUnqualified = false;
} else if (option.equalsIgnoreCase("use")) {
// -use
// create usage indices
// no additional arguments
shouldGenerateUsageIndices = true;
} else if (option.equalsIgnoreCase("doNotSeparateInstanceDoc")) {
// -doNotSeparateInstanceDoc
// do not separate instance documentation from the main documentation pages
// no additional arguments
shouldSeparateInstanceDoc = false;
} else if (option.equalsIgnoreCase("verbose")) {
// -verbose
// output detailed messages about what the tool is doing
// no additional arguments
logger.setLevel(Level.ALL);
Handler[] handlers = logger.getHandlers();
for (final Handler handler : handlers) {
handler.setLevel(Level.ALL);
}
} else if (option.equalsIgnoreCase("quiet")) {
// -quiet
// do not display status messages to screen
// no additional arguments
Handler[] handlers = logger.getHandlers();
for (final Handler handler : handlers) {
handler.setLevel(Level.WARNING);
}
} else if (option.startsWith("X")) {
// "hidden" X options
// the actual name of the X option is after the X
option = option.substring(1);
if (option.equalsIgnoreCase("excludeTestModules")) {
// -XexcludeTestModules
// exclude "test" modules
// no additional arguments
moduleFilterList.add(new ExcludeTestModulesFilter(workspaceManager.getWorkspace()));
} else {
throw new CommandLineException(curArg);
}
} else {
throw new CommandLineException(curArg);
}
} else {
throw new CommandLineException(curArg);
}
}
if (publicEntitiesOnly) {
// add the public-entries-only filter to the start of the list, since
// this is a fast filter that has the potential to filter out a lot of entities
// so it should be the first filter to be run
scopedEntityFilterList.add(0, new PublicEntitiesOnlyFilter());
}
// create the module and scoped entity filters from the aggregated lists.
ModuleFilter moduleFilter = CompositeModuleFilter.make(moduleFilterList);
ScopedEntityFilter scopedEntityFilter = CompositeScopedEntityFilter.make(scopedEntityFilterList);
FileSystemFileGenerator fileGenerator = new FileSystemFileGenerator(baseDirectory, logger);
return new HTMLDocumentationGeneratorConfiguration(
fileGenerator,
moduleFilter,
scopedEntityFilter,
shouldGenerateFromMetadata,
shouldAlwaysGenerateFromCALDoc,
shouldGenerateAuthorInfo,
shouldGenerateVersionInfo,
shouldDisplayPreludeNamesAsUnqualified,
shouldGenerateUsageIndices,
shouldSeparateInstanceDoc,
windowTitle,
docTitle,
header,
footer,
bottom,
locale,
logger);
}
}