// Copyright (C) 2013 The Android Open Source Project // // 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. import com.google.common.io.ByteStreams; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.asciidoctor.Asciidoctor; import org.asciidoctor.AttributesBuilder; import org.asciidoctor.Options; import org.asciidoctor.OptionsBuilder; import org.asciidoctor.SafeMode; import org.asciidoctor.internal.JRubyAsciidoctor; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; public class AsciiDoctor { private static final String DOCTYPE = "article"; private static final String ERUBY = "erb"; private static final String REVNUMBER_NAME = "revnumber"; @Option(name = "-b", usage = "set output format backend") private String backend = "html5"; @Option(name = "-z", usage = "output zip file") private String zipFile; @Option(name = "--in-ext", usage = "extension for input files") private String inExt = ".txt"; @Option(name = "--out-ext", usage = "extension for output files") private String outExt = ".html"; @Option(name = "--base-dir", usage = "base directory") private File basedir; @Option(name = "--tmp", usage = "temporary output path") private File tmpdir; @Option(name = "--mktmp", usage = "create a temporary output path") private boolean mktmp; @Option(name = "-a", usage = "a list of attributes, in the form key or key=value pair") private List<String> attributes = new ArrayList<>(); @Option( name = "--bazel", usage = "bazel mode: generate multiple output files instead of a single zip file" ) private boolean bazel; @Option(name = "--revnumber-file", usage = "the file contains revnumber string") private File revnumberFile; @Argument(usage = "input files") private List<String> inputFiles = new ArrayList<>(); private String revnumber; public static String mapInFileToOutFile(String inFile, String inExt, String outExt) { String basename = new File(inFile).getName(); if (basename.endsWith(inExt)) { basename = basename.substring(0, basename.length() - inExt.length()); } else { // Strip out the last extension int pos = basename.lastIndexOf('.'); if (pos > 0) { basename = basename.substring(0, pos); } } return basename + outExt; } private Options createOptions(File base, File outputFile) { OptionsBuilder optionsBuilder = OptionsBuilder.options(); optionsBuilder .backend(backend) .docType(DOCTYPE) .eruby(ERUBY) .safe(SafeMode.UNSAFE) .baseDir(base) .toFile(outputFile); AttributesBuilder attributesBuilder = AttributesBuilder.attributes(); attributesBuilder.attributes(getAttributes()); if (revnumber != null) { attributesBuilder.attribute(REVNUMBER_NAME, revnumber); } optionsBuilder.attributes(attributesBuilder.get()); return optionsBuilder.get(); } private Map<String, Object> getAttributes() { Map<String, Object> attributeValues = new HashMap<>(); for (String attribute : attributes) { int equalsIndex = attribute.indexOf('='); if (equalsIndex > -1) { String name = attribute.substring(0, equalsIndex); String value = attribute.substring(equalsIndex + 1, attribute.length()); attributeValues.put(name, value); } else { attributeValues.put(attribute, ""); } } return attributeValues; } private void invoke(String... parameters) throws IOException { CmdLineParser parser = new CmdLineParser(this); try { parser.parseArgument(parameters); if (inputFiles.isEmpty()) { throw new CmdLineException(parser, "asciidoctor: FAILED: input file missing"); } } catch (CmdLineException e) { System.err.println(e.getMessage()); parser.printUsage(System.err); System.exit(1); return; } if (revnumberFile != null) { try (BufferedReader reader = new BufferedReader(new FileReader(revnumberFile))) { revnumber = reader.readLine(); } } if (mktmp) { tmpdir = Files.createTempDirectory("asciidoctor-").toFile(); } if (bazel) { renderFiles(inputFiles, null); } else { try (ZipOutputStream zip = new ZipOutputStream(Files.newOutputStream(Paths.get(zipFile)))) { renderFiles(inputFiles, zip); File[] cssFiles = tmpdir.listFiles( new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".css"); } }); for (File css : cssFiles) { zipFile(css, css.getName(), zip); } } } } private void renderFiles(List<String> inputFiles, ZipOutputStream zip) throws IOException { Asciidoctor asciidoctor = JRubyAsciidoctor.create(); for (String inputFile : inputFiles) { String outName = mapInFileToOutFile(inputFile, inExt, outExt); File out = bazel ? new File(outName) : new File(tmpdir, outName); if (!bazel) { out.getParentFile().mkdirs(); } File input = new File(inputFile); Options options = createOptions(basedir != null ? basedir : input.getParentFile(), out); asciidoctor.renderFile(input, options); if (zip != null) { zipFile(out, outName, zip); } } } public static void zipFile(File file, String name, ZipOutputStream zip) throws IOException { zip.putNextEntry(new ZipEntry(name)); try (InputStream input = Files.newInputStream(file.toPath())) { ByteStreams.copy(input, zip); } zip.closeEntry(); } public static void main(String[] args) { try { new AsciiDoctor().invoke(args); } catch (IOException e) { System.err.println(e.getMessage()); System.exit(1); } } }