Skip to content

Commit

Permalink
Render documentation for provider init callbacks (#224)
Browse files Browse the repository at this point in the history
By default, we want the following behavior:

* Custom init callback specified
  * The set of parameters for the init callback equals the set of
    fields for the provider; and the docs for the init callback's
    parameters are either empty or equal to corresponding field docs
    * Some init parameters have a default value:
      -> Render a single "Fields" table with 3 columns (name, doc,
         default value)
    * ... otherwise
      -> Render a single "Fields" table with 2 columns
  * ... otherwise
    -> Render two tables - "Constructor parameters" and "Fields" - with
       the links from the summary blurb (interfixed with "_init")
       leading to the parameters table (not the fields table)
* ... otherwise
  -> Trivial case - single "Fields" table (as before).

Fixes #182
  • Loading branch information
tetromino committed May 22, 2024
1 parent be1a9a8 commit 666b7ba
Show file tree
Hide file tree
Showing 12 changed files with 511 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
*/
public final class RendererMain {

@SuppressWarnings("ProtoParseWithRegistry") // See https://github.com/bazelbuild/stardoc/pull/221
public static void main(String[] args) throws IOException {

RendererOptions rendererOptions = new RendererOptions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@

package com.google.devtools.build.stardoc.rendering;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.AspectInfo;
import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.FunctionParamInfo;
import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.ModuleExtensionInfo;
import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.ModuleInfo;
import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.ProviderFieldInfo;
import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.ProviderInfo;
import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.RepositoryRuleInfo;
import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.RuleInfo;
Expand Down Expand Up @@ -157,16 +162,81 @@ public String render(RuleInfo ruleInfo) throws IOException {
/**
* Returns a markdown rendering of provider documentation for the given provider information
* object with the given name.
*
* <p>For evaluating the provider template, populates the the following constants:
*
* <ul>
* <li>util - a {@link MarkdownUtil} object
* <li>providerName - the provider's name
* <li>providerInfo - the {@link ProviderInfo} proto
* <li>initParamsWithInferredDocs - the list of the init callback's {@link FunctionParamInfo}
* protos, with any undocumented parameters inheriting the doc string of the provider's
* field with the same name; or an empty list of the provider doesn't have an init callback
* <li>initParamNamesEqualFieldNames - true iff the provider has an init callback and the set of
* names of the init callback's parameters equals the set of names of the provider's fields
* <li>initParamsHaveDefaultValues - true iff the provider has an init callback and at least one
* of the init callback's parameters has a default value specified
* <li>initParamsHaveDistinctDocs - true iff the provider has an init callback and at least one
* of the init callback's parameters has a docstring which is non-empty and not equal to the
* corresponding field's docstring.
* </ul>
*/
public String render(ProviderInfo providerInfo) throws IOException {
ImmutableMap.Builder<String, String> fieldDocsBuilder = ImmutableMap.builder();
for (ProviderFieldInfo fieldInfo : providerInfo.getFieldInfoList()) {
fieldDocsBuilder.put(fieldInfo.getName(), fieldInfo.getDocString());
}
ImmutableMap<String, String> fieldDocs = fieldDocsBuilder.buildOrThrow();

ImmutableList<FunctionParamInfo> initParamsWithInferredDocs;
if (providerInfo.hasInit()) {
initParamsWithInferredDocs =
providerInfo.getInit().getParameterList().stream()
.map(param -> withInferredDoc(param, fieldDocs))
.collect(toImmutableList());
} else {
initParamsWithInferredDocs = ImmutableList.of();
}
boolean initParamNamesEqualFieldNames =
providerInfo.hasInit()
&& providerInfo.getInit().getParameterList().stream()
.map(FunctionParamInfo::getName)
.collect(toImmutableSet())
.equals(
providerInfo.getFieldInfoList().stream()
.map(ProviderFieldInfo::getName)
.collect(toImmutableSet()));
boolean initParamsHaveDefaultValues =
providerInfo.hasInit()
&& providerInfo.getInit().getParameterList().stream()
.filter(param -> !param.getDefaultValue().isEmpty())
.findFirst()
.isPresent();
boolean initParamsHaveDistinctDocs =
providerInfo.hasInit()
&& providerInfo.getInit().getParameterList().stream()
.filter(
param ->
!param.getDocString().isEmpty()
&& !param.getDocString().equals(fieldDocs.get(param.getName())))
.findFirst()
.isPresent();
ImmutableMap<String, Object> vars =
ImmutableMap.of(
"util",
new MarkdownUtil(extensionBzlFile),
"providerName",
providerInfo.getProviderName(),
"providerInfo",
providerInfo);
providerInfo,
"initParamsWithInferredDocs",
initParamsWithInferredDocs,
"initParamNamesEqualFieldNames",
initParamNamesEqualFieldNames,
"initParamsHaveDefaultValues",
initParamsHaveDefaultValues,
"initParamsHaveDistinctDocs",
initParamsHaveDistinctDocs);
Reader reader = readerFromPath(providerTemplateFilename);
try {
return Template.parseFrom(reader).evaluate(vars);
Expand All @@ -175,6 +245,18 @@ public String render(ProviderInfo providerInfo) throws IOException {
}
}

private static FunctionParamInfo withInferredDoc(
FunctionParamInfo paramInfo, ImmutableMap<String, String> fallbackDocs) {
if (paramInfo.getDocString().isEmpty() && fallbackDocs.containsKey(paramInfo.getName())) {
return paramInfo.toBuilder()
.clearDocString()
.setDocString(fallbackDocs.get(paramInfo.getName()))
.build();
} else {
return paramInfo;
}
}

/**
* Returns a markdown rendering of a user-defined function's documentation for the function info
* object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -223,15 +224,36 @@ public String ruleSummary(String ruleName, RuleInfo ruleInfo) {
/**
* Return a string representing the summary for the given provider with the given name.
*
* <p>For example: 'MyInfo(foo, bar)'. The summary will contain hyperlinks for each field.
* <p>For example: 'MyInfo(foo, bar)'.
*
* <p>If the provider has an init callback, the summary will contain hyperlinks for each of the
* init callback's parameters; if the provider doesn't have an init callback, the summary will
* contain hyperlinks for each field.
*/
@SuppressWarnings("unused") // Used by markdown template.
public String providerSummary(String providerName, ProviderInfo providerInfo) {
ImmutableList<String> fieldNames =
providerInfo.getFieldInfoList().stream()
.map(field -> field.getName())
.collect(toImmutableList());
return summary(providerName, fieldNames);
return providerSummaryImpl(
providerName, providerInfo, param -> String.format("%s-%s", providerName, param));
}

/** Like {@link providerSummary}, but using "$providerName-_init" in HTML anchors. */
@SuppressWarnings("unused") // Used by markdown template.
public String providerSummaryWithInitAnchor(String providerName, ProviderInfo providerInfo) {
return providerSummaryImpl(
providerName, providerInfo, param -> String.format("%s-_init-%s", providerName, param));
}

private String providerSummaryImpl(
String providerName, ProviderInfo providerInfo, UnaryOperator<String> paramAnchorNamer) {
ImmutableList<String> paramNames =
providerInfo.hasInit()
? providerInfo.getInit().getParameterList().stream()
.map(FunctionParamInfo::getName)
.collect(toImmutableList())
: providerInfo.getFieldInfoList().stream()
.map(field -> field.getName())
.collect(toImmutableList());
return summary(providerName, paramNames, paramAnchorNamer);
}

/**
Expand Down Expand Up @@ -310,14 +332,24 @@ public String funcSummary(StarlarkFunctionInfo funcInfo) {
return summary(funcInfo.getFunctionName(), paramNames);
}

private static String summary(String functionName, ImmutableList<String> paramNames) {
/**
* Returns a string representing the summary for a function or other callable.
*
* @param paramAnchorNamer translates a paremeter's name into the name of its HTML anchor
*/
private static String summary(
String functionName,
ImmutableList<String> paramNames,
UnaryOperator<String> paramAnchorNamer) {
ImmutableList<ImmutableList<String>> paramLines =
wrap(functionName, paramNames, MAX_LINE_LENGTH);
List<String> paramLinksLines = new ArrayList<>();
for (List<String> params : paramLines) {
String paramLinksLine =
params.stream()
.map(param -> String.format("<a href=\"#%s-%s\">%s</a>", functionName, param, param))
.map(
param ->
String.format("<a href=\"#%s\">%s</a>", paramAnchorNamer.apply(param), param))
.collect(joining(", "));
paramLinksLines.add(paramLinksLine);
}
Expand All @@ -326,6 +358,10 @@ private static String summary(String functionName, ImmutableList<String> paramNa
return String.format("%s(%s)", functionName, paramList);
}

private static String summary(String functionName, ImmutableList<String> paramNames) {
return summary(functionName, paramNames, param -> String.format("%s-%s", functionName, param));
}

/**
* Wraps the given function parameter names to be able to construct a function summary that stays
* within the provided line length limit.
Expand Down
75 changes: 74 additions & 1 deletion stardoc/templates/html_tables/provider.vm
Original file line number Diff line number Diff line change
@@ -1,12 +1,62 @@
<a id="${providerName}"></a>
#if ($providerInfo.hasInit() && $initParamNamesEqualFieldNames && !$initParamsHaveDistinctDocs && !$initParamsWithInferredDocs.isEmpty())
#set ($mergeParamsAndFields = true)
#else
#set ($mergeParamsAndFields = false)
#end

#[[##]]# ${providerName}

<pre>
#if ($providerInfo.hasInit() && !$mergeParamsAndFields)
${util.providerSummaryWithInitAnchor($providerName, $providerInfo)}
#else
${util.providerSummary($providerName, $providerInfo)}
#end
</pre>
#if (!$providerInfo.docString.isEmpty())

${providerInfo.docString}
#end
#if ($providerInfo.hasInit() && !$providerInfo.init.deprecated.docString.isEmpty())

#[[###]]# Deprecated

${providerInfo.init.deprecated.docString}
#end
#if ($providerInfo.hasInit() && !$providerInfo.init.parameterList.isEmpty() && !$mergeParamsAndFields)

#[[###]]# Constructor parameters

<table class="params-table">
<colgroup>
<col class="col-param" />
<col class="col-description" />
</colgroup>
<tbody>
#foreach ($param in $initParamsWithInferredDocs)
<tr id="${providerName}-_init-${param.name}">
<td><code>${param.name}</code></td>
<td>

${util.mandatoryString($param)}.
#if(!$param.getDefaultValue().isEmpty())
default is <code>$param.getDefaultValue()</code>
#end

#if (!$param.docString.isEmpty())
<p>

${param.docString.trim()}

${util.htmlEscape($providerInfo.docString)}
</p>
#end
</td>
</tr>
#end
</tbody>
</table>
#end

#if (!$providerInfo.fieldInfoList.isEmpty())
#[[###]]# Fields
Expand All @@ -17,6 +67,28 @@ ${util.htmlEscape($providerInfo.docString)}
<col class="col-description" />
</colgroup>
<tbody>
#if ($mergeParamsAndFields)
#foreach ($param in $initParamsWithInferredDocs)
<tr id="${providerName}-_init-${param.name}">
<td><code>${param.name}</code></td>
<td>

${util.mandatoryString($param)}.
#if(!$param.getDefaultValue().isEmpty())
default is <code>$param.getDefaultValue()</code>
#end

#if (!$param.docString.isEmpty())
<p>

${param.docString.trim()}

</p>
#end
</td>
</tr>
#end
#else
#foreach ($field in $providerInfo.fieldInfoList)
<tr id="${providerName}-${field.name}">
<td><code>${field.name}</code></td>
Expand All @@ -29,6 +101,7 @@ ${field.docString}
</td>
</tr>
#end
#end
</tbody>
</table>
#end
62 changes: 61 additions & 1 deletion stardoc/templates/markdown_tables/provider.vm
Original file line number Diff line number Diff line change
@@ -1,20 +1,80 @@
<a id="${providerName}"></a>
#if ($providerInfo.hasInit() && $initParamNamesEqualFieldNames && !$initParamsHaveDistinctDocs && !$initParamsWithInferredDocs.isEmpty())
#set ($mergeParamsAndFields = true)
#else
#set ($mergeParamsAndFields = false)
#end

#[[##]]# ${providerName}

<pre>
#if ($providerInfo.hasInit() && !$mergeParamsAndFields)
${util.providerSummaryWithInitAnchor($providerName, $providerInfo)}
#else
${util.providerSummary($providerName, $providerInfo)}
#end
</pre>
#if (!$providerInfo.docString.isEmpty())

${providerInfo.docString}
#end
#if ($providerInfo.hasInit() && !$providerInfo.init.deprecated.docString.isEmpty())

**FIELDS**
**DEPRECATED**

${providerInfo.init.deprecated.docString}
#end
#if ($providerInfo.hasInit() && !$providerInfo.init.parameterList.isEmpty() && !$mergeParamsAndFields)

**CONSTRUCTOR PARAMETERS**

| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
#foreach ($param in $initParamsWithInferredDocs)
| <a id="${providerName}-_init-${param.name}"></a>$param.name | ##
#if (!$param.docString.isEmpty())
${util.markdownCellFormat($param.docString)} ##
#else
<p align="center">-</p> ##
#end
| ##
#if (!$param.getDefaultValue().isEmpty())
${util.markdownCodeSpan($param.defaultValue)} ##
#else
none ##
#end
|
#end
#end
#if (!$providerInfo.fieldInfoList.isEmpty())

**FIELDS**

#if ($mergeParamsAndFields)
| Name | Description #if ($initParamsHaveDefaultValues)| Default Value #end|
| :------------- | :------------- #if ($initParamsHaveDefaultValues)| :------------- #end|
#foreach ($param in $initParamsWithInferredDocs)
| <a id="${providerName}-${param.name}"></a>$param.name | ##
#if (!$param.docString.isEmpty())
${util.markdownCellFormat($param.docString)} ##
#else
<p align="center"> - </p> ##
#end
#if($initParamsHaveDefaultValues)
| ##
#if (!$param.getDefaultValue().isEmpty())
${util.markdownCodeSpan($param.defaultValue)} ##
#else
none ##
#end
#end
|
#end
#else
| Name | Description |
| :------------- | :------------- |
#foreach ($field in $providerInfo.fieldInfoList)
| <a id="${providerName}-${field.name}"></a>$field.name | #if(!$field.docString.isEmpty()) ${util.markdownCellFormat($field.docString)} #else - #end |
#end
#end
#end
Loading

0 comments on commit 666b7ba

Please sign in to comment.