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