Move presentation-specific logic from BE docgen into Velocity templates. Make
documentation of predefined attributes consistent with new look and feel.

--
MOS_MIGRATED_REVID=100905320
diff --git a/src/main/java/BUILD b/src/main/java/BUILD
index b51694e..a838185 100644
--- a/src/main/java/BUILD
+++ b/src/main/java/BUILD
@@ -315,6 +315,7 @@
     deps = [
         ":analysis-exec-rules-skyframe",
         ":common",
+        ":concurrent",
         ":packages",
         ":vfs",
         "//third_party:apache_velocity",
@@ -539,6 +540,7 @@
         ":analysis-exec-rules-skyframe",
         ":bazel-core",
         ":common",
+        ":concurrent",
         ":packages",
         ":vfs",
         "//third_party:apache_velocity",
diff --git a/src/main/java/com/google/devtools/build/docgen/BuildDocCollector.java b/src/main/java/com/google/devtools/build/docgen/BuildDocCollector.java
index 0bf4f20..4947f4e 100644
--- a/src/main/java/com/google/devtools/build/docgen/BuildDocCollector.java
+++ b/src/main/java/com/google/devtools/build/docgen/BuildDocCollector.java
@@ -119,6 +119,9 @@
                 }
               }
               if (bestAttributeDoc != null) {
+                // Add reference to the Attribute that the attribute doc is associated with
+                // in order to generate documentation for the Attribute.
+                bestAttributeDoc.setAttribute(attribute);
                 ruleDoc.addAttribute(bestAttributeDoc);
               // If there is no matching attribute doc try to add the common.
               } else if (ruleDoc.getRuleType().equals(RuleType.BINARY)
diff --git a/src/main/java/com/google/devtools/build/docgen/BuildEncyclopediaProcessor.java b/src/main/java/com/google/devtools/build/docgen/BuildEncyclopediaProcessor.java
index 4990d97..cd8dfe6 100644
--- a/src/main/java/com/google/devtools/build/docgen/BuildEncyclopediaProcessor.java
+++ b/src/main/java/com/google/devtools/build/docgen/BuildEncyclopediaProcessor.java
@@ -17,7 +17,6 @@
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
@@ -29,6 +28,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -117,10 +117,10 @@
 
     renderBeHeader(docEntries, page);
 
-    page.add(DocgenConsts.VAR_SECTION_BINARY, getRuleDocs(binaryDocs));
-    page.add(DocgenConsts.VAR_SECTION_LIBRARY, getRuleDocs(libraryDocs));
-    page.add(DocgenConsts.VAR_SECTION_TEST, getRuleDocs(testDocs));
-    page.add(DocgenConsts.VAR_SECTION_OTHER, getRuleDocs(otherDocs));
+    page.add("binaryDocs", binaryDocs);
+    page.add("libraryDocs", libraryDocs);
+    page.add("testDocs", testDocs);
+    page.add("otherDocs", otherDocs);
   }
 
   private void renderBeHeader(Iterable<RuleDocumentation> docEntries, Page page)
@@ -134,42 +134,25 @@
     Map<String, ListMultimap<RuleType, RuleDocumentation>> ruleMapping = new HashMap<>();
     createRuleMapping(docEntries, ruleMapping);
 
-    String languageSpecificTable;
-    {
-      StringBuilder sb = new StringBuilder();
-
-      sb.append("<colgroup span=\"6\" width=\"20%\"></colgroup>\n")
-        .append("<tr><th>Language</th><th>Binary rules</th><th>Library rules</th>"
-          + "<th>Test rules</th><th>Other rules</th><th></th></tr>\n");
-
-      // Generate the table.
-      for (String ruleFamily : languageSpecificRuleFamilies) {
-        generateHeaderTableRuleFamily(sb, ruleMapping.get(ruleFamily), ruleFamily);
-      }
-      languageSpecificTable = sb.toString();
+    List<SummaryRuleFamily> languageSpecificSummaryFamilies =
+        new ArrayList<SummaryRuleFamily>(languageSpecificRuleFamilies.size());
+    for (String ruleFamily : languageSpecificRuleFamilies) {
+      languageSpecificSummaryFamilies.add(
+          new SummaryRuleFamily(ruleMapping.get(ruleFamily), ruleFamily));
     }
 
-    String otherRulesTable;
-    {
-      StringBuilder sb = new StringBuilder();
-
-      sb.append("<colgroup span=\"6\" width=\"20%\"></colgroup>\n");
-      for (String ruleFamily : genericRuleFamilies) {
-        generateHeaderTableRuleFamily(sb, ruleMapping.get(ruleFamily), ruleFamily);
-      }
-      otherRulesTable = sb.toString();
+    List<SummaryRuleFamily> otherSummaryFamilies =
+        new ArrayList<SummaryRuleFamily>(genericRuleFamilies.size());
+    for (String ruleFamily : genericRuleFamilies) {
+      otherSummaryFamilies.add(
+          new SummaryRuleFamily(ruleMapping.get(ruleFamily), ruleFamily));
     }
-    page.add(DocgenConsts.VAR_LANG_SPECIFIC_HEADER_TABLE, languageSpecificTable);
-    page.add(DocgenConsts.VAR_OTHER_RULES_HEADER_TABLE, otherRulesTable);
-    page.add(DocgenConsts.VAR_COMMON_ATTRIBUTE_DEFINITION,
-        generateCommonAttributeDocs(
-            PredefinedAttributes.COMMON_ATTRIBUTES, DocgenConsts.COMMON_ATTRIBUTES));
-    page.add(DocgenConsts.VAR_TEST_ATTRIBUTE_DEFINITION,
-        generateCommonAttributeDocs(
-            PredefinedAttributes.TEST_ATTRIBUTES, DocgenConsts.TEST_ATTRIBUTES));
-    page.add(DocgenConsts.VAR_BINARY_ATTRIBUTE_DEFINITION,
-        generateCommonAttributeDocs(
-            PredefinedAttributes.BINARY_ATTRIBUTES, DocgenConsts.BINARY_ATTRIBUTES));
+
+    page.add("langSpecificSummaryFamilies", languageSpecificSummaryFamilies);
+    page.add("otherSummaryFamilies", otherSummaryFamilies);
+    page.add("commonAttributes", PredefinedAttributes.COMMON_ATTRIBUTES);
+    page.add("testAttributes", PredefinedAttributes.TEST_ATTRIBUTES);
+    page.add("binaryAttributes", PredefinedAttributes.BINARY_ATTRIBUTES);
     page.add(DocgenConsts.VAR_LEFT_PANEL, generateLeftNavigationPanel(docEntries));
   }
 
@@ -237,60 +220,6 @@
     return sb.toString();
   }
 
