Allow projects to define own CSS for Markdown files
Projects may have different preferences of how their Markdown
documentation should be formatted. By default the standard Gerrit CSS
for Markdown files is used, but now projects can override it by custom
CSS that is stored in the project's refs/meta/config branch as
'xdocs/markdown.css'.
The current revision of the refs/meta/config branch is included in the
cache key of the project documentation so that the project
documentation is newly formatted if there was a change in this branch.
This ensures that the new CSS is immediately active.
Custom CSS is not inherited from parent projects.
Change-Id: I909ed431a103aa6a6561ae8fc1d558bfb57f6af8
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java
index 7c89d92..f84efd7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java
@@ -17,6 +17,8 @@
import com.google.common.cache.LoadingCache;
import com.google.gerrit.httpd.resources.Resource;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
@@ -25,20 +27,38 @@
import com.googlesource.gerrit.plugins.xdocs.formatter.Formatters.FormatterProvider;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
@Singleton
public class XDocCache {
private final LoadingCache<String, Resource> cache;
+ private final GitRepositoryManager repoManager;
@Inject
XDocCache(
- @Named(XDocLoader.Module.X_DOC_RESOURCES) LoadingCache<String, Resource> cache) {
+ @Named(XDocLoader.Module.X_DOC_RESOURCES) LoadingCache<String, Resource> cache,
+ GitRepositoryManager repoManager) {
this.cache = cache;
+ this.repoManager = repoManager;
}
public Resource get(FormatterProvider formatter, Project.NameKey project,
String file, ObjectId revId) {
+ ObjectId metaConfigRevId;
+ try {
+ Repository repo = repoManager.openRepository(project);
+ try {
+ metaConfigRevId = repo.resolve(RefNames.REFS_CONFIG);
+ } finally {
+ repo.close();
+ }
+ } catch (IOException e) {
+ return null;
+ }
+
return cache.getUnchecked((new XDocResourceKey(formatter.getName(),
- project, file, revId)).asString());
+ project, file, revId, metaConfigRevId)).asString());
}
}
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 3705f33..99f5792 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java
@@ -104,7 +104,7 @@
return Resources.METHOD_NOT_ALLOWED;
}
String html =
- formatter.get().format(formatterCfg,
+ formatter.get().format(key.getProject().get(), formatterCfg,
replaceMacros(key.getProject(), bytes));
return getAsHtmlResource(html, commit.getCommitTime());
} finally {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java
index 2407490..feb2bdd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java
@@ -25,13 +25,15 @@
private final Project.NameKey project;
private final String resource;
private final ObjectId revId;
+ private final ObjectId metaConfigRevId;
XDocResourceKey(String formatter, Project.NameKey project, String r,
- ObjectId revId) {
+ ObjectId revId, ObjectId metaConfigRevId) {
this.formatter = formatter;
this.project = project;
this.resource = r;
this.revId = revId;
+ this.metaConfigRevId = metaConfigRevId;
}
public String getFormatter() {
@@ -74,6 +76,8 @@
b.append(resource != null ? IdString.fromDecoded(resource).encoded() : "");
b.append("/");
b.append(revId != null ? revId.name() : "");
+ b.append("/");
+ b.append(metaConfigRevId != null ? metaConfigRevId.name() : "");
return b.toString();
}
@@ -83,6 +87,7 @@
String project = null;
String file = null;
String revision = null;
+ String metaConfigRevision = null;
if (s.length > 0) {
formatter = IdString.fromUrl(s[0]).get();
}
@@ -95,7 +100,10 @@
if (s.length > 3) {
revision = s[3];
}
+ if (s.length > 4) {
+ metaConfigRevision = s[4];
+ }
return new XDocResourceKey(formatter, new Project.NameKey(project), file,
- ObjectId.fromString(revision));
+ ObjectId.fromString(revision), ObjectId.fromString(metaConfigRevision));
}
}
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 a1d1dce..d15ab42 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
@@ -25,7 +25,8 @@
public final static String NAME = "ASCIIDOCTOR";
@Override
- public String format(ConfigSection cfg, String raw) throws IOException {
+ public String format(String projectName, ConfigSection cfg, String raw)
+ throws IOException {
return Asciidoctor.Factory.create(AsciidoctorFormatter.class.getClassLoader())
.convert(raw, new HashMap<String, Object>());
}
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 e934c0d..9432983 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
@@ -26,10 +26,13 @@
/**
* Formats the given raw text as html.
*
+ * @param projectName the name of the project that contains the file to be
+ * formatted
* @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(ConfigSection cfg, String raw) throws IOException;
+ public String format(String projectName, 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 6a3a896..3fa1b64 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
@@ -16,23 +16,89 @@
import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_ALLOW_HTML;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.xdocs.ConfigSection;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class MarkdownFormatter implements Formatter {
public final static String NAME = "MARKDOWN";
+ private final GitRepositoryManager repoManager;
+ private final String pluginName;
+
+ @Inject
+ MarkdownFormatter(@PluginName String pluginName,
+ GitRepositoryManager repoManager) {
+ this.pluginName = pluginName;
+ this.repoManager = repoManager;
+ }
+
@Override
- public String format(ConfigSection cfg, String raw)
+ public String format(String projectName, 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)) {
f.suppressHtml();
}
+ // if there is no project-specific CSS and f.setCss(null) is invoked
+ // com.google.gerrit.server.documentation.MarkdownFormatter applies the
+ // default CSS
+ f.setCss(getCss(projectName));
byte[] b = f.markdownToDocHtml(raw, UTF_8.name());
return new String(b, UTF_8);
}
+
+ private String getCss(String projectName) {
+ try {
+ Repository repo =
+ repoManager.openRepository(new Project.NameKey(projectName));
+ try {
+ RevWalk rw = new RevWalk(repo);
+ try {
+ RevCommit commit = rw.parseCommit(repo.resolve(RefNames.REFS_CONFIG));
+ RevTree tree = commit.getTree();
+ TreeWalk tw = new TreeWalk(repo);
+ try {
+ tw.addTree(tree);
+ tw.setRecursive(true);
+ tw.setFilter(PathFilter.create(pluginName + "/markdown.css"));
+ if (!tw.next()) {
+ return null;
+ }
+ ObjectId objectId = tw.getObjectId(0);
+ ObjectLoader loader = repo.open(objectId);
+ byte[] raw = loader.getBytes(Integer.MAX_VALUE);
+ return escapeHtml(new String(raw, UTF_8));
+ } finally {
+ tw.release();
+ }
+ } finally {
+ rw.release();
+ }
+ } finally {
+ repo.close();
+ }
+ } catch (IOException e) {
+ return null;
+ }
+ }
}
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 d6ad0cd..625d422 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,7 @@
public final static String NAME = "PLAIN_TEXT";
@Override
- public String format(ConfigSection cfg, String raw) {
+ public String format(String projectName, 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 f8e4c57..83b56cc 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -54,6 +54,17 @@
Overrides the [globally configured prefixes](#formatterPrefix)
for this formatter.
+<a id="projectCss">
+Project-Specific CSS
+--------------------
+
+For some formatters a custom CSS file for the rendering can be
+provided in the `refs/meta/config` branch of the project:
+
+* `MARKDOWN`: `xdoc/markdown.css`
+
+Custom CSS files are *NOT* inherited from parent projects.
+
<a id="globalConfig">
Global Configuration
--------------------