Java tutorial
/* * Copyright 2013 Benot Prioux * * 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. */ package org.asciidoctor.ant; import org.apache.commons.io.FileUtils; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileSet; import org.asciidoctor.*; import org.asciidoctor.internal.JRubyRuntimeContext; import org.asciidoctor.internal.RubyUtils; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; public class AsciidoctorAntTask extends Task { private File sourceDirectory; private File outputDirectory; private boolean preserveDirectories = false; private String sourceDocumentName; private String backend = "docbook"; private String imagesDir = "images"; private String doctype = "article"; private boolean compact = false; private boolean headerFooter = true; private String sourceHighlighter; private boolean embedAssets = false; private String eruby = ""; private String templateDir; private String templateEngine; private String baseDir; private boolean relativeBaseDir = false; private String extensions; private SafeMode safe = SafeMode.SAFE; private List<FileSet> resources = new ArrayList<FileSet>(); private List<Attribute> attributes = new ArrayList<Attribute>(); private List<Extension> preProcessors = new ArrayList<Extension>(); private List<Extension> treeProcessors = new ArrayList<Extension>(); private List<Extension> postProcessors = new ArrayList<Extension>(); private List<Extension> blockProcessors = new ArrayList<Extension>(); private List<Extension> blockMacroProcessors = new ArrayList<Extension>(); private List<Extension> inlineMacroProcessors = new ArrayList<Extension>(); private List<Extension> includeProcessors = new ArrayList<Extension>(); private List<RubyLibrary> requires = new ArrayList<RubyLibrary>(); private String gemPaths; @Override public void execute() throws BuildException { checkMandatoryParameter("sourceDirectory", sourceDirectory); checkMandatoryParameter("outputDirectory", outputDirectory); ensureOutputExists(outputDirectory); Asciidoctor asciidoctor = createAsciidoctor(gemPaths); registerAdditionalRubyLibraries(asciidoctor); registerExtensions(asciidoctor); AttributesBuilder attributesBuilder = buildAttributes(); OptionsBuilder optionsBuilder = buildOptions(); optionsBuilder.attributes(attributesBuilder.get()); if (sourceDocumentName == null) { log("Render asciidoc files from " + sourceDirectory + " to " + outputDirectory + " with backend=" + backend); for (File file : scanSourceFiles()) { setDestinationPaths(optionsBuilder, file); asciidoctor.renderFile(file, optionsBuilder.get()); } } else { log("Render " + sourceDocumentName + " from " + sourceDirectory + " to " + outputDirectory + " with backend=" + backend); File file = new File(sourceDirectory, sourceDocumentName); setDestinationPaths(optionsBuilder, file); asciidoctor.renderFile(file, optionsBuilder.get()); } try { for (FileSet resource : resources) { File resourceDir = resource.getDir(); String destPath = resourceDir.getCanonicalPath() .substring(sourceDirectory.getCanonicalPath().length()); File destResourceDir = new File(outputDirectory, destPath); destResourceDir.mkdirs(); String[] includedFiles = resource.getDirectoryScanner(getProject()).getIncludedFiles(); FileUtils.copyDirectory(resourceDir, destResourceDir, new ResourceFileFilter(includedFiles), false); } } catch (IOException e) { throw new BuildException("Error copying resources", e); } } private void registerAdditionalRubyLibraries(Asciidoctor asciidoctor) { for (RubyLibrary require : requires) { asciidoctor.rubyExtensionRegistry().requireLibrary(require.getName()); } } private void registerExtensions(Asciidoctor asciidoctor) { try { asciidoctor.rubyExtensionRegistry().requireLibrary("asciidoctor-diagram"); } catch (RuntimeException e) { log("asciidoctor-diagram is not available", Project.MSG_WARN); } for (Extension preProcessor : preProcessors) { asciidoctor.javaExtensionRegistry().preprocessor(preProcessor.getClassName()); } for (Extension treeProcessor : treeProcessors) { asciidoctor.javaExtensionRegistry().treeprocessor(treeProcessor.getClassName()); } for (Extension postProcessor : postProcessors) { asciidoctor.javaExtensionRegistry().postprocessor(postProcessor.getClassName()); } for (Extension blockProcessor : blockProcessors) { asciidoctor.javaExtensionRegistry().block(blockProcessor.getBlockName(), blockProcessor.getClassName()); } for (Extension blockMacroProcessor : blockMacroProcessors) { asciidoctor.javaExtensionRegistry().blockMacro(blockMacroProcessor.getBlockName(), blockMacroProcessor.getClassName()); } for (Extension inlineMacroProcessor : inlineMacroProcessors) { asciidoctor.javaExtensionRegistry().inlineMacro(inlineMacroProcessor.getBlockName(), inlineMacroProcessor.getClassName()); } for (Extension includeProcessor : includeProcessors) { asciidoctor.javaExtensionRegistry().includeProcessor(includeProcessor.getClassName()); } } private Asciidoctor createAsciidoctor(String gemPath) { ClassLoader oldTCCL = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); return getAsciidoctorInstance(gemPath); } finally { Thread.currentThread().setContextClassLoader(oldTCCL); } } private Asciidoctor getAsciidoctorInstance(String gemPath) { Asciidoctor asciidoctor; if (gemPath == null) { asciidoctor = Asciidoctor.Factory.create(); } else { // Replace Windows path separator to avoid paths with mixed \ and /. // This happens for instance when setting: <gemPath>${project.build.directory}/gems-provided</gemPath> // because the project's path is converted to string. String normalizedGemPath = (File.separatorChar == '\\') ? gemPath.replaceAll("\\\\", "/") : gemPath; asciidoctor = Asciidoctor.Factory.create(normalizedGemPath); } String gemHome = JRubyRuntimeContext.get().evalScriptlet("ENV['GEM_HOME']").toString(); String gemHomeExpected = (gemPath == null || "".equals(gemPath)) ? "" : gemPath.split(java.io.File.pathSeparator)[0]; if (!"".equals(gemHome) && !gemHomeExpected.equals(gemHome)) { log("Using inherited external environment to resolve gems (" + gemHome + "), i.e. build is platform dependent!"); } return asciidoctor; } private OptionsBuilder buildOptions() { OptionsBuilder optionsBuilder = OptionsBuilder.options(); optionsBuilder.safe(safe).eruby(eruby).backend(backend).docType(doctype).compact(compact) .headerFooter(headerFooter).mkDirs(true); if (templateEngine != null) { optionsBuilder.templateEngine(templateEngine); } if (templateDir != null) { optionsBuilder.templateDir(new File(templateDir)); } return optionsBuilder; } private void setDestinationPaths(OptionsBuilder optionsBuilder, final File sourceFile) { optionsBuilder.baseDir(computeBaseDir(sourceFile)); try { if (preserveDirectories) { String proposalPath = sourceFile.getParentFile().getCanonicalPath() .substring(sourceDirectory.getCanonicalPath().length()); File relativePath = new File(outputDirectory, proposalPath); relativePath.mkdirs(); optionsBuilder.toDir(relativePath).destinationDir(relativePath); } else { File destinationDir = outputDirectory; optionsBuilder.toDir(destinationDir).destinationDir(destinationDir); } } catch (IOException e) { throw new BuildException("Unable to locate output directory", e); } } private File computeBaseDir(File sourceFile) { File baseDirFile; if (baseDir != null) { baseDirFile = new File(baseDir); } else { // when preserveDirectories == false, parent and sourceDirectory are the same if (relativeBaseDir) { baseDirFile = sourceFile.getParentFile(); } else { baseDirFile = getProject().getBaseDir(); } } return baseDirFile; } private AttributesBuilder buildAttributes() { AttributesBuilder attributesBuilder = AttributesBuilder.attributes(); attributesBuilder.imagesDir(imagesDir); if (sourceHighlighter != null) { attributesBuilder.sourceHighlighter(sourceHighlighter); } if (embedAssets) { attributesBuilder.linkCss(false); attributesBuilder.dataUri(true); } attributesBuilder.copyCss(false); // TODO Figure out how to reliably set other values (like boolean values, dates, times, etc) for (Attribute attribute : attributes) { if ("true".equals(attribute.getValue()) || "false".equals(attribute.getValue())) { attributesBuilder.attribute(attribute.getKey(), Attributes.toAsciidoctorFlag(Boolean.valueOf(attribute.getValue()))); continue; } // Can't do anything about dates and times because all that logic is private in Attributes attributesBuilder.attribute(attribute.getKey(), attribute.getValue()); } return attributesBuilder; } private void checkMandatoryParameter(String name, Object value) { if (value == null) { throw new BuildException(name + " is mandatory"); } } private void ensureOutputExists(File outputFile) { if (!outputFile.exists()) { if (!outputFile.mkdirs()) { log("Can't create " + outputFile.getPath(), Project.MSG_ERR); } } } private List<File> scanSourceFiles() { final List<File> asciidoctorFiles; String absoluteSourceDirectory = sourceDirectory.getAbsolutePath(); if (extensions == null || extensions.isEmpty()) { final DirectoryWalker directoryWalker = new AsciiDocDirectoryWalker(absoluteSourceDirectory); asciidoctorFiles = directoryWalker.scan(); } else { final DirectoryWalker directoryWalker = new CustomExtensionDirectoryWalker(absoluteSourceDirectory, Arrays.asList(extensions.split(","))); asciidoctorFiles = directoryWalker.scan(); } for (Iterator<File> iter = asciidoctorFiles.iterator(); iter.hasNext();) { File f = iter.next(); do { // stop when we hit the source directory root if (absoluteSourceDirectory.equals(f.getAbsolutePath())) { break; } // skip if the filename or directory begins with _ if (f.getName().startsWith("_")) { iter.remove(); break; } } while ((f = f.getParentFile()) != null); } return asciidoctorFiles; } private static class CustomExtensionDirectoryWalker extends AbstractDirectoryWalker { private final List<String> extensions; public CustomExtensionDirectoryWalker(final String absolutePath, final List<String> extensions) { super(absolutePath); this.extensions = extensions; } @Override protected boolean isAcceptedFile(final File filename) { final String name = filename.getName(); for (final String extension : extensions) { if (name.endsWith(extension)) { return true; } } return false; } } // Setters for Ant Task @SuppressWarnings("UnusedDeclaration") public void setSourceDirectory(File sourceDirectory) { this.sourceDirectory = sourceDirectory; } @SuppressWarnings("UnusedDeclaration") public void setOutputDirectory(File outputDirectory) { this.outputDirectory = outputDirectory; } @SuppressWarnings("UnusedDeclaration") public void setSourceDocumentName(String sourceDocumentName) { this.sourceDocumentName = sourceDocumentName; } @SuppressWarnings("UnusedDeclaration") public void setBackend(String backend) { this.backend = backend; } @SuppressWarnings("UnusedDeclaration") public void setImagesDir(String imagesDir) { this.imagesDir = imagesDir; } @SuppressWarnings("UnusedDeclaration") public void setDoctype(String doctype) { this.doctype = doctype; } @SuppressWarnings("UnusedDeclaration") public void setCompact(boolean compact) { this.compact = compact; } @SuppressWarnings("UnusedDeclaration") public void setHeaderFooter(boolean headerFooter) { this.headerFooter = headerFooter; } @SuppressWarnings("UnusedDeclaration") public void setSourceHighlighter(String sourceHighlighter) { this.sourceHighlighter = sourceHighlighter; } @SuppressWarnings("UnusedDeclaration") public void setEmbedAssets(boolean embedAssets) { this.embedAssets = embedAssets; } @SuppressWarnings("UnusedDeclaration") public void setEruby(String eruby) { this.eruby = eruby; } @SuppressWarnings("UnusedDeclaration") public void setTemplateDir(String templateDir) { this.templateDir = templateDir; } @SuppressWarnings("UnusedDeclaration") public void setTemplateEngine(String templateEngine) { this.templateEngine = templateEngine; } @SuppressWarnings("UnusedDeclaration") public void setBaseDir(String baseDir) { this.baseDir = baseDir; } @SuppressWarnings("UnusedDeclaration") public void setRelativeBaseDir(boolean relativeBaseDir) { this.relativeBaseDir = relativeBaseDir; } @SuppressWarnings("UnusedDeclaration") public void setExtensions(String extensions) { this.extensions = extensions; } @SuppressWarnings("UnusedDeclaration") public void setPreserveDirectories(boolean preserveDirectories) { this.preserveDirectories = preserveDirectories; } @SuppressWarnings("UnusedDeclaration") public void addResource(FileSet fileSet) { resources.add(fileSet); } @SuppressWarnings("UnusedDeclaration") public Attribute createAttribute() { Attribute attribute = new Attribute(); attributes.add(attribute); return attribute; } public class Attribute { private String key; private String value; public String getKey() { return key; } public String getValue() { return value; } @SuppressWarnings("UnusedDeclaration") public void setKey(String key) { this.key = key; } @SuppressWarnings("UnusedDeclaration") public void setValue(String value) { this.value = value; } } private static class ResourceFileFilter implements FileFilter { private final List<String> includedFiles; public ResourceFileFilter(String[] includedFiles) { this.includedFiles = Arrays.asList(includedFiles); } @Override public boolean accept(File pathname) { return includedFiles.contains(pathname.getName()); } } /** * Safemode can be UNSAFE, SAFE, SERVER, SECURE. * * @param s New value of safe. Case is ignored. Not required-default is SAFE. */ public void setSafemode(String s) { safe = SafeMode.valueOf(s.toUpperCase()); } @SuppressWarnings("UnusedDeclaration") public Extension createPreProcessor() { Extension extension = new Extension(); preProcessors.add(extension); return extension; } @SuppressWarnings("UnusedDeclaration") public Extension createTreeProcessor() { Extension extension = new Extension(); treeProcessors.add(extension); return extension; } @SuppressWarnings("UnusedDeclaration") public Extension createPostProcessor() { Extension extension = new Extension(); postProcessors.add(extension); return extension; } @SuppressWarnings("UnusedDeclaration") public Extension createBlockProcessor() { Extension extension = new Extension(); blockProcessors.add(extension); return extension; } @SuppressWarnings("UnusedDeclaration") public Extension createBlockMacroProcessor() { Extension extension = new Extension(); blockMacroProcessors.add(extension); return extension; } @SuppressWarnings("UnusedDeclaration") public Extension createInlineMacroProcessor() { Extension extension = new Extension(); inlineMacroProcessors.add(extension); return extension; } @SuppressWarnings("UnusedDeclaration") public Extension createIncludeProcessor() { Extension extension = new Extension(); includeProcessors.add(extension); return extension; } public class Extension { private String className; private String blockName; public String getClassName() { return className; } public String getBlockName() { return blockName; } @SuppressWarnings("UnusedDeclaration") public void setClassName(String className) { this.className = className; } @SuppressWarnings("UnusedDeclaration") public void setBlockName(String blockName) { this.blockName = blockName; } } @SuppressWarnings("UnusedDeclaration") public void setGemPaths(String gemPaths) { this.gemPaths = gemPaths; } @SuppressWarnings("UnusedDeclaration") public RubyLibrary createRequire() { RubyLibrary rubyLibrary = new RubyLibrary(); requires.add(rubyLibrary); return rubyLibrary; } public class RubyLibrary { private String name; public String getName() { return name; } @SuppressWarnings("UnusedDeclaration") public void setName(String name) { this.name = name; } } }