Merge "Support inheriting of CSS"
diff --git a/BUCK b/BUCK
index 99a8eca..cccde03 100644
--- a/BUCK
+++ b/BUCK
@@ -2,7 +2,7 @@
MODULE = 'com.googlesource.gerrit.plugins.xdocs.XDocs'
-ASCIIDOCTOR = '//lib/asciidoctor:asciidoc_lib' if __standalone_mode__ \
+ASCIIDOCTOR = '//lib/asciidoctor:asciidoc_lib' if STANDALONE_MODE \
else '//plugins/x-docs/lib/asciidoctor:asciidoc_lib'
gerrit_plugin(
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 cf0971c..7971096 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java
@@ -22,6 +22,8 @@
import com.google.common.cache.Weigher;
import com.google.common.collect.Maps;
import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.httpd.resources.Resource;
import com.google.gerrit.httpd.resources.SmallResource;
import com.google.gerrit.reviewdb.client.Project;
@@ -33,8 +35,11 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.xdocs.formatter.Formatter;
import com.googlesource.gerrit.plugins.xdocs.formatter.Formatters;
import com.googlesource.gerrit.plugins.xdocs.formatter.Formatters.FormatterProvider;
+import com.googlesource.gerrit.plugins.xdocs.formatter.StreamFormatter;
+import com.googlesource.gerrit.plugins.xdocs.formatter.StringFormatter;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -48,14 +53,19 @@
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Singleton
public class XDocLoader extends CacheLoader<String, Resource> {
+ private static final Logger log = LoggerFactory.getLogger(XDocLoader.class);
+
private static final String DEFAULT_HOST = "review.example.com";
private final GitRepositoryManager repoManager;
@@ -80,60 +90,118 @@
@Override
public Resource load(String strKey) throws Exception {
XDocResourceKey key = XDocResourceKey.fromString(strKey);
- XDocGlobalConfig cfg =
- new XDocGlobalConfig(cfgFactory.getGlobalPluginConfig(pluginName));
- FormatterProvider formatter = formatters.getByName(key.getFormatter());
- if (formatter == null) {
- return Resource.NOT_FOUND;
- }
- ConfigSection formatterCfg = cfg.getFormatterConfig(formatter.getName());
- Repository repo = repoManager.openRepository(key.getProject());
try {
- RevWalk rw = new RevWalk(repo);
+ FormatterProvider formatter = getFormatter(key.getFormatter());
+ Repository repo = repoManager.openRepository(key.getProject());
try {
- ObjectId revId = key.getRevId();
- if (revId == null) {
- return Resource.NOT_FOUND;
- }
- RevCommit commit = rw.parseCommit(revId);
- RevTree tree = commit.getTree();
- TreeWalk tw = new TreeWalk(repo);
+ RevWalk rw = new RevWalk(repo);
try {
- tw.addTree(tree);
- tw.setRecursive(true);
- tw.setFilter(PathFilter.create(key.getResource()));
- if (!tw.next()) {
- return Resource.NOT_FOUND;
- }
- ObjectId objectId = tw.getObjectId(0);
- ObjectLoader loader = repo.open(objectId);
- byte[] bytes = loader.getBytes(Integer.MAX_VALUE);
- boolean isBinary = RawText.isBinary(bytes);
- if (formatter.getName().equals(Formatters.RAW_FORMATTER) && isBinary) {
- return Resources.METHOD_NOT_ALLOWED;
- }
- ObjectReader reader = repo.newObjectReader();
+ ObjectId revId = checkRevId(key.getRevId());
+ RevCommit commit = rw.parseCommit(revId);
+ RevTree tree = commit.getTree();
+ TreeWalk tw = new TreeWalk(repo);
try {
- String abbrRevId = reader.abbreviate(revId).name();
- String raw = new String(bytes, UTF_8);
- if (!isBinary) {
- raw = replaceMacros(repo, key.getProject(), revId, abbrRevId, raw);
+ tw.addTree(tree);
+ tw.setRecursive(true);
+ tw.setFilter(PathFilter.create(key.getResource()));
+ if (!tw.next()) {
+ throw new ResourceNotFoundException();
}
+ ObjectId objectId = tw.getObjectId(0);
+ ObjectLoader loader = repo.open(objectId);
String html =
- formatter.get().format(key.getProject().get(),
- abbrRevId, formatterCfg, raw);
+ getHtml(formatter, repo, loader, key.getProject(), revId);
return getAsHtmlResource(html, commit.getCommitTime());
} finally {
- reader.release();
+ tw.release();
}
} finally {
- tw.release();
+ rw.release();
}
} finally {
- rw.release();
+ repo.close();
}
+ } catch (ResourceNotFoundException e) {
+ return Resource.NOT_FOUND;
+ } catch (MethodNotAllowedException e) {
+ return Resources.METHOD_NOT_ALLOWED;
+ }
+ }
+
+ private FormatterProvider getFormatter(String formatterName)
+ throws ResourceNotFoundException {
+ FormatterProvider formatter = formatters.getByName(formatterName);
+ if (formatter == null) {
+ throw new ResourceNotFoundException();
+ }
+ return formatter;
+ }
+
+ private static ObjectId checkRevId(ObjectId revId)
+ throws ResourceNotFoundException {
+ if (revId == null) {
+ throw new ResourceNotFoundException();
+ }
+ return revId;
+ }
+
+ private String getHtml(FormatterProvider formatter, Repository repo,
+ ObjectLoader loader, Project.NameKey project, ObjectId revId)
+ throws MethodNotAllowedException, IOException, GitAPIException,
+ ResourceNotFoundException {
+ Formatter f = formatter.get();
+ if (f instanceof StringFormatter) {
+ return getHtml(formatter.getName(), (StringFormatter) f, repo, loader,
+ project, revId);
+ } else if (f instanceof StreamFormatter) {
+ return getHtml(formatter.getName(), (StreamFormatter) f, repo, loader,
+ project, revId);
+ } else {
+ log.error(String.format("Unsupported formatter: %s", formatter.getName()));
+ throw new ResourceNotFoundException();
+ }
+ }
+
+ private String getHtml(String formatterName, StringFormatter f,
+ Repository repo, ObjectLoader loader, Project.NameKey project,
+ ObjectId revId) throws MethodNotAllowedException, IOException,
+ GitAPIException {
+ byte[] bytes = loader.getBytes(Integer.MAX_VALUE);
+ boolean isBinary = RawText.isBinary(bytes);
+ if (formatterName.equals(Formatters.RAW_FORMATTER) && isBinary) {
+ throw new MethodNotAllowedException();
+ }
+ String raw = new String(bytes, UTF_8);
+ String abbrRevId = getAbbrRevId(repo, revId);
+ if (!isBinary) {
+ raw = replaceMacros(repo, project, revId, abbrRevId, raw);
+ }
+ return f.format(project.get(), abbrRevId,
+ getFormatterConfig(formatterName), raw);
+ }
+
+ private String getHtml(String formatterName, StreamFormatter f,
+ Repository repo, ObjectLoader loader, Project.NameKey project,
+ ObjectId revId) throws IOException {
+ try (InputStream raw = loader.openStream()) {
+ return ((StreamFormatter) f).format(project.get(),
+ getAbbrRevId(repo, revId), getFormatterConfig(formatterName), raw);
+ }
+ }
+
+ private ConfigSection getFormatterConfig(String formatterName) {
+ XDocGlobalConfig cfg =
+ new XDocGlobalConfig(cfgFactory.getGlobalPluginConfig(pluginName));
+ return cfg.getFormatterConfig(formatterName);
+ }
+
+ private static String getAbbrRevId(Repository repo, ObjectId revId)
+ throws IOException {
+ ObjectReader reader = repo.newObjectReader();
+ try {
+ return reader.abbreviate(revId).name();
} finally {
- repo.close();
+ reader.release();
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocServlet.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocServlet.java
index f557bee..78520f7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocServlet.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocServlet.java
@@ -23,6 +23,7 @@
import com.google.common.net.HttpHeaders;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.httpd.resources.Resource;
import com.google.gerrit.httpd.resources.SmallResource;
@@ -106,104 +107,52 @@
@Override
public void service(HttpServletRequest req, HttpServletResponse res)
throws IOException {
- if (!"GET".equals(req.getMethod()) && !"HEAD".equals(req.getMethod())) {
- CacheHeaders.setNotCacheable(res);
- res.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
- return;
- }
+ try {
+ validateRequestMethod(req);
- ResourceKey key = ResourceKey.fromPath(req.getPathInfo());
- ProjectState state = projectCache.get(key.project);
- if (state == null) {
- Resource.NOT_FOUND.send(req, res);
- return;
- }
- XDocProjectConfig cfg = cfgFactory.create(state);
- if (key.file == null) {
- res.sendRedirect(getRedirectUrl(req, key, cfg));
- return;
- }
+ ResourceKey key = ResourceKey.fromPath(req.getPathInfo());
+ ProjectState state = getProject(key);
+ XDocProjectConfig cfg = cfgFactory.create(state);
- MimeType mimeType = fileTypeRegistry.getMimeType(key.file, null);
- FormatterProvider formatter;
- if (req.getParameter("raw") != null) {
- formatter = formatters.getRawFormatter();
- } else {
- formatter = formatters.get(state, key.file);
- if (formatter == null
- && !("image".equals(mimeType.getMediaType())
- && fileTypeRegistry.isSafeInline(mimeType))) {
- Resource.NOT_FOUND.send(req, res);
+ if (key.file == null) {
+ res.sendRedirect(getRedirectUrl(req, key, cfg));
return;
}
- }
- try {
+ MimeType mimeType = fileTypeRegistry.getMimeType(key.file, null);
+ FormatterProvider formatter = getFormatter(req, key);
+ if (formatter == null && !isSafeImage(mimeType)) {
+ throw new ResourceNotFoundException();
+ }
+
ProjectControl projectControl = projectControlFactory.validateFor(key.project);
- String rev = key.revision;
- if (rev == null) {
- rev = cfg.getIndexRef();
- }
- if (Constants.HEAD.equals(rev)) {
- rev = getHead.get().apply(new ProjectResource(projectControl));
- } else {
- if (!ObjectId.isId(rev)) {
- if (!rev.startsWith(Constants.R_REFS)) {
- rev = Constants.R_HEADS + rev;
- }
- if (!projectControl.controlForRef(rev).isVisible()) {
- Resource.NOT_FOUND.send(req, res);
- return;
- }
- }
- }
+ String rev = getRevision(cfg, key.revision, projectControl);
Repository repo = repoManager.openRepository(key.project);
try {
- ObjectId revId =
- repo.resolve(MoreObjects.firstNonNull(rev, Constants.HEAD));
- if (revId == null) {
- Resource.NOT_FOUND.send(req, res);
- return;
- }
+ ObjectId revId = resolveRevision(repo, rev);
if (ObjectId.isId(rev)) {
- RevWalk rw = new RevWalk(repo);
- try {
- RevCommit commit = rw.parseCommit(repo.resolve(rev));
- if (!projectControl.canReadCommit(db.get(), rw, commit)) {
- Resource.NOT_FOUND.send(req, res);
- return;
- }
- } finally {
- rw.release();
- }
+ validateCanReadCommit(repo, projectControl, revId);
}
- String eTag = null;
- String receivedETag = req.getHeader(HttpHeaders.IF_NONE_MATCH);
- if (receivedETag != null) {
- eTag = computeETag(key.project, revId, key.file);
- if (eTag.equals(receivedETag)) {
- res.sendError(SC_NOT_MODIFIED);
- return;
- }
+ if (isResourceNotModified(req, key, revId)) {
+ res.sendError(SC_NOT_MODIFIED);
+ return;
}
Resource rsc;
if (formatter != null) {
rsc = docCache.get(formatter, key.project, key.file, revId);
- } else if ("image".equals(mimeType.getMediaType())) {
+ } else if (isImage(mimeType)) {
rsc = getImageResource(repo, revId, key.file);
} else {
rsc = Resource.NOT_FOUND;
}
if (rsc != Resource.NOT_FOUND) {
- res.setHeader(
- HttpHeaders.ETAG,
- MoreObjects.firstNonNull(eTag,
- computeETag(key.project, revId, key.file)));
+ res.setHeader(HttpHeaders.ETAG,
+ computeETag(key.project, revId, key.file));
}
CacheHeaders.setCacheablePrivate(res, 7, TimeUnit.DAYS, false);
rsc.send(req, res);
@@ -214,7 +163,9 @@
} catch (RepositoryNotFoundException | NoSuchProjectException
| ResourceNotFoundException | AuthException | RevisionSyntaxException e) {
Resource.NOT_FOUND.send(req, res);
- return;
+ } catch (MethodNotAllowedException e) {
+ CacheHeaders.setNotCacheable(res);
+ res.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
}
@@ -236,8 +187,7 @@
byte[] content = loader.getBytes(Integer.MAX_VALUE);
MimeType mimeType = fileTypeRegistry.getMimeType(file, content);
- if (!"image".equals(mimeType.getMediaType())
- || !fileTypeRegistry.isSafeInline(mimeType)) {
+ if (!isSafeImage(mimeType)) {
return Resource.NOT_FOUND;
}
return new SmallResource(content)
@@ -254,6 +204,94 @@
}
}
+ private static void validateRequestMethod(HttpServletRequest req)
+ throws MethodNotAllowedException {
+ if (!("GET".equals(req.getMethod()) || "HEAD".equals(req.getMethod()))) {
+ throw new MethodNotAllowedException();
+ }
+ }
+
+ private ProjectState getProject(ResourceKey key)
+ throws ResourceNotFoundException {
+ ProjectState state = projectCache.get(key.project);
+ if (state == null) {
+ throw new ResourceNotFoundException();
+ }
+ return state;
+ }
+
+ private FormatterProvider getFormatter(HttpServletRequest req, ResourceKey key)
+ throws ResourceNotFoundException {
+ if (req.getParameter("raw") != null) {
+ return formatters.getRawFormatter();
+ } else {
+ return formatters.get(getProject(key), key.file);
+ }
+ }
+
+ private boolean isSafeImage(MimeType mimeType) {
+ return isImage(mimeType) && fileTypeRegistry.isSafeInline(mimeType);
+ }
+
+ private static boolean isImage(MimeType mimeType) {
+ return "image".equals(mimeType.getMediaType());
+ }
+
+ private String getRevision(XDocProjectConfig cfg, String revision,
+ ProjectControl projectControl) throws ResourceNotFoundException,
+ AuthException, IOException {
+ String rev = revision;
+ if (rev == null) {
+ rev = cfg.getIndexRef();
+ }
+ if (Constants.HEAD.equals(rev)) {
+ rev = getHead.get().apply(new ProjectResource(projectControl));
+ } else {
+ if (!ObjectId.isId(rev)) {
+ if (!rev.startsWith(Constants.R_REFS)) {
+ rev = Constants.R_HEADS + rev;
+ }
+ if (!projectControl.controlForRef(rev).isVisible()) {
+ throw new ResourceNotFoundException();
+ }
+ }
+ }
+ return rev;
+ }
+
+ private static ObjectId resolveRevision(Repository repo, String revision)
+ throws ResourceNotFoundException, IOException {
+ ObjectId revId =
+ repo.resolve(MoreObjects.firstNonNull(revision, Constants.HEAD));
+ if (revId == null) {
+ throw new ResourceNotFoundException();
+ }
+ return revId;
+ }
+
+ private void validateCanReadCommit(Repository repo,
+ ProjectControl projectControl, ObjectId revId)
+ throws ResourceNotFoundException, IOException {
+ RevWalk rw = new RevWalk(repo);
+ try {
+ RevCommit commit = rw.parseCommit(revId);
+ if (!projectControl.canReadCommit(db.get(), rw, commit)) {
+ throw new ResourceNotFoundException();
+ }
+ } finally {
+ rw.release();
+ }
+ }
+
+ private static boolean isResourceNotModified(HttpServletRequest req,
+ ResourceKey key, ObjectId revId) {
+ String receivedETag = req.getHeader(HttpHeaders.IF_NONE_MATCH);
+ if (receivedETag != null) {
+ return receivedETag.equals(computeETag(key.project, revId, key.file));
+ }
+ return false;
+ }
+
private static String computeETag(Project.NameKey project, ObjectId revId,
String file) {
return Hashing.md5().newHasher()
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocScreen.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocScreen.java
index 92fa3ee..0bfa9c0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocScreen.java
@@ -53,8 +53,11 @@
setStyleName("xdocs-panel");
HorizontalPanel p = new HorizontalPanel();
+ p.setStyleName("xdocs-header");
p.add(new InlineHyperlink(projectName, "/admin/projects/" + projectName));
- p.add(new Label(" / " + fileName + " (" + revision + ")"));
+ p.add(new Label("/"));
+ p.add(new Label(fileName));
+ p.add(new Label("(" + revision + ")"));
add(p);
final String url = getUrl(projectName, revision, fileName);
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 8f86ccf..42c0456 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
@@ -49,7 +49,7 @@
import java.util.Properties;
@Singleton
-public class AsciidoctorFormatter implements Formatter {
+public class AsciidoctorFormatter implements StringFormatter {
public static final String NAME = "ASCIIDOCTOR";
private static final String BACKEND = "html5";
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 c975147..d7c6549 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
@@ -16,25 +16,6 @@
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.googlesource.gerrit.plugins.xdocs.ConfigSection;
-
-import java.io.IOException;
-
@ExtensionPoint
public interface Formatter {
-
- /**
- * Formats the given raw text as html.
- *
- * @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 global 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, String revision, ConfigSection cfg,
- String raw)
- throws IOException;
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/Formatters.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/Formatters.java
index 4e3c359..95e412e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/Formatters.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/Formatters.java
@@ -201,10 +201,9 @@
public static class FormatterProvider {
private final String name;
- private final Provider<Formatter> formatter;
+ private final Provider<? extends Formatter> formatter;
- FormatterProvider(String name,
- Provider<Formatter> formatter) {
+ FormatterProvider(String name, Provider<? extends Formatter> formatter) {
this.name = name;
this.formatter = formatter;
}
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 5ce5a73..58a8966 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
@@ -25,7 +25,7 @@
import java.io.IOException;
-public class MarkdownFormatter implements Formatter {
+public class MarkdownFormatter implements StringFormatter {
public final static String NAME = "MARKDOWN";
private final FormatterUtil util;
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 0c79bd5..6e36bba 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
@@ -18,7 +18,7 @@
import com.googlesource.gerrit.plugins.xdocs.ConfigSection;
-public class PlainTextFormatter implements Formatter {
+public class PlainTextFormatter implements StringFormatter {
public final static String NAME = "PLAIN_TEXT";
@Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/StreamFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/StreamFormatter.java
new file mode 100644
index 0000000..c435b23
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/StreamFormatter.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 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.googlesource.gerrit.plugins.xdocs.formatter;
+
+import com.googlesource.gerrit.plugins.xdocs.ConfigSection;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface StreamFormatter extends Formatter {
+ /**
+ * Formats the given raw text as html.
+ *
+ * @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 global configuration for this formatter
+ * @param raw the raw stream
+ * @return the content from the given stream formatted as html
+ * @throws IOException thrown if the formatting fails
+ */
+ public String format(String projectName, String revision, ConfigSection cfg,
+ InputStream raw) throws IOException;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/StringFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/StringFormatter.java
new file mode 100644
index 0000000..952794c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/StringFormatter.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 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.googlesource.gerrit.plugins.xdocs.formatter;
+
+import com.googlesource.gerrit.plugins.xdocs.ConfigSection;
+
+import java.io.IOException;
+
+public interface StringFormatter extends Formatter {
+
+ /**
+ * Formats the given raw text as html.
+ *
+ * @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 global 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, String revision, ConfigSection cfg,
+ String raw) throws IOException;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css b/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css
index cf984d4..42471c7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css
@@ -3,8 +3,14 @@
width: 100%;
}
+.xdocs-header td {
+ padding-right: 5px;
+}
+
.xdocs-panel iframe {
width: 100%;
+ border-width: 1px;
+ border-style: solid;
}
.xdocs-error {