|  | // 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. | 
|  |  | 
|  | package com.google.gerrit.asciidoctor; | 
|  |  | 
|  | import static java.nio.charset.StandardCharsets.UTF_8; | 
|  |  | 
|  | import com.google.common.io.ByteStreams; | 
|  | import java.io.BufferedReader; | 
|  | import java.io.File; | 
|  | 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; | 
|  | import org.kohsuke.args4j.ParserProperties; | 
|  |  | 
|  | 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); | 
|  |  | 
|  | attributeValues.put(name, value); | 
|  | } else { | 
|  | attributeValues.put(attribute, ""); | 
|  | } | 
|  | } | 
|  |  | 
|  | return attributeValues; | 
|  | } | 
|  |  | 
|  | private void invoke(String... parameters) throws IOException { | 
|  | CmdLineParser parser = new CmdLineParser(this, ParserProperties.defaults().withAtSyntax(false)); | 
|  | try { | 
|  | parser.parseArgument(parameters); | 
|  | if (inputFiles.isEmpty()) { | 
|  | throw new IllegalArgumentException("asciidoctor: FAILED: input file missing"); | 
|  | } | 
|  | } catch (CmdLineException | IllegalArgumentException e) { | 
|  | System.err.println(e.getMessage()); | 
|  | parser.printUsage(System.err); | 
|  | System.exit(1); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (revnumberFile != null) { | 
|  | try (BufferedReader reader = Files.newBufferedReader(revnumberFile.toPath(), UTF_8)) { | 
|  | 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((dir, name) -> 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); | 
|  | } | 
|  | } | 
|  | } |