Simple Markdown rendering for skydoc This uses apache velocity engine templates to create markdown-HTML. There are other alternatives, but there is already precedent for depending on this library from docgen. RELNOTES: None. PiperOrigin-RevId: 203795431
diff --git a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java index c47a20a..7f66473 100644 --- a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java +++ b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
@@ -62,6 +62,7 @@ import com.google.devtools.build.skydoc.fakebuildapi.platform.FakePlatformCommon; import com.google.devtools.build.skydoc.fakebuildapi.repository.FakeRepositoryModule; import com.google.devtools.build.skydoc.fakebuildapi.test.FakeTestingModule; +import com.google.devtools.build.skydoc.rendering.MarkdownRenderer; import com.google.devtools.build.skydoc.rendering.RuleInfo; import java.io.IOException; import java.io.PrintWriter; @@ -116,29 +117,33 @@ new SkydocMain(new FilesystemFileAccessor()).eval(path, ruleInfoMap, unexportedRuleInfos); + MarkdownRenderer renderer = new MarkdownRenderer(); + try (PrintWriter printWriter = new PrintWriter(outputPath, "UTF-8")) { - printRuleInfos(printWriter, ruleInfoMap.build(), unexportedRuleInfos.build()); + printRuleInfos(printWriter, renderer, ruleInfoMap.build(), unexportedRuleInfos.build()); } } // TODO(cparsons): Improve output (markdown or HTML). private static void printRuleInfos( PrintWriter printWriter, + MarkdownRenderer renderer, Map<String, RuleInfo> ruleInfos, List<RuleInfo> unexportedRuleInfos) throws IOException { for (Entry<String, RuleInfo> ruleInfoEntry : ruleInfos.entrySet()) { - printRuleInfo(printWriter, ruleInfoEntry.getKey(), ruleInfoEntry.getValue()); + printRuleInfo(printWriter, renderer, ruleInfoEntry.getKey(), ruleInfoEntry.getValue()); + printWriter.println(); } for (RuleInfo unexportedRuleInfo : unexportedRuleInfos) { - printRuleInfo(printWriter, "<unknown name>", unexportedRuleInfo); + printRuleInfo(printWriter, renderer, "<unknown name>", unexportedRuleInfo); + printWriter.println(); } } private static void printRuleInfo( - PrintWriter printWriter, String exportedName, RuleInfo ruleInfo) { - printWriter.println(exportedName); - printWriter.println(ruleInfo.getDescription()); - printWriter.println(); + PrintWriter printWriter, MarkdownRenderer renderer, + String exportedName, RuleInfo ruleInfo) throws IOException { + printWriter.println(renderer.render(exportedName, ruleInfo)); } /**
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeDescriptor.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeDescriptor.java index 77c912d..249ee60 100644 --- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeDescriptor.java +++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeDescriptor.java
@@ -21,10 +21,19 @@ * Fake implementation of {@link Descriptor}. */ public class FakeDescriptor implements Descriptor { + private final String docString; + + public FakeDescriptor(String docString) { + this.docString = docString; + } + + public String getDocString() { + return docString; + } @Override public void repr(SkylarkPrinter printer) {} // TODO(cparsons): This class should store information about the attribute definition, for // example, the attribute type. -} \ No newline at end of file +}
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkAttrApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkAttrApi.java index f9e5f8c..3dcfaa2 100644 --- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkAttrApi.java +++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkAttrApi.java
@@ -30,13 +30,13 @@ @Override public Descriptor intAttribute(Integer defaultInt, String doc, Boolean mandatory, SkylarkList<?> values, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override public Descriptor stringAttribute(String defaultString, String doc, Boolean mandatory, SkylarkList<?> values, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override @@ -44,21 +44,21 @@ Object allowFiles, Object allowSingleFile, Boolean mandatory, SkylarkList<?> providers, Object allowRules, Boolean singleFile, Object cfg, SkylarkList<?> aspects, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override public Descriptor stringListAttribute(Boolean mandatory, Boolean nonEmpty, Boolean allowEmpty, SkylarkList<?> defaultList, String doc, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override public Descriptor intListAttribute(Boolean mandatory, Boolean nonEmpty, Boolean allowEmpty, SkylarkList<?> defaultList, String doc, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override @@ -66,7 +66,7 @@ Object allowFiles, Object allowRules, SkylarkList<?> providers, SkylarkList<?> flags, Boolean mandatory, Boolean nonEmpty, Object cfg, SkylarkList<?> aspects, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override @@ -74,46 +74,46 @@ String doc, Object allowFiles, Object allowRules, SkylarkList<?> providers, SkylarkList<?> flags, Boolean mandatory, Boolean nonEmpty, Object cfg, SkylarkList<?> aspects, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override public Descriptor boolAttribute(Boolean defaultO, String doc, Boolean mandatory, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override public Descriptor outputAttribute(Object defaultO, String doc, Boolean mandatory, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override public Descriptor outputListAttribute(Boolean allowEmpty, SkylarkList<?> defaultList, String doc, Boolean mandatory, Boolean nonEmpty, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override public Descriptor stringDictAttribute(Boolean allowEmpty, SkylarkDict<?, ?> defaultO, String doc, Boolean mandatory, Boolean nonEmpty, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override public Descriptor stringListDictAttribute(Boolean allowEmpty, SkylarkDict<?, ?> defaultO, String doc, Boolean mandatory, Boolean nonEmpty, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override public Descriptor licenseAttribute(Object defaultO, String doc, Boolean mandatory, FuncallExpression ast, Environment env) throws EvalException { - return new FakeDescriptor(); + return new FakeDescriptor(doc); } @Override
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java index 16635fc..9b1081b 100644 --- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java +++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
@@ -14,14 +14,13 @@ package com.google.devtools.build.skydoc.fakebuildapi; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.skylarkbuildapi.FileApi; import com.google.devtools.build.lib.skylarkbuildapi.FileTypeApi; import com.google.devtools.build.lib.skylarkbuildapi.ProviderApi; import com.google.devtools.build.lib.skylarkbuildapi.SkylarkAspectApi; -import com.google.devtools.build.lib.skylarkbuildapi.SkylarkAttrApi.Descriptor; import com.google.devtools.build.lib.skylarkbuildapi.SkylarkRuleFunctionsApi; import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.Environment; @@ -30,10 +29,11 @@ import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.syntax.SkylarkDict; import com.google.devtools.build.lib.syntax.SkylarkList; +import com.google.devtools.build.skydoc.rendering.AttributeInfo; import com.google.devtools.build.skydoc.rendering.RuleInfo; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.Nullable; /** @@ -68,19 +68,24 @@ SkylarkList<?> toolchains, String doc, SkylarkList<?> providesArg, Boolean executionPlatformConstraintsAllowed, SkylarkList<?> execCompatibleWith, FuncallExpression ast, Environment funcallEnv) throws EvalException { - Set<String> attrNames; + List<AttributeInfo> attrInfos; + // TODO(cparsons): Include implicit "Name" attribute. if (attrs != null && attrs != Runtime.NONE) { SkylarkDict<?, ?> attrsDict = (SkylarkDict<?, ?>) attrs; - Map<String, Descriptor> attrsMap = - attrsDict.getContents(String.class, Descriptor.class, "attrs"); - attrNames = attrsMap.keySet(); + Map<String, FakeDescriptor> attrsMap = + attrsDict.getContents(String.class, FakeDescriptor.class, "attrs"); + // TODO(cparsons): Include better attribute details. For example, attribute type. + attrInfos = attrsMap.entrySet().stream() + .map(entry -> new AttributeInfo(entry.getKey(), entry.getValue().getDocString())) + .sorted((o1, o2) -> o1.getName().compareTo(o2.getName())) + .collect(Collectors.toList()); } else { - attrNames = ImmutableSet.of(); + attrInfos = ImmutableList.of(); } RuleDefinitionIdentifier functionIdentifier = new RuleDefinitionIdentifier(); - // TODO(cparsons): Improve details given to RuleInfo (for example, attribute types). - ruleInfoList.add(new RuleInfo(functionIdentifier, ast.getLocation(), doc, attrNames)); + + ruleInfoList.add(new RuleInfo(functionIdentifier, ast.getLocation(), doc, attrInfos)); return functionIdentifier; }
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/AttributeInfo.java b/src/main/java/com/google/devtools/build/skydoc/rendering/AttributeInfo.java new file mode 100644 index 0000000..8051057 --- /dev/null +++ b/src/main/java/com/google/devtools/build/skydoc/rendering/AttributeInfo.java
@@ -0,0 +1,37 @@ +// Copyright 2018 The Bazel Authors. 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.skydoc.rendering; + +/** + * Stores information about a skylark attribute definition. + */ +public class AttributeInfo { + + private final String name; + private final String docString; + + public AttributeInfo(String name, String docString) { + this.name = name; + this.docString = docString; + } + + public String getName() { + return name; + } + + public String getDocString() { + return docString; + } +}
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/BUILD b/src/main/java/com/google/devtools/build/skydoc/rendering/BUILD index 0dc5f08..6a2633b 100644 --- a/src/main/java/com/google/devtools/build/skydoc/rendering/BUILD +++ b/src/main/java/com/google/devtools/build/skydoc/rendering/BUILD
@@ -12,11 +12,20 @@ java_library( name = "rendering", srcs = glob(["*.java"]), + resources = [":template_files"], deps = [ "//src/main/java/com/google/devtools/build/lib:events", "//src/main/java/com/google/devtools/build/lib:skylarkinterface", "//src/main/java/com/google/devtools/build/lib:syntax", + "//third_party:apache_velocity", "//third_party:guava", "//third_party:jsr305", ], ) + +filegroup( + name = "template_files", + srcs = glob([ + "templates/*.vm", + ]), +)
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownRenderer.java b/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownRenderer.java new file mode 100644 index 0000000..f9f5495 --- /dev/null +++ b/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownRenderer.java
@@ -0,0 +1,77 @@ +// Copyright 2018 The Bazel Authors. 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.skydoc.rendering; + +import com.google.common.base.Joiner; +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; +import org.apache.velocity.runtime.resource.loader.JarResourceLoader; + +/** + * Produces skydoc output in markdown form. + */ +public class MarkdownRenderer { + + private static final String TEMPLATE_FILENAME = + "com/google/devtools/build/skydoc/rendering/templates/test.vm"; + + private final VelocityEngine velocityEngine; + + public MarkdownRenderer() { + this.velocityEngine = new VelocityEngine(); + velocityEngine.setProperty("resource.loader", "classpath, jar"); + velocityEngine.setProperty("classpath.resource.loader.class", + ClasspathResourceLoader.class.getName()); + velocityEngine.setProperty("jar.resource.loader.class", JarResourceLoader.class.getName()); + velocityEngine.setProperty("input.encoding", "UTF-8"); + velocityEngine.setProperty("output.encoding", "UTF-8"); + velocityEngine.setProperty("runtime.references.strict", true); + } + + /** + * Returns a markdown rendering of rule documentation for the given rule information object with + * the given rule name. + */ + public String render(String ruleName, RuleInfo ruleInfo) throws IOException { + VelocityContext context = new VelocityContext(); + // TODO(cparsons): Attributes in summary form should have links. + context.put("summaryform", getSummaryForm(ruleName, ruleInfo)); + context.put("ruleName", ruleName); + context.put("ruleInfo", ruleInfo); + + StringWriter stringWriter = new StringWriter(); + try { + velocityEngine.mergeTemplate(TEMPLATE_FILENAME, "UTF-8", context, stringWriter); + } catch (ResourceNotFoundException | ParseErrorException | MethodInvocationException e) { + throw new IOException(e); + } + return stringWriter.toString(); + } + + private static String getSummaryForm(String ruleName, RuleInfo ruleInfo) { + List<String> attributeNames = ruleInfo.getAttributes().stream() + .map(attr -> attr.getName()) + .collect(Collectors.toList()); + return String.format("%s(%s)", ruleName, Joiner.on(", ").join(attributeNames)); + } +}
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/RuleInfo.java b/src/main/java/com/google/devtools/build/skydoc/rendering/RuleInfo.java index dee5e0f..6f231cf 100644 --- a/src/main/java/com/google/devtools/build/skydoc/rendering/RuleInfo.java +++ b/src/main/java/com/google/devtools/build/skydoc/rendering/RuleInfo.java
@@ -14,8 +14,6 @@ package com.google.devtools.build.skydoc.rendering; -import com.google.common.base.Joiner; -import com.google.common.base.Strings; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.syntax.BaseFunction; import java.util.Collection; @@ -28,16 +26,16 @@ private final BaseFunction identifierFunction; private final Location location; private final String docString; - private final Collection<String> attrNames; + private final Collection<AttributeInfo> attrInfos; public RuleInfo(BaseFunction identifierFunction, Location location, String docString, - Collection<String> attrNames) { + Collection<AttributeInfo> attrInfos) { this.identifierFunction = identifierFunction; this.location = location; this.docString = docString; - this.attrNames = attrNames; + this.attrInfos = attrInfos; } public BaseFunction getIdentifierFunction() { @@ -52,17 +50,7 @@ return docString; } - public Collection<String> getAttrNames() { - return attrNames; - } - - public String getDescription() { - StringBuilder stringBuilder = new StringBuilder(); - if (!Strings.isNullOrEmpty(docString)) { - stringBuilder.append(docString); - stringBuilder.append("\n"); - } - Joiner.on(",").appendTo(stringBuilder, attrNames); - return stringBuilder.toString(); + public Collection<AttributeInfo> getAttributes() { + return attrInfos; } }
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/templates/test.vm b/src/main/java/com/google/devtools/build/skydoc/rendering/templates/test.vm new file mode 100644 index 0000000..b1600e9 --- /dev/null +++ b/src/main/java/com/google/devtools/build/skydoc/rendering/templates/test.vm
@@ -0,0 +1,27 @@ +<a name="#${ruleName}"></a> +#[[##]]# ${ruleName} + +<pre> +${summaryform} +</pre> + +${ruleInfo.docString} + +#[[###]]# Attributes + +#if (!$ruleInfo.attributes.isEmpty()) +<table class="params-table"> + <colgroup> + <col class="col-param" /> + <col class="col-description" /> + </colgroup> + <tbody> +#foreach ($attribute in $ruleInfo.attributes) + <tr id="#${ruleName}_${attribute.name}"> + <td><code>${attribute.name}</code></td> + <td>${attribute.docString}</td> + </tr> +#end + </tbody> +</table> +#end