-  private String generateCommonAttributeDocs(Map<String, RuleDocumentationAttribute> attributes,
-      String attributeGroupName) throws BuildEncyclopediaDocException {
-    RuleDocumentation ruleDoc = new RuleDocumentation(
-        attributeGroupName, "OTHER", null, null, 0, null, ImmutableSet.<String>of(),
-        ruleClassProvider);
-    for (RuleDocumentationAttribute attribute : attributes.values()) {
-      ruleDoc.addAttribute(attribute);
-    }
-    return ruleDoc.generateAttributeDefinitions();
-  }
-
-  private void generateHeaderTableRuleFamily(StringBuilder sb,
-      ListMultimap<RuleType, RuleDocumentation> ruleTypeMap, String ruleFamily) {
-    sb.append("<tr>\n")
-      .append(String.format("<td class=\"lang\">%s</td>\n", ruleFamily));
-    boolean otherRulesSplitted = false;
-    for (RuleType ruleType : DocgenConsts.RuleType.values()) {
-      sb.append("<td>");
-      int i = 0;
-      List<RuleDocumentation> ruleDocList = ruleTypeMap.get(ruleType);
-      for (RuleDocumentation ruleDoc : ruleDocList) {
-        if (i > 0) {
-          if (ruleType.equals(RuleType.OTHER)
-              && ruleDocList.size() >= 4 && i == (ruleDocList.size() + 1) / 2) {
-            // Split 'other rules' into two columns if there are too many of them.
-            sb.append("</td>\n<td>");
-            otherRulesSplitted = true;
-          } else {
-            sb.append("<br/>");
-          }
-        }
-        String ruleName = ruleDoc.getRuleName();
-        String deprecatedString = ruleDoc.hasFlag(DocgenConsts.FLAG_DEPRECATED)
-            ? " class=\"deprecated\"" : "";
-        sb.append(String.format("<a href=\"#%s\"%s>%s</a>", ruleName, deprecatedString, ruleName));
-        i++;
-      }
-      sb.append("</td>\n");
-    }
-    // There should be 6 columns.
-    if (!otherRulesSplitted) {
-      sb.append("<td></td>\n");
-    }
-    sb.append("</tr>\n");
-  }
-
-  private String getRuleDocs(Iterable<RuleDocumentation> docEntries) {
-    StringBuilder sb = new StringBuilder();
-    for (RuleDocumentation doc : docEntries) {
-      sb.append(doc.getHtmlDocumentation());
-    }
-    return sb.toString();
-  }
-
   private File setupDirectories(String outputRootDir) {
     if (outputRootDir != null) {
       File outputRootPath = new File(outputRootDir);
diff --git a/src/main/java/com/google/devtools/build/docgen/RuleDocumentation.java b/src/main/java/com/google/devtools/build/docgen/RuleDocumentation.java
index 51fe118..c3057ff 100644
--- a/src/main/java/com/google/devtools/build/docgen/RuleDocumentation.java
+++ b/src/main/java/com/google/devtools/build/docgen/RuleDocumentation.java
@@ -13,12 +13,10 @@
 // limitations under the License.
 package com.google.devtools.build.docgen;
 
-import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.docgen.DocgenConsts.RuleType;
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
-import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.RuleClass;
 
 import java.util.HashMap;
@@ -43,8 +41,7 @@
  * such as declaring file name and the first line of the raw documentation. This can be useful for
  * proper error signaling during documentation processing.
  */
-class RuleDocumentation implements Comparable<RuleDocumentation> {
-
+public class RuleDocumentation implements Comparable<RuleDocumentation> {
   private final String ruleName;
   private final RuleType ruleType;
   private final String ruleFamily;
@@ -86,7 +83,7 @@
   /**
    * Returns the name of the rule.
    */
-  String getRuleName() {
+  public String getRuleName() {
     return ruleName;
   }
 
@@ -124,8 +121,8 @@
    * Returns true if this rule applies to a specific programming language (e.g. java_library),
    * returns false if it is a generic action (e.g. genrule, filegroup).
    *
-   * A rule is considered to be specific to a programming language by default. Generic rules have
-   * to be marked with the flag GENERIC_RULE in their #BLAZE_RULE definition.
+   * <p>A rule is considered to be specific to a programming language by default. Generic rules
+   * have to be marked with the flag GENERIC_RULE in their #BLAZE_RULE definition.
    */
   boolean isLanguageSpecific() {
     return !flags.contains(DocgenConsts.FLAG_GENERIC_RULE);
@@ -146,22 +143,31 @@
   }
 
   /**
+   * Returns the rule's set of RuleDocumentationAttributes.
+   */
+  public Set<RuleDocumentationAttribute> getAttributes() {
+    return attributes;
+  }
+
+  /**
    * Returns the html documentation in the exact format it should be written into the Build
    * Encyclopedia (expanding variables).
    */
-  String getHtmlDocumentation() {
+  public String getHtmlDocumentation() {
     String expandedDoc = htmlDocumentation;
     // Substituting variables
     for (Entry<String, String> docVariable : docVariables.entrySet()) {
       expandedDoc = expandedDoc.replace("${" + docVariable.getKey() + "}",
           expandBuiltInVariables(docVariable.getKey(), docVariable.getValue()));
     }
-    expandedDoc = expandedDoc.replace("${" + DocgenConsts.VAR_ATTRIBUTE_SIGNATURE + "}",
-        generateAttributeSignatures());
-    expandedDoc = expandedDoc.replace("${" + DocgenConsts.VAR_ATTRIBUTE_DEFINITION + "}",
-        generateAttributeDefinitions(true));
-    return String.format("<h3 id=\"%s\"%s>%s</h3>\n\n%s", ruleName,
-        getDeprecatedString(hasFlag(DocgenConsts.FLAG_DEPRECATED)), ruleName, expandedDoc);
+    // Replace the instances of the ATTRIBUTE_SIGNATURE and ATTRIBUTE_DEFINITION variables
+    // with the empty string since the variables are no longer used but are still present
+    // in the rule doc comments..
+    // TODO(dzc): Remove uses of ${ATTRIBUTE_SIGNATURE} and ${ATTRIBUTE_DEFINITION} from Bazel
+    // doc comments.
+    expandedDoc = expandedDoc.replace("${" + DocgenConsts.VAR_ATTRIBUTE_SIGNATURE + "}", "");
+    expandedDoc = expandedDoc.replace("${" + DocgenConsts.VAR_ATTRIBUTE_DEFINITION + "}", "");
+    return expandedDoc;
   }
 
   /**
@@ -172,57 +178,38 @@
   }
 
   /**
-   * Returns the html code of the attribute definitions without the header and name
-   * attribute of the rule.
+   * Returns a string containing any extra documentation for the name attribute for this
+   * rule.
    */
-  String generateAttributeDefinitions() {
-    return generateAttributeDefinitions(false);
+  public String getNameExtraHtmlDoc() {
+    return docVariables.containsKey(DocgenConsts.VAR_NAME)
+        ? docVariables.get(DocgenConsts.VAR_NAME)
+        : "";
   }
 
-  private String generateAttributeDefinitions(boolean generateNameAndHeader) {
-    StringBuilder sb = new StringBuilder();
-    if (generateNameAndHeader){
-      String nameExtraHtmlDoc = docVariables.containsKey(DocgenConsts.VAR_NAME)
-          ? docVariables.get(DocgenConsts.VAR_NAME) : "";
-      sb.append(String.format(Joiner.on('\n').join(new String[] {
-          "<h4 id=\"%s_args\">Arguments</h4>",
-          "<ul>",
-          "<li id=\"%s.name\"><code>name</code>: A unique name for this rule.",
-          "<i>(<a href=\"build-ref.html#name\">Name</a>; required)</i>%s</li>\n"}),
-          ruleName, ruleName, nameExtraHtmlDoc));
-    } else {
-      sb.append("<ul>\n");
-    }
-    for (RuleDocumentationAttribute attributeDoc : attributes) {
-      // Only generate attribute documentation here if the rule and the attribute is
-      // either both user defined or built in (of common type).
-      if (isCommonType() == attributeDoc.isCommonType()) {
-        String attrName = attributeDoc.getAttributeName();
-        Attribute attribute = isCommonType() ? null
-            : ruleClassProvider.getRuleClassMap().get(ruleName).getAttributeByName(attrName);
-        sb.append(String.format("<li id=\"%s.%s\"%s><code>%s</code>:\n%s</li>\n",
-            ruleName.toLowerCase(), attrName, getDeprecatedString(
-                attributeDoc.hasFlag(DocgenConsts.FLAG_DEPRECATED)),
-            attrName, attributeDoc.getHtmlDocumentation(attribute, ruleName)));
-      }
-    }
-    sb.append("</ul>\n");
+  /**
+   * Returns whether this rule has public visibility by default.
+   */
+  public boolean isPublicByDefault() {
     RuleClass ruleClass = ruleClassProvider.getRuleClassMap().get(ruleName);
-    if (ruleClass != null && ruleClass.isPublicByDefault()) {
-      sb.append(
-          "The default visibility is public: <code>visibility = [\"//visibility:public\"]</code>.");
-    }
-    return sb.toString();
+    return ruleClass != null && ruleClass.isPublicByDefault();
   }
 
-  private String getDeprecatedString(boolean deprecated) {
-    return deprecated ? " class=\"deprecated\"" : "";
+  /**
+   * Returns whether this rule is deprecated.
+   */
+  public boolean isDeprecated() {
+    return hasFlag(DocgenConsts.FLAG_DEPRECATED);
   }
 
-  private String generateAttributeSignatures() {
+  /**
+   * Returns a string containing the attribute signature for this rule with HTML links
+   * to the attributes.
+   */
+  public String getAttributeSignature() {
     StringBuilder sb = new StringBuilder();
     sb.append(String.format(
-        "<p class=\"rule-signature\">\n%s(<a href=\"#%s.name\">name</a>,\n",
+        "%s(<a href=\"#%s.name\">name</a>, ",
         ruleName, ruleName));
     int i = 0;
     for (RuleDocumentationAttribute attributeDoc : attributes) {
@@ -231,14 +218,12 @@
       sb.append(String.format("<a href=\"#%s.%s\">%s</a>",
           attributeDoc.getGeneratedInRule(ruleName).toLowerCase(), attrName, attrName));
       if (i < attributes.size() - 1) {
-        sb.append(",");
+        sb.append(", ");
       } else {
         sb.append(")");
       }
-      sb.append("\n");
       i++;
     }
-    sb.append("</p>\n");
     return sb.toString();
   }
 
@@ -290,13 +275,6 @@
   }
 
   /**
-   * Return true if the rule doesn't belong to a specific rule family.
-   */
-  private boolean isCommonType() {
-    return ruleFamily == null;
-  }
-
-  /**
    * Creates a BuildEncyclopediaDocException with the file containing this rule doc and
    * the number of the first line (where the rule doc is defined). Can be used to create
    * general BuildEncyclopediaDocExceptions about this rule.
diff --git a/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java b/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java
index c68dae2..a753e3f 100644
--- a/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java
+++ b/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java
@@ -34,7 +34,7 @@
  *
  * <p>Warning, two RuleDocumentationAttribute objects are equal based on only the attributeName.
  */
-class RuleDocumentationAttribute implements Comparable<RuleDocumentationAttribute> {
+public class RuleDocumentationAttribute implements Comparable<RuleDocumentationAttribute> {
 
   private static final Map<Type<?>, String> TYPE_DESC = ImmutableMap.<Type<?>, String>builder()
       .put(Type.BOOLEAN, "Boolean")
@@ -61,6 +61,7 @@
   private final String commonType;
   private int startLineCnt;
   private Set<String> flags;
+  private Attribute attribute;
 
   /**
    * Creates common RuleDocumentationAttribute such as deps or data.
@@ -96,34 +97,41 @@
   }
 
   /**
+   * Sets the Attribute object that this documents.
+   */
+  void setAttribute(Attribute attribute) {
+    this.attribute = attribute;
+  }
+
+  /**
    * Returns the name of the rule attribute.
    */
-  String getAttributeName() {
+  public String getAttributeName() {
     return attributeName;
   }
 
   /**
+   * Returns whether this attribute is marked as deprecated.
+   */
+  public boolean isDeprecated() {
+    return hasFlag(DocgenConsts.FLAG_DEPRECATED);
+  }
+
+  /**
    * Returns the raw html documentation of the rule attribute.
    */
-  String getHtmlDocumentation(Attribute attribute, String ruleName) {
-    // TODO(bazel-team): this is needed for common type attributes. Fix those and remove this.
-    if (attribute == null) {
-      return htmlDocumentation;
-    }
-    StringBuilder sb = new StringBuilder()
-        .append("<i>(")
-        .append(TYPE_DESC.get(attribute.getType()))
-        .append("; " + (attribute.isMandatory() ? "required" : "optional"))
-        .append(getDefaultValue(attribute))
-        .append(")</i><br/>\n");
-    String synposisVar = "${" + DocgenConsts.VAR_SYNOPSIS + "}";
-    if (!flags.contains(DocgenConsts.FLAG_DEPRECATED) && !htmlDocumentation.contains(synposisVar)) {
-      System.err.println("WARNING: No synopsis found for " + ruleName + "." + attributeName);
-    }
-    return htmlDocumentation.replace(synposisVar, sb.toString());
+  public String getHtmlDocumentation() {
+    // Replace the instances of the SYNOPSIS variable in the rule attribute doc with the
+    // empty string since the variables are no longer used but are still present in the
+    // rule doc comments..
+    // TODO(dzc): Remove uses of ${SYNOPSIS} from Bazel doc comments.
+    return htmlDocumentation.replace("${" + DocgenConsts.VAR_SYNOPSIS + "}", "");
   }
 
-  private String getDefaultValue(Attribute attribute) {
+  private String getDefaultValue() {
+    if (attribute == null) {
+      return "";
+    }
     String prefix = "; default is ";
     Object value = attribute.getDefaultValueForTesting();
     if (value instanceof Boolean) {
@@ -148,6 +156,20 @@
   }
 
   /**
+   * Returns a string containing the synopsis for this attribute.
+   */
+  public String getSynopsis() {
+    if (attribute == null) {
+      return "";
+    }
+    StringBuilder sb = new StringBuilder()
+        .append(TYPE_DESC.get(attribute.getType()))
+        .append("; " + (attribute.isMandatory() ? "required" : "optional"))
+        .append(getDefaultValue());
+    return sb.toString();
+  }
+
+  /**
    * Returns the number of first line of the attribute documentation in its declaration file.
    */
   int getStartLineCnt() {
@@ -157,7 +179,7 @@
   /**
    * Returns true if the attribute doc is of a common attribute type.
    */
-  boolean isCommonType() {
+  public boolean isCommonType() {
     return commonType != null;
   }
 
diff --git a/src/main/java/com/google/devtools/build/docgen/SummaryRuleFamily.java b/src/main/java/com/google/devtools/build/docgen/SummaryRuleFamily.java
new file mode 100644
index 0000000..c53c945
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/docgen/SummaryRuleFamily.java
@@ -0,0 +1,80 @@
+// Copyright 2014 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.
+
+package com.google.devtools.build.docgen;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ListMultimap;
+import com.google.devtools.build.docgen.DocgenConsts.RuleType;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+import java.util.List;
+
+/**
+ * Helper class for representing a rule family in the rule summary table template.
+ *
+ * <p>The rules are separated into categories by rule class: binary, library, test, and
+ * other.
+ */
+@Immutable
+public class SummaryRuleFamily {
+  private final String name;
+  private final ImmutableList<RuleDocumentation> binaryRules;
+  private final ImmutableList<RuleDocumentation> libraryRules;
+  private final ImmutableList<RuleDocumentation> testRules;
+  private final ImmutableList<RuleDocumentation> otherRules1;
+  private final ImmutableList<RuleDocumentation> otherRules2;
+
+  SummaryRuleFamily(ListMultimap<RuleType, RuleDocumentation> ruleTypeMap, String name) {
+    this.name = name;
+    this.binaryRules = ImmutableList.copyOf(ruleTypeMap.get(RuleType.BINARY));
+    this.libraryRules = ImmutableList.copyOf(ruleTypeMap.get(RuleType.LIBRARY));
+    this.testRules = ImmutableList.copyOf(ruleTypeMap.get(RuleType.TEST));
+
+    final ImmutableList<RuleDocumentation> otherRules =
+        ImmutableList.copyOf(ruleTypeMap.get(RuleType.OTHER));
+    if (otherRules.size() >= 4) {
+      this.otherRules1 = ImmutableList.copyOf(otherRules.subList(0, otherRules.size() / 2));
+      this.otherRules2 =
+          ImmutableList.copyOf(otherRules.subList(otherRules.size() / 2, otherRules.size()));
+    } else {
+      this.otherRules1 = otherRules;
+      this.otherRules2 = ImmutableList.of();
+    }
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public List<RuleDocumentation> getBinaryRules() {
+    return binaryRules;
+  }
+
+  public List<RuleDocumentation> getLibraryRules() {
+    return libraryRules;
+  }
+
+  public List<RuleDocumentation> getTestRules() {
+    return testRules;
+  }
+
+  public List<RuleDocumentation> getOtherRules1() {
+    return otherRules1;
+  }
+
+  public List<RuleDocumentation> getOtherRules2() {
+    return otherRules2;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/docgen/TemplateEngine.java b/src/main/java/com/google/devtools/build/docgen/TemplateEngine.java
index f7f2dd9..a63cde8 100644
--- a/src/main/java/com/google/devtools/build/docgen/TemplateEngine.java
+++ b/src/main/java/com/google/devtools/build/docgen/TemplateEngine.java
@@ -38,6 +38,7 @@
     engine.setProperty("output.encoding", "UTF-8");
     engine.setProperty("directive.set.null.allowed", true);
     engine.setProperty("parser.pool.size", 3);
+    engine.setProperty("runtime.references.strict", true);
     return new Page(engine, template);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/binary/args.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/binary/args.html
index 6fdfe4f..febe51a 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/binary/args.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/binary/args.html
@@ -1,11 +1,18 @@
-Add these arguments to the target when executed by
-<code>bazel run</code>.
-<i>(List of strings; optional; subject to
+<p>
+<code>List of strings; optional; subject to
 <a href="#location">$(location)</a> and
 <a href="#make_variables">"Make variable"</a> substitution, and
-<a href="#sh-tokenization">Bourne shell tokenization</a>)</i><br/>
-These arguments are passed to the target before the target options
-specified on the <code>bazel run</code> command line.
-<p>Most binary rules permit an <code>args</code> attribute, but where
+<a href="#sh-tokenization">Bourne shell tokenization</a></code>
+</p>
+
+<p>
+Command line arguments that bazel will pass to the target. These arguments are
+passed before the ones that are specified on the <code>bazel run</code>
+command line.
+</p>
+
+<p>
+Most binary rules permit an <code>args</code> attribute, but where
 this attribute is not allowed, this fact is documented under the
-specific rule.</p>
+specific rule.
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/binary/output_licenses.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/binary/output_licenses.html
index 8716afa..41b64c32 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/binary/output_licenses.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/binary/output_licenses.html
@@ -1,5 +1,10 @@
+<p><code>List of strings; optional</code></p>
+
+<p>
 The licenses of the output files that this binary generates.
-<i>(List of strings; optional)</i><br/>
+</p>
+
+<p>
 Describes the licenses of the output of the binary generated by
 the rule. When a binary is referenced in a host attribute (for
 example, the <code>tools</code> attribute of
@@ -10,13 +15,19 @@
 into the license of that rule. If this attribute is missing, the
 license computation proceeds as if the host dependency was a
 regular dependency.
-<p>(For more about the distinction between host and target
+</p>
+
+<p>
+(For more about the distinction between host and target
 configurations,
 see <a href="bazel-user-manual.html#configurations">Build
-configurations</a> in the Bazel manual.)
-<p><em class="harmful">WARNING: in some cases (specifically, in
+configurations</a> in the Bazel manual.)</p>
+
+<p>
+<em class="harmful">WARNING: in some cases (specifically, in
 genrules) the build tool cannot guarantee that the binary
 referenced by this attribute is actually used as a tool, and is
 not, for example, copied to the output. In these cases, it is the
 responsibility of the user to make sure that this is
-true.</em></p>
+true.</em>
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/data.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/data.html
index a0815ed..9158cb5 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/data.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/data.html
@@ -1,11 +1,20 @@
+<p><code>List of <a href="build-ref.html#labels">labels</a>; optional</code></p>
+
+<p>
 The list of files needed by this rule at runtime.
-<i>(List of <a href="build-ref.html#labels">labels</a>; optional)</i><br/>
+</p>
+
+<p>
 Targets named in the <code>data</code> attribute will appear in
 the <code>*.runfiles</code> area of this rule, if it has one.  This
 may include data files needed by a binary or library, or other
 programs needed by it.  See the
 <a href="build-ref.html#data">data dependencies</a> section for more
 information about how to depend on and use data files.
-<p>Almost all rules permit a <code>data</code> attribute, but where
+</p>
+
+<p>
+Almost all rules permit a <code>data</code> attribute, but where
 this attribute is not allowed, this fact is documented under the
-specific rule.</p>
+specific rule.
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/deprecation.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/deprecation.html
index 707fc07..533b0b8 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/deprecation.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/deprecation.html
@@ -1,4 +1,6 @@
-<i>(String; optional)</i><br/>
+<p><code>String; optional</code></p>
+
+<p>
 An explanatory warning message associated with this rule.
 Typically this is used to notify users that a rule has become obsolete,
 or has become superseded by another rule, is private to a package, or is
@@ -7,18 +9,27 @@
 that one can easily find out what changes are required to avoid the message.
 If there is a new target that can be used as a drop in replacement, it is a
 good idea to just migrate all users of the old target.
+</p>
+
 <p>
 This attribute has no effect on the way things are built, but it
 may affect a build tool's diagnostic output.  The build tool issues a
 warning when a rule with a <code>deprecation</code> attribute is
-depended upon by another rule.</p>
+depended upon by another rule.
+</p>
+
 <p>
 Intra-package dependencies are exempt from this warning, so that,
 for example, building the tests of a deprecated rule does not
-encounter a warning.</p>
+encounter a warning.
+</p>
+
 <p>
 If a deprecated rule depends on another deprecated rule, no warning
-message is issued.</p>
+message is issued.
+</p>
+
 <p>
 Once people have stopped using it, the package can be removed or marked as
-<a href="#common.obsolete"><code>obsolete</code></a>.</p>
+<a href="#common.obsolete"><code>obsolete</code></a>.
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/deps.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/deps.html
index 9b42a2b..c5a1469 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/deps.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/deps.html
@@ -1,12 +1,21 @@
+
+<p><code>List of <a href="build-ref.html#labels">labels</a>; optional</code></p>
+
+<p>
 A list of dependencies of this rule.
-<i>(List of <a href="build-ref.html#labels">labels</a>; optional)</i><br/>
+</p>
+
+<p>
 The precise semantics of what it means for this rule to depend on
 another using <code>deps</code> are specific to the kind of this rule,
 and the rule-specific documentation below goes into more detail.
 At a minimum, though, the targets named via <code>deps</code> will
 appear in the <code>*.runfiles</code> area of this rule, if it has
 one.
-<p>Most often, a <code>deps</code> dependency is used to allow one
+</p>
+
+<p>
+Most often, a <code>deps</code> dependency is used to allow one
 module to use symbols defined in another module written in the
 same programming language and separately compiled.  Cross-language
 dependencies are also permitted in many cases: for example,
@@ -14,7 +23,11 @@
 a <code>cc_library</code> rule, by declaring the latter in
 the <code>deps</code> attribute.  See the definition
 of <a href="build-ref.html#deps">dependencies</a> for more
-information.</p>
-<p>Almost all rules permit a <code>deps</code> attribute, but where
+information.
+</p>
+
+<p>
+Almost all rules permit a <code>deps</code> attribute, but where
 this attribute is not allowed, this fact is documented under the
-specific rule.</p>
+specific rule.
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/distribs.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/distribs.html
index 4ce6078..52ec244 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/distribs.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/distribs.html
@@ -1,4 +1,7 @@
-<i>(List of strings; optional)</i><br/>
+<p><code>List of strings; optional</code></p>
+
+<p>
 A list of distribution-method strings to be used for this particular build rule.
 Overrides the <code>BUILD</code>-file scope defaults defined by the
 <a href="#distribs"><code>distribs()</code></a> directive.
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/features.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/features.html
index ba4bbb4..9e158ee4 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/features.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/features.html
@@ -1,5 +1,6 @@
-List of <i>features</i>. Default is the empty list.<br/>
-<i>Features</i> on a rule modify the <i>features</i> currently enabled on
+<p><code>List of <i>features</i>. Default is the empty list.</code></p>
+
+<p><i>Features</i> on a rule modify the <i>features</i> currently enabled on
 the <a href="#package">package</a> level via the <i>features</i> attribute.<br/>
 For example, if the features ['a', 'b'] are enabled on the package level,
 and a rule <i>features</i> attribute contains ['-a', 'c'], the features
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/licenses.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/licenses.html
index e0f831e..854364e 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/licenses.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/licenses.html
@@ -1,4 +1,7 @@
-<i>(List of strings; optional)</i><br/>
+<p><code>List of strings; optional</code></p>
+
+<p>
 A list of license-type strings to be used for this particular build rule.
 Overrides the <code>BUILD</code>-file scope defaults defined by the
 <a href="#licenses"><code>licenses()</code></a> directive.
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/tags.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/tags.html
index e26672c..a004ece 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/tags.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/tags.html
@@ -1,5 +1,7 @@
-List of arbitrary text tags.  Tags may be any valid string; default is the
-empty list.<br/>
+<p><code>List of arbitrary text tags.  Tags may be any valid string; default is the
+empty list.</code></p>
+
+<p>
 <i>Tags</i> can be used on any rule; but <i>tags</i> are most useful
 on test and <code>test_suite</code> rules.  Tags on non-test rules
 are only useful to humans and/or external programs.
@@ -9,6 +11,8 @@
 lack any runtime annotation ability.  The use of tags and size elements
 gives flexibility in assembling suites of tests based around codebase
 check-in policy.
+</p>
+
 <p>
 A few tags have special meaning to the build tool, such as
 indicating that a particular test cannot be run remotely, for
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/testonly.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/testonly.html
index 0e9a80f..14153df 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/testonly.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/testonly.html
@@ -1,20 +1,38 @@
-<i>(Boolean; optional; default 0 except as noted)</i><br />
+<p><code>Boolean; optional; default 0 except as noted</code></p>
+
+<p>
 If 1, only testonly targets (such as tests) can depend on this target.
-<p>Equivalently, a rule that is not <code>testonly</code> is not allowed to
-depend on any rule that is <code>testonly</code>.</p>
-<p>Tests (<code>*_test</code> rules)
+</p>
+
+<p>
+Equivalently, a rule that is not <code>testonly</code> is not allowed to
+depend on any rule that is <code>testonly</code>.
+</p>
+
+<p>
+Tests (<code>*_test</code> rules)
 and test suites (<a href="#test_suite">test_suite</a> rules)
-are <code>testonly</code> by default.</p>
-<p>By virtue of
+are <code>testonly</code> by default.
+</p>
+
+<p>
+By virtue of
 <a href="#package.default_testonly"><code>default_testonly</code></a>,
-targets under <code>javatests</code> are <code>testonly</code> by default.</p>
-<p>This attribute is intended to mean that the target should not be
-contained in binaries that are released to production.</p>
-<p>Because testonly is enforced at build time, not run time, and propagates
+targets under <code>javatests</code> are <code>testonly</code> by default.
+</p>
+
+<p>
+This attribute is intended to mean that the target should not be
+contained in binaries that are released to production.
+</p>
+
+<p>
+Because testonly is enforced at build time, not run time, and propagates
 virally through the dependency tree, it should be applied judiciously. For
 example, stubs and fakes that
 are useful for unit tests may also be useful for integration tests
 involving the same binaries that will be released to production, and
 therefore should probably not be marked testonly. Conversely, rules that
 are dangerous to even link in, perhaps because they unconditionally
-override normal behavior, should definitely be marked testonly.</p>
+override normal behavior, should definitely be marked testonly.
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/visibility.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/visibility.html
index 28d5e5c..61eae83 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/visibility.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/common/visibility.html
@@ -1,8 +1,12 @@
-<i>(List of <a href="build-ref.html#labels">labels</a>; optional;
-default private)</i><br/>
-<p>The <code>visibility</code> attribute on a rule controls whether
+<p><code>List of <a href="build-ref.html#labels">labels</a>; optional;
+default private</code></p>
+
+<p>
+The <code>visibility</code> attribute on a rule controls whether
 the rule can be used by other packages. Rules are always visible to
-other rules declared in the same package.</p>
+other rules declared in the same package.
+</p>
+
 <p>There are five forms (and one temporary form) a visibility label can take:
 <ul>
 <li><code>["//visibility:public"]</code>: Anyone can use this rule.</li>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/args.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/args.html
index 072b467..ff01cf1 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/args.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/args.html
@@ -1,8 +1,12 @@
-Add these arguments to the <code>--test_arg</code>
-when executed by <code>bazel test</code>.
-<i>(List of strings; optional; subject to
+<p><code>List of strings; optional; subject to
 <a href="#location">$(location)</a> and
 <a href="#make_variables">"Make variable"</a> substitution, and
-<a href="#sh-tokenization">Bourne shell tokenization</a>)</i><br/>
+<a href="#sh-tokenization">Bourne shell tokenization</a></code></p>
+
+<p>Add these arguments to the <code>--test_arg</code>
+when executed by <code>bazel test</code>.</p>
+
+<p>
 These arguments are passed before the <code>--test_arg</code> values
 specified on the <code>bazel test</code> command line.
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/flaky.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/flaky.html
index be0eea7..ef4dfd1 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/flaky.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/flaky.html
@@ -1,5 +1,12 @@
-Marks test as flaky. <i>(Boolean; optional)</i><br/>
+<p><code>Boolean; optional</code></p>
+
+<p>
+Marks test as flaky.
+</p>
+
+<p>
 If set, executes the test up to 3 times before being declared as failed.
 By default this attribute is set to 0 and test is considered to be stable.
 Note, that use of this attribute is generally discouraged - we do prefer
 all tests to be stable.
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/size.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/size.html
index 5485345..1dea0ae 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/size.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/size.html
@@ -1,8 +1,11 @@
-How "heavy" the test is
-<i>(String "enormous", "large" "medium" or "small",
-default is "medium")</i><br/>
-A classification of the test's "heaviness": how much time/resources
-it needs to run.
-Unittests are considered "small", integration tests "medium", and
+<p><code>String "enormous", "large" "medium" or "small",
+default is "medium"</code></p>
+
+<p>How "heavy" the test is.</p>
+
+<p>A classification of the test's "heaviness": how much time/resources
+it needs to run.</p>
+
+<p>Unittests are considered "small", integration tests "medium", and
 end-to-end tests "large" or "enormous". Bazel uses the size only
-to determine a default timeout.
+to determine a default timeout.</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/timeout.html b/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/timeout.html
index a43e668..f20d0fd 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/timeout.html
+++ b/src/main/java/com/google/devtools/build/docgen/templates/attributes/test/timeout.html
@@ -1,9 +1,14 @@
-How long the test is
-normally expected to run before returning.
-<i>(String "eternal", "long", "moderate", or "short"
-with the default derived from a test's size attribute)</i><br/>
+<p><code>String "eternal", "long", "moderate", or "short"
+with the default derived from a test's size attribute)</code></p>
+
+<p>
+How long the test is normally expected to run before returning.
+</p>
+
+<p>
 While a test's size attribute controls resource estimation, a test's
 timeout may be set independently.  If not explicitly specified, the
 timeout is based on the test's size (with "small" &rArr; "short",
 "medium" &rArr; "moderate", etc...). "short" means 1 minute, "moderate"
 5 minutes, and "long" 15 minutes.
+</p>
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/be-header.vm b/src/main/java/com/google/devtools/build/docgen/templates/be-header.vm
index 3eba02b..c0bee73 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/be-header.vm
+++ b/src/main/java/com/google/devtools/build/docgen/templates/be-header.vm
@@ -62,16 +62,76 @@
 
 <h4>Language-specific Rules</h4>
 
-<table class="table table-condensed table-striped"
-    summary="Table of rules sorted by language">
-${LANG_SPECIFIC_HEADER_TABLE}
+#macro(summaryTable $ruleFamilies)
+  <tbody>
+  #foreach($ruleFamily in $ruleFamilies)
+    <tr>
+      <td class="lang">${ruleFamily.name}</td>
+      <td>
+    #foreach($ruleDoc in $ruleFamily.binaryRules)
+        <a href="#${ruleDoc.ruleName}"#if($ruleDoc.isDeprecated()) class="deprecated"#end>
+          ${ruleDoc.ruleName}
+        </a>
+        <br />
+    #end
+      </td>
+      <td>
+    #foreach($ruleDoc in $ruleFamily.libraryRules)
+        <a href="#${ruleDoc.ruleName}"#if($ruleDoc.isDeprecated()) class="deprecated"#end>
+          ${ruleDoc.ruleName}
+        </a>
+        <br />
+    #end
+      </td>
+      <td>
+    #foreach($ruleDoc in $ruleFamily.testRules)
+        <a href="#${ruleDoc.ruleName}"#if($ruleDoc.isDeprecated()) class="deprecated"#end>
+          ${ruleDoc.ruleName}
+        </a>
+        <br />
+    #end
+      </td>
+      <td>
+    #foreach($ruleDoc in $ruleFamily.otherRules1)
+        <a href="#${ruleDoc.ruleName}"#if($ruleDoc.isDeprecated()) class="deprecated"#end>
+          ${ruleDoc.ruleName}
+        </a>
+        <br />
+    #end
+      </td>
+      <td>
+    #foreach($ruleDoc in $ruleFamily.otherRules2)
+        <a href="#${ruleDoc.ruleName}"#if($ruleDoc.isDeprecated()) class="deprecated"#end>
+          ${ruleDoc.ruleName}
+        </a>
+        <br />
+    #end
+      </td>
+    </tr>
+  #end
+  </tbody>
+#end
+
+<table class="table table-condensed table-striped" summary="Table of rules sorted by language">
+  <colgroup span="6" width="20%"></colgroup>
+  <thead>
+    <tr>
+      <th>Language</th>
+      <th>Binary rules</th>
+      <th>Library rules</th>
+      <th>Test rules</th>
+      <th>Other rules</th>
+      <th></th>
+    </tr>
+  </thead>
+#summaryTable($langSpecificSummaryFamilies)
 
 </table>
 <h4>Rules that do not apply to a specific programming language</h4>
 
-<table class="table table-condensed table-striped"
-    summary="Table of rules not specific to a programming language">
-${OTHER_RULES_HEADER_TABLE}
+<table class="table table-condensed table-striped" summary="Table of rules not specific to a programming language">
+  <colgroup span="6" width="20%"></colgroup>
+#summaryTable($otherSummaryFamilies)
 
 </table>
 <h2 id="common-definitions">Common definitions</h2>
@@ -122,24 +182,47 @@
 
 <h3 id="common-attributes">Attributes common to all build rules</h3>
 
+#macro(commonAttributeDoc $type $attributeMap)
+  <table class="table table-condensed table-bordered table-params">
+    <colgroup>
+      <col class="col-param" />
+      <col class="param-description" />
+    </colgroup>
+    <thead>
+      <tr>
+        <th>Attribute</th>
+        <th>Description</th>
+      </tr>
+    </thead>
+    <tbody>
+  #foreach ($name in $attributeMap.keySet())
+      <tr>
+        <td id="${type}.${name}"><code>${name}</code></td>
+        <td>${attributeMap.get($name).htmlDocumentation}</td>
+      </tr>
+  #end
+    </tbody>
+  </table>
+#end
+
 <p>This section describes attributes that are common to all build rules.<br/>
 Please note that it is an error to list the same label twice in a list of
 labels attribute.
 </p>
 
-${COMMON_ATTRIBUTE_DEFINITION}
+#commonAttributeDoc("common" $commonAttributes)
 
 <h3 id="common-attributes-tests">Attributes common to all test rules (*_test)</h3>
 
 <p>This section describes attributes that are common to all test rules.</p>
 
-${TEST_ATTRIBUTE_DEFINITION}
+#commonAttributeDoc("test" $testAttributes)
 
 <h3 id="common-attributes-binaries">Attributes common to all binary rules (*_binary)</h3>
 
 <p>This section describes attributes that are common to all binary rules.</p>
 
-${BINARY_ATTRIBUTE_DEFINITION}
+#commonAttributeDoc("binary" $binaryAttributes)
 
 <h3 id="configurable-attributes">Configurable attributes</h3>
 
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/build-encyclopedia.vm b/src/main/java/com/google/devtools/build/docgen/templates/build-encyclopedia.vm
index aa7f4fa..2fbc20d 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/build-encyclopedia.vm
+++ b/src/main/java/com/google/devtools/build/docgen/templates/build-encyclopedia.vm
@@ -2,6 +2,60 @@
 
 #parse("com/google/devtools/build/docgen/templates/be-header.vm")
 
+#macro(ruledoc $ruleDocs)
+  #foreach ($rule in $ruleDocs)
+    <h3 id="${rule.ruleName}"#if($rule.isDeprecated()) class="deprecated"#end>
+      ${rule.ruleName}
+    </h3>
+
+    <pre class="rule-signature">${rule.attributeSignature}</pre>
+
+    $rule.htmlDocumentation
+
+    <h4 id="${rule.ruleName}_args">Arguments</h4>
+    <table class="table table-condensed table-bordered table-params">
+      <colgroup>
+        <col class="col-param" />
+        <col class="param-description" />
+      </colgroup>
+      <thead>
+        <tr>
+          <th>Attribute</th>
+          <th>Description</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td id="${rule.ruleName}.name"><code>name</code></td>
+          <td>
+            <p><code><a href="build-ref.html#name">Name</a>; required</code></p>
+            <p>A unique name for this rule.</p>
+            ${rule.nameExtraHtmlDoc}
+          </td>
+        </tr>
+      #foreach ($attribute in $rule.attributes)
+        #if (!$attribute.isCommonType())
+        <tr>
+          <td id="${rule.ruleName.toLowerCase()}.${attribute.attributeName}"#if($attribute.isDeprecated()) class="deprecated"#end>
+            <code>${attribute.attributeName}</code>
+          </td>
+          <td>
+            #if (!$attribute.synopsis.isEmpty())
+            <p><code>${attribute.synopsis}</code></p>
+            #end
+            $attribute.htmlDocumentation
+          </td>
+        </tr>
+        #end
+      #end
+      </tbody>
+    </table>
+    #if ($rule.isPublicByDefault())
+      The default visibility is public: <code>visibility = ["//visibility:public"]</code>.
+    #end
+  #end
+#end
+
 <!-- ============================================
                       binary
      ============================================
@@ -11,7 +65,7 @@
 <p>A <code>*_binary</code> rule compiles an application. This might be
    an executable, a <code>.jar</code> file, and/or a collection of scripts.</p>
 
-${SECTION_BINARY}
+#ruledoc($binaryDocs)
 
 <!-- ============================================
                       library
@@ -24,7 +78,7 @@
    the corresponding <code><var>language</var>_binary</code> rule, but
    doesn't generate an executable.</p>
 
-${SECTION_LIBRARY}
+#ruledoc($libraryDocs)
 
 <!-- ============================================
                       test
@@ -34,8 +88,7 @@
 
 <p>A <code>*_test</code> rule compiles a test.
 
-
-${SECTION_TEST}
+#ruledoc($testDocs)
 
 <!-- ============================================
                       variables
@@ -240,7 +293,7 @@
 -->
 <h2 id="misc">Other Stuff</h2>
 
-${SECTION_OTHER}
+#ruledoc($otherDocs)
 
 
 #parse("com/google/devtools/build/docgen/templates/be-footer.vm")