Configure ASCIIDOCTOR formatter Set options and attributes for asciidoc generation, include CSS and set the safe mode to SECURE. A new configuration parameter allows to control whether a Table Of Contents should be included at the beginning of each document. For now this is a global configuration, but a follow-up change should make this configurable per project. Change-Id: I51faf84e8edd6a6e515e8f2693acd5d45929501f Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java index cbb6201..fb791d6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java
@@ -25,6 +25,7 @@ public static final String KEY_ALLOW_HTML = "allowHtml"; public static final String KEY_ENABLED = "enabled"; public static final String KEY_EXT = "ext"; + public static final String KEY_INCLUDE_TOC = "includeToc"; public static final String KEY_MIME_TYPE = "mimeType"; public static final String KEY_PREFIX = "prefix";
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java index 99f5792..758a7c2 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java
@@ -38,6 +38,7 @@ import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; @@ -103,10 +104,16 @@ && RawText.isBinary(bytes)) { return Resources.METHOD_NOT_ALLOWED; } - String html = - formatter.get().format(key.getProject().get(), formatterCfg, - replaceMacros(key.getProject(), bytes)); - return getAsHtmlResource(html, commit.getCommitTime()); + ObjectReader reader = repo.newObjectReader(); + try { + String html = + formatter.get().format(key.getProject().get(), + reader.abbreviate(key.getRevId()).name(), formatterCfg, + replaceMacros(key.getProject(), bytes)); + return getAsHtmlResource(html, commit.getCommitTime()); + } finally { + reader.release(); + } } finally { tw.release(); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java index d15ab42..5517fbe 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java
@@ -14,20 +14,133 @@ package com.googlesource.gerrit.plugins.xdocs.formatter; +import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_INCLUDE_TOC; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.io.ByteStreams; +import com.google.gerrit.common.TimeUtil; +import com.google.gerrit.extensions.annotations.PluginData; +import com.google.inject.Inject; +import com.google.inject.Singleton; + import com.googlesource.gerrit.plugins.xdocs.ConfigSection; import org.asciidoctor.Asciidoctor; +import org.asciidoctor.Attributes; +import org.asciidoctor.AttributesBuilder; +import org.asciidoctor.Options; +import org.asciidoctor.OptionsBuilder; +import org.asciidoctor.SafeMode; +import org.eclipse.jgit.util.TemporaryBuffer; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; -import java.util.HashMap; +import java.io.InputStream; +import java.net.URL; +import java.util.Properties; +@Singleton public class AsciidoctorFormatter implements Formatter { - public final static String NAME = "ASCIIDOCTOR"; + public static final String NAME = "ASCIIDOCTOR"; + + private static final String BACKEND = "html5"; + private static final String DOCTYPE = "article"; + private static final String ERUBY = "erb"; + + private final File baseDir; + private final String css; + private final Properties attributes; + + @Inject + public AsciidoctorFormatter(@PluginData File baseDir) throws IOException { + this.baseDir = baseDir; + this.css = readCss(); + this.attributes = readAttributes(); + } @Override - public String format(String projectName, ConfigSection cfg, String raw) - throws IOException { - return Asciidoctor.Factory.create(AsciidoctorFormatter.class.getClassLoader()) - .convert(raw, new HashMap<String, Object>()); + public String format(String projectName, String revision, ConfigSection cfg, + String raw) throws IOException { + // asciidoctor ignores all attributes if no output file is specified, + // this is why we must specify an output file and then read its content + File tmpFile = + new File(baseDir, "tmp/asciidoctor-" + TimeUtil.nowTs().getNanos() + ".tmp"); + try { + Asciidoctor.Factory.create(AsciidoctorFormatter.class.getClassLoader()) + .render(raw, createOptions(cfg, revision, tmpFile)); + try (FileInputStream input = new FileInputStream(tmpFile)) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteStreams.copy(input, out); + return insertCss(out.toString(UTF_8.name())); + } + } finally { + if (!tmpFile.delete()) { + tmpFile.deleteOnExit(); + } + } + } + + private Options createOptions(ConfigSection cfg, String revision, File out) { + return OptionsBuilder.options() + .backend(BACKEND) + .docType(DOCTYPE) + .eruby(ERUBY) + .safe(SafeMode.SECURE) + .attributes(getAttributes(cfg, revision)) + .mkDirs(true) + .toFile(out) + .get(); + } + + private Attributes getAttributes(ConfigSection cfg, String revision) { + AttributesBuilder ab = AttributesBuilder.attributes() + .tableOfContents(cfg.getBoolean(KEY_INCLUDE_TOC, true)) + .sourceHighlighter("prettify"); + for (String name : attributes.stringPropertyNames()) { + ab.attribute(name, attributes.getProperty(name)); + } + ab.attribute("last-update-label!"); + ab.attribute("revnumber", revision); + return ab.get(); + } + + private String insertCss(String html) { + int p = html.lastIndexOf("</head>"); + if (p > 0) { + StringBuilder b = new StringBuilder(); + b.append(html.substring(0, p)); + b.append("<style type=\"text/css\">\n"); + b.append(css); + b.append("</style>\n"); + b.append(html.substring(p)); + return b.toString(); + } else { + return html; + } + } + + private static String readCss() throws IOException { + String name = "asciidoctor.css"; + URL url = AsciidoctorFormatter.class.getResource(name); + if (url == null) { + throw new FileNotFoundException("Resource " + name); + } + try (InputStream in = url.openStream(); + TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap(128 * 1024)) { + tmp.copy(in); + return new String(tmp.toByteArray(), UTF_8); + } + } + + private static Properties readAttributes() throws IOException { + Properties attributes = new Properties(); + try (InputStream in = AsciidoctorFormatter.class + .getResourceAsStream("asciidoctor.properties")) { + attributes.load(in); + } + return attributes; } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/Formatter.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/Formatter.java index 9432983..f58efee 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/Formatter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/Formatter.java
@@ -28,11 +28,13 @@ * * @param projectName the name of the project that contains the file to be * formatted + * @param revision the abbreviated revision from which the file is loaded * @param cfg the configuration for this formatter * @param raw the raw text * @return the given text formatted as html * @throws IOException thrown if the formatting fails */ - public String format(String projectName, ConfigSection cfg, String raw) + public String format(String projectName, String revision, ConfigSection cfg, + String raw) throws IOException; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/MarkdownFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/MarkdownFormatter.java index 3fa1b64..0f2b2f3 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/MarkdownFormatter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/MarkdownFormatter.java
@@ -52,8 +52,8 @@ } @Override - public String format(String projectName, ConfigSection cfg, String raw) - throws UnsupportedEncodingException { + public String format(String projectName, String revision, ConfigSection cfg, + String raw) throws UnsupportedEncodingException { com.google.gerrit.server.documentation.MarkdownFormatter f = new com.google.gerrit.server.documentation.MarkdownFormatter(); if (!cfg.getBoolean(KEY_ALLOW_HTML, false)) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/PlainTextFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/PlainTextFormatter.java index 625d422..0c79bd5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/PlainTextFormatter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/PlainTextFormatter.java
@@ -22,7 +22,8 @@ public final static String NAME = "PLAIN_TEXT"; @Override - public String format(String projectName, ConfigSection cfg, String raw) { + public String format(String projectName, String revision, ConfigSection cfg, + String raw) { return "<pre>" + escapeHtml(raw) + "</pre>"; } }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index 83b56cc..c18c697 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -145,3 +145,13 @@ *CANNOT* be overridden on project-level. Default: `true` + +<a id="formatterIncludeToc"> +formatter.<formatter>.includeToc +: Whether a Table Of Contents should be included into each document. + + *CANNOT* be overridden on project-level. + + Supported for the following formatters: `ASCIIDOCTOR` + + Default: `true`
diff --git a/src/main/resources/com/googlesource/gerrit/plugins/xdocs/formatter/asciidoctor.css b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/formatter/asciidoctor.css new file mode 100644 index 0000000..c49b596 --- /dev/null +++ b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/formatter/asciidoctor.css
@@ -0,0 +1,54 @@ +body { + margin: 1em auto; + width: 900px; +} + +#toctitle { + margin-top: 0.5em; + font-weight: bold; +} + +h1, h2, h3, h4, h5, h6, #toctitle { + color: #527bbd; + font-family: sans-serif; +} + +h1, h2, h3 { + border-bottom: 2px solid silver; +} + +p { + margin: 0.5em 0 0.5em 0; +} +li p { + margin: 0.2em 0 0.2em 0; +} + +.listingblock > .content { + border: 2px solid silver; + background: #ebebeb; + margin-left: 2em; + color: darkgreen; + padding: 2px; + overflow: auto; +} + +.listingblock > .content pre { + background: none; + border: 0 solid silver; + padding: 0 0 0 0; +} + +dl dt { + margin-top: 1em; +} + +table.tableblock { + border-collapse: collapse; +} + +table.tableblock, +th.tableblock, +td.tableblock { + border: 1px solid #EEE; +}
diff --git a/src/main/resources/com/googlesource/gerrit/plugins/xdocs/formatter/asciidoctor.properties b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/formatter/asciidoctor.properties new file mode 100644 index 0000000..9509f08 --- /dev/null +++ b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/formatter/asciidoctor.properties
@@ -0,0 +1,7 @@ +newline = \n +asterisk = * +plus = + +caret = ^ +startsb = [ +endsb = ] +tilde = ~