/**********************************************************************
* Copyright (c) 2005-2009 ant4eclipse project team.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nils Hartmann, Daniel Kasmeroglu, Gerd Wuetherich
**********************************************************************/
package org.ant4eclipse.ant.pydt;
import org.ant4eclipse.ant.core.AbstractAnt4EclipseTask;
import org.ant4eclipse.lib.core.exception.Ant4EclipseException;
import org.ant4eclipse.lib.core.logging.A4ELogging;
import org.ant4eclipse.lib.core.service.ServiceRegistryAccess;
import org.ant4eclipse.lib.core.util.Utilities;
import org.ant4eclipse.lib.pydt.PydtExceptionCode;
import org.ant4eclipse.lib.pydt.model.pyre.PythonRuntime;
import org.ant4eclipse.lib.pydt.model.pyre.PythonRuntimeRegistry;
import org.ant4eclipse.lib.pydt.tools.PythonTools;
import org.apache.tools.ant.BuildException;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* @author Daniel Kasmeroglu (Daniel.Kasmeroglu@Kasisoft.net)
*/
public class PythonDocumentationTask extends AbstractAnt4EclipseTask {
private static final String SCRIPT = "import sys\n" + "if __name__ == \"__main__\":\n"
+ " sys.path.append(\"%s\")\n" + " from %s import cli\n"
+ " sys.argv=[]\n%s" + " cli.cli()\n";
private static final String MSG_DOCS_NOT_SUPPORTED = "Generation of documentation is currently not supported for python with major version >= 3 !";
private static final String MSG_DOCS_NOT_AVAILABLE = "The epydoc could not be unpacked onto your system, so this feature is not available.";
private String _runtimeid = null;
private File _destdir = null;
private File _sourcedir = null;
/**
* Changes the id of the runtime used to access the python interpreter.
*
* @param runtimeid
* The id of the runtime used to access the python interpreter.
*/
public void setRuntime(String runtimeid) {
this._runtimeid = Utilities.cleanup(runtimeid);
}
/**
* Changes the destination directory where the documentation shall be written to.
*
* @param destdir
* The destination directory where the documentation shall be written to.
*/
public void setDestdir(File destdir) {
this._destdir = destdir;
}
/**
* Changes the sources directory used to create the documentation from.
*
* @param sourcedir
* The sources directory used to create the documentation from.
*/
public void setSourcedir(File sourcedir) {
this._sourcedir = sourcedir;
}
/**
* {@inheritDoc}
*/
@Override
protected void preconditions() throws BuildException {
super.preconditions();
if (this._destdir == null) {
throw new Ant4EclipseException(PydtExceptionCode.MISSINGATTRIBUTE, "destdir");
}
if (this._sourcedir == null) {
throw new Ant4EclipseException(PydtExceptionCode.MISSINGATTRIBUTE, "sourcedir");
}
if (this._destdir.exists() && (!this._destdir.isDirectory())) {
throw new Ant4EclipseException(PydtExceptionCode.NOTADIRECTORY, this._destdir);
}
if (!this._sourcedir.isDirectory()) {
throw new Ant4EclipseException(PydtExceptionCode.NOTADIRECTORY, this._sourcedir);
}
if (this._runtimeid == null) {
throw new Ant4EclipseException(PydtExceptionCode.MISSINGATTRIBUTE, "runtime");
}
PythonRuntimeRegistry registry = ServiceRegistryAccess.instance().getService(PythonRuntimeRegistry.class);
if (!registry.hasRuntime(this._runtimeid)) {
throw new Ant4EclipseException(PydtExceptionCode.UNKNOWN_PYTHON_RUNTIME, this._runtimeid);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void doExecute() {
PythonRuntimeRegistry registry = ServiceRegistryAccess.instance().getService(PythonRuntimeRegistry.class);
PythonRuntime runtime = registry.getRuntime(this._runtimeid);
if (runtime.getVersion().getMajor() >= 3) {
// unfortunately the syntax has changed, so we can't use epydoc with it
A4ELogging.warn(MSG_DOCS_NOT_SUPPORTED);
return;
}
PythonTools pythontools = ServiceRegistryAccess.instance().getService(PythonTools.class);
File install = pythontools.getEpydocInstallation();
if (install == null) {
A4ELogging.warn(MSG_DOCS_NOT_AVAILABLE);
return;
}
File executable = runtime.getExecutable();
Utilities.mkdirs(this._destdir);
// setup some options for the commandline
StringBuffer options = new StringBuffer();
appendOption(options, "--html");
appendOption(options, "-o");
appendOption(options, pythonEscape(this._destdir.getAbsolutePath()));
collectModules(options);
// generate the python script used to generate the documentation
String name = Utilities.stripSuffix(install.getName());
String code = String.format(SCRIPT, pythonEscape(install.getAbsolutePath()), name, options);
// save the script
File script = Utilities.createTempFile(code, ".py", "ASCII");
// execute the script
Utilities.execute(executable, null, script.getAbsolutePath());
}
/**
* Makes sure that backslashes come in double packs. Otherwise the used interpreter may fail.
*
* @param str
* The path which requires to be altered. Neither <code>null</code> nor empty.
*
* @return The altered path. Neither <code>null</code> nor empty.
*/
private String pythonEscape(String str) {
return str.replaceAll("\\\\", "\\\\\\\\");
}
/**
* Appends a single commandline option to a buffer.
*
* @param buffer
* The buffer used to be extended with an additional option. Not <code>null</code>.
* @param option
* The option that has to be added. Neither <code>null</code> nor empty.
*/
private void appendOption(StringBuffer buffer, String option) {
buffer.append(" sys.argv.append(\"" + option + "\")\n");
}
/**
* Generates a comma separated list containing the modules used for the documentation generation.
*
* @param options
* The buffer used to collect the package locations as single options added to the commandline. Not
* <code>null</code>.
*/
private void collectModules(StringBuffer options) {
List<File> result = new ArrayList<File>();
collectPackages(result, this._sourcedir);
if (result.size() > 0) {
appendOption(options, pythonEscape(result.get(0).getAbsolutePath()));
for (int i = 1; i < result.size(); i++) {
appendOption(options, pythonEscape(result.get(i).getAbsolutePath()));
}
}
}
/**
* Returns <code>true</code> if the supplied directory refers to a package.
*
* @param dir
* The directory that has to be tested. Not <code>null</code> and must be a directory.
*
* @return <code>true</code> <=> The supplied directory is a package.
*/
private boolean isPackage(File dir) {
File child = new File(dir, "__init__.py");
return child.isFile();
}
/**
* This collector recursively traverses a filesystem while collecting each directory corresponding to a package. If a
* package will be detected it will no longer be traversed since this is performed by the <i>epydoc</i> tool.
*
* @param receiver
* The list used to collect the package location. Not <code>null</code>.
* @param current
* The current location within the filesystem. Not <code>null</code> and must be a directory.
*/
private void collectPackages(List<File> receiver, File current) {
if (isPackage(current)) {
receiver.add(current);
return;
}
File[] children = current.listFiles();
for (File child : children) {
if (child.isDirectory()) {
collectPackages(receiver, child);
}
}
}
} /* ENDCLASS */