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 = ~