Render custom templates using deltemplate with variants

This new feature in Soy[1] allows dynamically choosing a variant
template at runtime. Use this in conjunction with reading the config
for a particular request to allow per-request (e.g. per-repo)
templates. Currently only supports the customHeader template, which
uses template.customHeader to specify the variant name.
(Unfortunately like most stuff that is common to all pages, we need
to pass this around manually.)

Since we no longer need to exclude the default custom templates, we
can delete DefaultCustomTemplates.soy.

[1] https://developers.google.com/closure/templates/docs/commands#delegates-with-variant

Change-Id: I4f5d33e6e49b5c6af640f4ed6138b018a11745ae
diff --git a/gitiles-dev/src/main/java/com/google/gitiles/dev/DevServer.java b/gitiles-dev/src/main/java/com/google/gitiles/dev/DevServer.java
index c32cd8e..5b0f5e8 100644
--- a/gitiles-dev/src/main/java/com/google/gitiles/dev/DevServer.java
+++ b/gitiles-dev/src/main/java/com/google/gitiles/dev/DevServer.java
@@ -47,6 +47,7 @@
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.UnknownHostException;
+import java.util.Arrays;
 
 class DevServer {
   private static final Logger log = LoggerFactory.getLogger(PathServlet.class);
@@ -187,7 +188,7 @@
         cfg,
         new DebugRenderer(
             STATIC_PREFIX,
-            cfg.getString("gitiles", null, "customTemplates"),
+            Arrays.asList(cfg.getStringList("gitiles", null, "customTemplates")),
             new File(sourceRoot, "gitiles-servlet/src/main/resources/com/google/gitiles/templates")
                 .getPath(),
             Objects.firstNonNull(cfg.getString("gitiles", null, "siteTitle"), "Gitiles")),
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/BaseServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/BaseServlet.java
index ba0ee5c..76d9f96 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/BaseServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/BaseServlet.java
@@ -194,6 +194,12 @@
       setCacheHeaders(res);
 
       Map<String, Object> allData = getData(req);
+
+      String headerVariant = getAccess(req).getConfig().getString("template", null, "customHeader");
+      if (headerVariant != null) {
+        allData.put("headerVariant", headerVariant);
+      }
+
       allData.putAll(soyData);
       GitilesView view = ViewFilter.getView(req);
       if (!allData.containsKey("repositoryName") && view.getRepositoryName() != null) {
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/DebugRenderer.java b/gitiles-servlet/src/main/java/com/google/gitiles/DebugRenderer.java
index 366191a..9638fd8 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/DebugRenderer.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/DebugRenderer.java
@@ -16,7 +16,7 @@
 
 import static com.google.common.base.Preconditions.checkState;
 
-import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableMap;
 import com.google.template.soy.SoyFileSet;
 import com.google.template.soy.tofu.SoyTofu;
@@ -27,17 +27,13 @@
 
 /** Renderer that reloads Soy templates from the filesystem on every request. */
 public class DebugRenderer extends Renderer {
-  public DebugRenderer(String staticPrefix, String customTemplatesFilename,
+  public DebugRenderer(String staticPrefix, Iterable<String> customTemplatesFilenames,
       final String soyTemplatesRoot, String siteTitle) {
     super(
-        new Function<String, URL>() {
-          @Override
-          public URL apply(String name) {
-            return toFileURL(soyTemplatesRoot + File.separator + name);
-          }
-        },
+        new FileUrlMapper(soyTemplatesRoot + File.separator),
         ImmutableMap.<String, String> of(), staticPrefix,
-        toFileURL(customTemplatesFilename), siteTitle);
+        FluentIterable.from(customTemplatesFilenames).transform(new FileUrlMapper()),
+        siteTitle);
   }
 
   @Override
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/DefaultRenderer.java b/gitiles-servlet/src/main/java/com/google/gitiles/DefaultRenderer.java
index e2eef7c..0741fc2 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/DefaultRenderer.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/DefaultRenderer.java
@@ -31,12 +31,12 @@
     this("", null, "");
   }
 
-  public DefaultRenderer(String staticPrefix, URL customTemplates, String siteTitle) {
+  public DefaultRenderer(String staticPrefix, Iterable<URL> customTemplates, String siteTitle) {
     this(ImmutableMap.<String, String> of(), staticPrefix, customTemplates, siteTitle);
   }
 
-  public DefaultRenderer(Map<String, String> globals, String staticPrefix, URL customTemplates,
-      String siteTitle) {
+  public DefaultRenderer(Map<String, String> globals, String staticPrefix,
+      Iterable<URL> customTemplates, String siteTitle) {
     super(
         new Function<String, URL>() {
           @Override
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
index e8b161d..b878415 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
@@ -21,10 +21,12 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Maps;
+import com.google.gitiles.Renderer.FileUrlMapper;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -42,6 +44,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.net.UnknownHostException;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.regex.Pattern;
@@ -322,7 +325,9 @@
     if (renderer == null) {
       renderer = new DefaultRenderer(
           filterConfig.getServletContext().getContextPath() + STATIC_PREFIX,
-          Renderer.toFileURL(config.getString("gitiles", null, "customTemplates")),
+          FluentIterable.from(Arrays.asList(
+                config.getStringList("gitiles", null, "customTemplates")))
+              .transform(new FileUrlMapper()),
           Objects.firstNonNull(config.getString("gitiles", null, "siteTitle"), "Gitiles"));
     }
   }
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java b/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java
index 1ab26c4..643c389 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java
@@ -18,9 +18,10 @@
 
 import com.google.common.base.Charsets;
 import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.template.soy.tofu.SoyTofu;
 
@@ -52,14 +53,27 @@
       "gitiles.PRETTIFY_CSS_URL", "prettify/prettify.css",
       "gitiles.PRETTIFY_JS_URL", "prettify/prettify.js");
 
-  protected static final URL toFileURL(String filename) {
-    if (filename == null) {
-      return null;
+  protected static class FileUrlMapper implements Function<String, URL> {
+    private final String prefix;
+
+    protected FileUrlMapper() {
+      this("");
     }
-    try {
-      return new File(filename).toURI().toURL();
-    } catch (MalformedURLException e) {
-      throw new IllegalArgumentException(e);
+
+    protected FileUrlMapper(String prefix) {
+      this.prefix = checkNotNull(prefix, "prefix");
+    }
+
+    @Override
+    public URL apply(String filename) {
+      if (filename == null) {
+        return null;
+      }
+      try {
+        return new File(prefix + filename).toURI().toURL();
+      } catch (MalformedURLException e) {
+        throw new IllegalArgumentException(e);
+      }
     }
   }
 
@@ -67,18 +81,10 @@
   protected ImmutableMap<String, String> globals;
 
   protected Renderer(Function<String, URL> resourceMapper, Map<String, String> globals,
-      String staticPrefix, URL customTemplates, String siteTitle) {
+      String staticPrefix, Iterable<URL> customTemplates, String siteTitle) {
     checkNotNull(staticPrefix, "staticPrefix");
-    List<URL> allTemplates = Lists.newArrayListWithCapacity(SOY_FILENAMES.size() + 1);
-    for (String filename : SOY_FILENAMES) {
-      allTemplates.add(resourceMapper.apply(filename));
-    }
-    if (customTemplates != null) {
-      allTemplates.add(customTemplates);
-    } else {
-      allTemplates.add(resourceMapper.apply("DefaultCustomTemplates.soy"));
-    }
-    templates = ImmutableList.copyOf(allTemplates);
+    Iterable<URL> allTemplates = FluentIterable.from(SOY_FILENAMES).transform(resourceMapper);
+    templates = ImmutableList.copyOf(Iterables.concat(allTemplates, customTemplates));
 
     Map<String, String> allGlobals = Maps.newHashMap();
     for (Map.Entry<String, String> e : STATIC_URL_GLOBALS.entrySet()) {
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy
index f64368e..8ecfcce 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy
@@ -19,6 +19,7 @@
  * @param title human-readable revision name.
  * @param repositoryName name of this repository.
  * @param? menuEntries menu entries.
+ * @param? headerVariant variant name for custom header.
  * @param breadcrumbs breadcrumbs for this page.
  * @param data blob data, matching the params for .blobBox.
  * @param? regions for non-binary files, list of blame regions with the
@@ -37,6 +38,7 @@
     {param title: $title /}
     {param repositoryName: $repositoryName /}
     {param menuEntries: $menuEntries /}
+    {param headerVariant: $headerVariant /}
     {param breadcrumbs: $breadcrumbs /}
     {param css: [gitiles.PRETTIFY_CSS_URL] /}
     {param js: [gitiles.PRETTIFY_JS_URL] /}
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/Common.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/Common.soy
index 06db9a3..4cfd648 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/Common.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/Common.soy
@@ -21,6 +21,7 @@
  * @param? repositoryName repository name for this page, if applicable.
  * @param? menuEntries optional list of menu entries with "text" and optional
  *     "url" keys.
+ * @param? headerVariant variant name for custom header.
  * @param breadcrumbs navigation breadcrumbs for this page.
  * @param? css optional list of CSS URLs to include.
  * @param? js optional list of Javascript URLs to include.
@@ -56,7 +57,7 @@
   {/if}
 </head>
 <body {if $onLoad}onload="{$onLoad|id}"{/if}>
-  {call .customHeader /}
+  {delcall gitiles.customHeader variant="$headerVariant ?: 'default'" /}
 
   {if $menuEntries and length($menuEntries)}
     <div class="menu">
@@ -87,6 +88,13 @@
 {/template}
 
 /**
+ * Default custom header implementation for Gitiles.
+ */
+{deltemplate gitiles.customHeader variant="'default'"}
+<h1>{msg desc="short name of the application"}{gitiles.SITE_TITLE}{/msg}</h1>
+{/deltemplate}
+
+/**
  * Standard footer.
  */
 {template .footer}
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DefaultCustomTemplates.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DefaultCustomTemplates.soy
deleted file mode 100644
index b952d3c..0000000
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DefaultCustomTemplates.soy
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2012 Google Inc. All Rights Reserved.
-//
-// 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.
-{namespace gitiles autoescape="contextual"}
-
-/**
- * Default custom header implementation for Gitiles.
- */
-{template .customHeader}
-<h1>{msg desc="short name of the application"}{gitiles.SITE_TITLE}{/msg}</h1>
-{/template}
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DiffDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DiffDetail.soy
index dfd8af2..d888d0a 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DiffDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DiffDetail.soy
@@ -19,6 +19,7 @@
  * @param title human-readable revision name.
  * @param repositoryName name of this repository.
  * @param? menuEntries menu entries.
+ * @param? headerVariant variant name for custom header.
  * @param breadcrumbs breadcrumbs for this page.
  * @param? commit optional commit for which diffs are displayed, with keys
  *     corresponding to the gitiles.commitDetail template (minus "diffTree").
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/HostIndex.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/HostIndex.soy
index cf3e726..07da395 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/HostIndex.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/HostIndex.soy
@@ -18,6 +18,7 @@
  *
  * @param hostName host name.
  * @param? menuEntries menu entries.
+ * @param? headerVariant variant name for custom header.
  * @param baseUrl base URL for repositories.
  * @param repositories list of repository description maps with name, cloneUrl,
  *     and optional description values.
@@ -27,6 +28,7 @@
   {param title: $hostName ? $hostName + ' Git repositories' : 'Git repositories' /}
   {param menuEntries: $menuEntries /}
   {param breadcrumbs: null /}
+  {param headerVariant: $headerVariant /}
 {/call}
 
 {if length($repositories)}
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/LogDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/LogDetail.soy
index fd08196..3d5531e 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/LogDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/LogDetail.soy
@@ -19,6 +19,7 @@
  * @param title human-readable revision name.
  * @param repositoryName name of this repository.
  * @param? menuEntries menu entries.
+ * @param? headerVariant variant name for custom header.
  * @param breadcrumbs breadcrumbs for this page.
  * @param? tags optional list of tags encountered when peeling this object, with
  *     keys corresponding to gitiles.tagDetail.
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/PathDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/PathDetail.soy
index d954268..ccbffdd 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/PathDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/PathDetail.soy
@@ -19,6 +19,7 @@
  * @param title human-readable name of this path.
  * @param repositoryName name of this repository.
  * @param? menuEntries menu entries.
+ * @param? headerVariant variant name for custom header.
  * @param breadcrumbs breadcrumbs for this page.
  * @param type path type, matching one of the constant names defined in
  *         org.eclipse.jgit.lib.FileMode.
@@ -31,6 +32,7 @@
     {param title: $title /}
     {param repositoryName: $repositoryName /}
     {param menuEntries: $menuEntries /}
+    {param headerVariant: $headerVariant /}
     {param breadcrumbs: $breadcrumbs /}
     {param css: [gitiles.PRETTIFY_CSS_URL] /}
     {param js: [gitiles.PRETTIFY_JS_URL] /}
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RefList.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RefList.soy
index d13dd6d..e960b54 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RefList.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RefList.soy
@@ -19,6 +19,7 @@
  *
  * @param repositoryName name of this repository.
  * @param? menuEntries menu entries.
+ * @param? headerVariant variant name for custom header.
  * @param breadcrumbs breadcrumbs for this page.
  * @param branches list of branch objects with url, name, and isHead keys.
  * @param tags list of tag objects with url and name keys.
@@ -28,6 +29,7 @@
   {param title: 'Refs' /}
   {param repositoryName: $repositoryName /}
   {param menuEntries: $menuEntries /}
+  {param headerVariant: $headerVariant /}
   {param breadcrumbs: $breadcrumbs /}
 {/call}
 
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RepositoryIndex.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RepositoryIndex.soy
index 13cadfd..14d5335 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RepositoryIndex.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RepositoryIndex.soy
@@ -18,6 +18,7 @@
  *
  * @param repositoryName name of this repository.
  * @param? menuEntries menu entries.
+ * @param? headerVariant variant name for custom header.
  * @param breadcrumbs breadcrumbs for this page.
  * @param cloneUrl clone URL for this repository.
  * @param description description text of the repository.
@@ -35,6 +36,7 @@
   {param title: $repositoryName /}
   {param repositoryName: null /}
   {param menuEntries: $menuEntries /}
+  {param headerVariant: $headerVariant /}
   {param breadcrumbs: $breadcrumbs /}
 {/call}
 
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RevisionDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RevisionDetail.soy
index 1abed6b..a756b88 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RevisionDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RevisionDetail.soy
@@ -19,6 +19,7 @@
  * @param title human-readable revision name.
  * @param repositoryName name of this repository.
  * @param? menuEntries menu entries.
+ * @param? headerVariant variant name for custom header.
  * @param breadcrumbs breadcrumbs for this page.
  * @param? hasBlob set to true if the revision or its peeled value is a blob.
  * @param objects list of objects encountered when peeling this object. Each
@@ -33,6 +34,7 @@
     {param title: $title /}
     {param repositoryName: $repositoryName /}
     {param menuEntries: $menuEntries /}
+    {param headerVariant: $headerVariant /}
     {param breadcrumbs: $breadcrumbs /}
     {param css: [gitiles.PRETTIFY_CSS_URL] /}
     {param js: [gitiles.PRETTIFY_JS_URL] /}