blob: d9813922cd3d524897c57a5b24f46ae893817114 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.codehaus.groovy.ant;
import org.apache.groovy.util.SystemUtil;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.DirSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.PatternSet;
import org.codehaus.groovy.runtime.ResourceGroovyMethods;
import org.codehaus.groovy.tools.groovydoc.ClasspathResourceManager;
import org.codehaus.groovy.tools.groovydoc.FileOutputTool;
import org.codehaus.groovy.tools.groovydoc.GroovyDocTool;
import org.codehaus.groovy.tools.groovydoc.LinkArgument;
import org.codehaus.groovy.tools.groovydoc.gstringTemplates.GroovyDocTemplateInfo;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
/**
* Access to the GroovyDoc tool from Ant.
*/
public class Groovydoc extends Task {
private final LoggingHelper log = new LoggingHelper(this);
private Path sourcePath;
private File destDir;
private final List<String> packageNames;
private final List<String> excludePackageNames;
private String windowTitle = "Groovy Documentation";
private String docTitle = "Groovy Documentation";
private String footer = "Groovy Documentation";
private String header = "Groovy Documentation";
private Boolean privateScope;
private Boolean protectedScope;
private Boolean packageScope;
private Boolean publicScope;
private Boolean author;
private Boolean processScripts;
private Boolean includeMainForScripts;
private boolean useDefaultExcludes;
private boolean includeNoSourcePackages;
private Boolean noTimestamp;
private Boolean noVersionStamp;
private final List<DirSet> packageSets;
private final List<String> sourceFilesToDoc;
private final List<LinkArgument> links = new ArrayList<>();
private File overviewFile;
private File styleSheetFile;
// dev note: update javadoc comment for #setExtensions(String) if updating below
private String extensions = ".java:.groovy:.gv:.gvy:.gsh";
private String charset;
private String fileEncoding;
public Groovydoc() {
packageNames = new ArrayList<>();
excludePackageNames = new ArrayList<>();
packageSets = new ArrayList<DirSet>();
sourceFilesToDoc = new ArrayList<>();
privateScope = false;
protectedScope = false;
publicScope = false;
packageScope = false;
useDefaultExcludes = true;
includeNoSourcePackages = false;
author = true;
processScripts = true;
includeMainForScripts = true;
noTimestamp = false;
noVersionStamp = false;
}
/**
* Specify where to find source file
*
* @param src a Path instance containing the various source directories.
*/
public void setSourcepath(Path src) {
if (sourcePath == null) {
sourcePath = src;
} else {
sourcePath.append(src);
}
}
/**
* Set the directory where the Groovydoc output will be generated.
*
* @param dir the destination directory.
*/
public void setDestdir(File dir) {
destDir = dir;
// todo: maybe tell groovydoc to use file output
}
/**
* If set to false, author will not be displayed.
* Currently not used.
*
* @param author new value
*/
public void setAuthor(boolean author) {
this.author = author;
}
/**
* If set to true, hidden timestamp will not appear within generated HTML.
*
* @param noTimestamp new value
*/
public void setNoTimestamp(boolean noTimestamp) {
this.noTimestamp = noTimestamp;
}
/**
* If set to true, hidden version stamp will not appear within generated HTML.
*
* @param noVersionStamp new value
*/
public void setNoVersionStamp(boolean noVersionStamp) {
this.noVersionStamp = noVersionStamp;
}
/**
* If set to false, Scripts will not be processed.
* Defaults to true.
*
* @param processScripts new value
*/
public void setProcessScripts(boolean processScripts) {
this.processScripts = processScripts;
}
/**
* If set to false, 'public static void main' method will not be displayed.
* Defaults to true. Ignored when not processing Scripts.
*
* @param includeMainForScripts new value
*/
public void setIncludeMainForScripts(boolean includeMainForScripts) {
this.includeMainForScripts = includeMainForScripts;
}
/**
* A colon-separated list of filename extensions to look for when searching for files to process in a given directory.
* Default value: <code>.java:.groovy:.gv:.gvy:.gsh</code>
*
* @param extensions new value
*/
public void setExtensions(String extensions) {
this.extensions = extensions;
}
/**
* Set the package names to be processed.
*
* @param packages a comma separated list of packages specs
* (may be wildcarded).
*/
public void setPackagenames(String packages) {
StringTokenizer tok = new StringTokenizer(packages, ",");
while (tok.hasMoreTokens()) {
String packageName = tok.nextToken();
packageNames.add(packageName);
}
}
public void setUse(boolean b) {
//ignore as 'use external file' irrelevant with groovydoc :-)
}
/**
* Set the title to be placed in the HTML &lt;title&gt; tag of the
* generated documentation.
*
* @param title the window title to use.
*/
public void setWindowtitle(String title) {
windowTitle = title;
}
/**
* Set the title for the overview page.
*
* @param htmlTitle the html to use for the title.
*/
public void setDoctitle(String htmlTitle) {
docTitle = htmlTitle;
}
/**
* Specify the file containing the overview to be included in the generated documentation.
*
* @param file the overview file
*/
public void setOverview(File file) {
overviewFile = file;
}
/**
* Indicates the access mode or scope of interest: one of public, protected, package, or private.
* Package scoped access is ignored for fields of Groovy classes where they correspond to properties.
*
* @param access one of public, protected, package, or private
*/
public void setAccess(String access) {
if ("public".equals(access)) publicScope = true;
else if ("protected".equals(access)) protectedScope = true;
else if ("package".equals(access)) packageScope = true;
else if ("private".equals(access)) privateScope = true;
}
/**
* Indicate whether all classes and
* members are to be included in the scope processed.
*
* @param b true if scope is to be private level.
*/
public void setPrivate(boolean b) {
privateScope = b;
}
/**
* Indicate whether only public classes and members are to be included in the scope processed.
*
* @param b true if scope only includes public level classes and members
*/
public void setPublic(boolean b) {
publicScope = b;
}
/**
* Indicate whether only protected and public classes and members are to be included in the scope processed.
*
* @param b true if scope includes protected level classes and members
*/
public void setProtected(boolean b) {
protectedScope = b;
}
/**
* Indicate whether only package, protected and public classes and members are to be included in the scope processed.
* Package scoped access is ignored for fields of Groovy classes where they correspond to properties.
*
* @param b true if scope includes package level classes and members
*/
public void setPackage(boolean b) {
packageScope = b;
}
/**
* Set the footer to place at the bottom of each generated html page.
*
* @param footer the footer value
*/
public void setFooter(String footer) {
this.footer = footer;
}
/**
* Specifies the header text to be placed at the top of each output file.
* The header will be placed to the right of the upper navigation bar.
* It may contain HTML tags and white space, though if it does, it must
* be enclosed in quotes. Any internal quotation marks within the header
* may have to be escaped.
*
* @param header the header value
*/
public void setHeader(String header) {
this.header = header;
}
/**
* Specifies the charset to be used in the templates, i.e.&#160;the value output within:
* &lt;meta http-equiv="Content-Type" content="text/html; charset=<em>charset</em>"&gt;.
*
* @param charset the charset value
*/
public void setCharset(String charset) {
this.charset = charset;
}
/**
* Specifies the file encoding to be used for generated files. If <em>fileEncoding</em> is missing,
* the <em>charset</em> encoding will be used for writing the files. If <em>fileEncoding</em> and
* <em>charset</em> are missing, the file encoding will default to <em>Charset.defaultCharset()</em>.
*
* @param fileEncoding the file encoding
*/
public void setFileEncoding(String fileEncoding) {
this.fileEncoding = fileEncoding;
}
/**
* Specifies a stylesheet file to use. If not specified,
* a default one will be generated for you.
*
* @param styleSheetFile the css stylesheet file to use
*/
public void setStyleSheetFile(File styleSheetFile) {
this.styleSheetFile = styleSheetFile;
}
/**
* Add the directories matched by the nested dirsets to the resulting
* packages list and the base directories of the dirsets to the Path.
* It also handles the packages and excludepackages attributes and
* elements.
*
* @param resultantPackages a list to which we add the packages found
* @param sourcePath a path to which we add each basedir found
* @since 1.5
*/
private void parsePackages(List<String> resultantPackages, Path sourcePath) {
List<String> addedPackages = new ArrayList<>();
List<DirSet> dirSets = new ArrayList<DirSet>(packageSets);
// for each sourcePath entry, add a directoryset with includes
// taken from packagenames attribute and nested package
// elements and excludes taken from excludepackages attribute
// and nested excludepackage elements
if (this.sourcePath != null) {
PatternSet ps = new PatternSet();
if (!packageNames.isEmpty()) {
for (String pn : packageNames) {
String pkg = pn.replace('.', '/');
if (pkg.endsWith("*")) {
pkg += "*";
}
ps.createInclude().setName(pkg);
}
} else {
ps.createInclude().setName("**");
}
for (String epn : excludePackageNames) {
String pkg = epn.replace('.', '/');
if (pkg.endsWith("*")) {
pkg += "*";
}
ps.createExclude().setName(pkg);
}
String[] pathElements = this.sourcePath.list();
for (String pathElement : pathElements) {
File dir = new File(pathElement);
if (dir.isDirectory()) {
DirSet ds = new DirSet();
ds.setDefaultexcludes(useDefaultExcludes);
ds.setDir(dir);
ds.createPatternSet().addConfiguredPatternset(ps);
dirSets.add(ds);
} else {
log.warn("Skipping " + pathElement + " since it is no directory.");
}
}
}
for (DirSet ds : dirSets) {
File baseDir = ds.getDir(getProject());
log.debug("scanning " + baseDir + " for packages.");
DirectoryScanner dsc = ds.getDirectoryScanner(getProject());
String[] dirs = dsc.getIncludedDirectories();
boolean containsPackages = false;
for (String dir : dirs) {
// are there any groovy or java files in this directory?
File pd = new File(baseDir, dir);
String[] files = pd.list((dir1, name) -> {
if (!includeNoSourcePackages
&& name.equals("package.html")) return true;
final StringTokenizer tokenizer = new StringTokenizer(extensions, ":");
while (tokenizer.hasMoreTokens()) {
String ext = tokenizer.nextToken();
if (name.endsWith(ext)) return true;
}
return false;
});
if (files != null) {
for (String filename : files) {
sourceFilesToDoc.add(dir + File.separator + filename);
}
if (files.length > 0) {
if (dir.isEmpty()) {
log.warn(baseDir
+ " contains source files in the default package,"
+ " you must specify them as source files not packages.");
} else {
containsPackages = true;
String pn = dir.replace(File.separatorChar, '.');
if (!addedPackages.contains(pn)) {
addedPackages.add(pn);
resultantPackages.add(pn);
}
}
}
}
}
if (containsPackages) {
// We don't need to care for duplicates here,
// Path.list does it for us.
sourcePath.createPathElement().setLocation(baseDir);
} else {
log.verbose(baseDir + " doesn't contain any packages, dropping it.");
}
}
}
@Override
public void execute() throws BuildException {
List<String> packagesToDoc = new ArrayList<>();
Path sourceDirs = new Path(getProject());
Properties properties = new Properties();
properties.setProperty("windowTitle", windowTitle);
properties.setProperty("docTitle", docTitle);
properties.setProperty("footer", footer);
properties.setProperty("header", header);
checkScopeProperties(properties);
properties.setProperty("publicScope", publicScope.toString());
properties.setProperty("protectedScope", protectedScope.toString());
properties.setProperty("packageScope", packageScope.toString());
properties.setProperty("privateScope", privateScope.toString());
properties.setProperty("author", author.toString());
properties.setProperty("processScripts", processScripts.toString());
properties.setProperty("includeMainForScripts", includeMainForScripts.toString());
properties.setProperty("overviewFile", overviewFile != null ? overviewFile.getAbsolutePath() : "");
properties.setProperty("charset", charset != null ? charset : "");
properties.setProperty("fileEncoding", fileEncoding != null ? fileEncoding : "");
properties.setProperty("timestamp", Boolean.valueOf(!noTimestamp).toString());
properties.setProperty("versionStamp", Boolean.valueOf(!noVersionStamp).toString());
String phaseOverride = SystemUtil.getSystemPropertySafe("groovydoc.phase.override");
if (phaseOverride != null) properties.put("phaseOverride", phaseOverride);
if (sourcePath != null) {
sourceDirs.addExisting(sourcePath);
}
parsePackages(packagesToDoc, sourceDirs);
GroovyDocTool htmlTool = new GroovyDocTool(
new ClasspathResourceManager(), // we're gonna get the default templates out of the dist jar file
sourcePath.list(),
getDocTemplates(),
getPackageTemplates(),
getClassTemplates(),
links,
properties
);
try {
htmlTool.add(sourceFilesToDoc);
FileOutputTool output = new FileOutputTool();
htmlTool.renderToOutput(output, destDir.getCanonicalPath()); // TODO push destDir through APIs?
} catch (Exception e) {
e.printStackTrace();
}
// try to override the default stylesheet with custom specified one if needed
if (styleSheetFile != null) {
try {
String css = ResourceGroovyMethods.getText(styleSheetFile);
File outfile = new File(destDir, "stylesheet.css");
ResourceGroovyMethods.setText(outfile, css);
} catch (IOException e) {
System.out.println("Warning: Unable to copy specified stylesheet '" + styleSheetFile.getAbsolutePath() +
"'. Using default stylesheet instead. Due to: " + e.getMessage());
}
}
}
private void checkScopeProperties(Properties properties) {
// make protected the default scope and check for invalid duplication
int scopeCount = 0;
if (packageScope) scopeCount++;
if (privateScope) scopeCount++;
if (protectedScope) scopeCount++;
if (publicScope) scopeCount++;
if (scopeCount == 0) {
protectedScope = true;
} else if (scopeCount > 1) {
throw new BuildException("More than one of public, private, package, or protected scopes specified.");
}
}
/**
* Create link to Javadoc/GroovyDoc output at the given URL.
*
* @return link argument to configure
*/
public LinkArgument createLink() {
LinkArgument result = new LinkArgument();
links.add(result);
return result;
}
/**
* Creates and returns an array of package template classpath entries.
* <p>
* This method is meant to be overridden by custom GroovyDoc implementations, using custom package templates.
*
* @return an array of package templates, whereas each entry is resolved as classpath entry, defaults to
* {@link GroovyDocTemplateInfo#DEFAULT_PACKAGE_TEMPLATES}.
*/
protected String[] getPackageTemplates() {
return GroovyDocTemplateInfo.DEFAULT_PACKAGE_TEMPLATES;
}
/**
* Creates and returns an array of doc template classpath entries.
* <p>
* This method is meant to be overridden by custom GroovyDoc implementations, using custom doc templates.
*
* @return an array of doc templates, whereas each entry is resolved as classpath entry, defaults to
* {@link GroovyDocTemplateInfo#DEFAULT_DOC_TEMPLATES}.
*/
protected String[] getDocTemplates() {
return GroovyDocTemplateInfo.DEFAULT_DOC_TEMPLATES;
}
/**
* Creates and returns an array of class template classpath entries.
* <p>
* This method is meant to be overridden by custom GroovyDoc implementations, using custom class templates.
*
* @return an array of class templates, whereas each entry is resolved as classpath entry, defaults to
* {@link GroovyDocTemplateInfo#DEFAULT_CLASS_TEMPLATES}.
*/
protected String[] getClassTemplates() {
return GroovyDocTemplateInfo.DEFAULT_CLASS_TEMPLATES;
}
}