Skip to content

Commit

Permalink
Render documentation for provider init callbacks
Browse files Browse the repository at this point in the history
If a provider has a custom `init` callback, we want the summary blurb
to show `init`'s parameters (since these are what the user will interact
with); we have to render constructor paramaters and fields separately
(and using separate html anchors) since there is not necessarily a 1-1
relationship between them, and since they may have different docs.

Fixes bazelbuild#182
  • Loading branch information
tetromino committed May 20, 2024
1 parent be1a9a8 commit d3ba6d5
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 8 deletions.
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,28 @@ 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);
if (providerInfo.hasInit()) {
ImmutableList<String> paramNames =
providerInfo.getInit().getParameterList().stream()
.map(FunctionParamInfo::getName)
.collect(toImmutableList());
return summary(
providerName, paramNames, param -> String.format("%s-_init-%s", providerName, param));
} else {
ImmutableList<String> fieldNames =
providerInfo.getFieldInfoList().stream()
.map(field -> field.getName())
.collect(toImmutableList());
return summary(providerName, fieldNames);
}
}

/**
Expand Down Expand Up @@ -310,14 +324,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 +350,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
20 changes: 20 additions & 0 deletions stardoc/templates/markdown_tables/provider.vm
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,28 @@
<pre>
${util.providerSummary($providerName, $providerInfo)}
</pre>
#if ($providerInfo.hasInit() && !$providerInfo.init.docString.isEmpty())

${providerInfo.init.docString}
#end

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

**DEPRECATED**

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

**CONSTRUCTOR PARAMETERS**

| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
#foreach ($param in $providerInfo.init.parameterList)
| <a id="${providerInfo.init.functionName}-_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

**FIELDS**

Expand Down
55 changes: 55 additions & 0 deletions test/testdata/provider_basic_test/golden.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,61 @@



<a id="MyCustomInitInfo"></a>

## MyCustomInitInfo

<pre>
MyCustomInitInfo(<a href="#MyCustomInitInfo-_init-foo">foo</a>, <a href="#MyCustomInitInfo-_init-bar">bar</a>)
</pre>

MyCustomInfo constructor.

A provider with a custom constructor.

**CONSTRUCTOR PARAMETERS**

| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="MyCustomInitInfo-_init-foo"></a>foo | Foo data; must be non-negative | none |
| <a id="MyCustomInitInfo-_init-bar"></a>bar | Bar data | `42` |

**FIELDS**


| Name | Description |
| :------------- | :------------- |
| <a id="MyCustomInitInfo-foo"></a>foo | Foo data |
| <a id="MyCustomInitInfo-bar"></a>bar | Bar data |
| <a id="MyCustomInitInfo-validated"></a>validated | Whether the data has been validated |


<a id="MyDeprecatedInfo"></a>

## MyDeprecatedInfo

<pre>
MyDeprecatedInfo()
</pre>

MyDeprecatedInfo constructor.

You can read this info.

But should you really construct it?

**DEPRECATED**

Do not construct!

**FIELDS**


| Name | Description |
| :------------- | :------------- |
| <a id="MyDeprecatedInfo-foo"></a>foo | Foo |


<a id="MyFooInfo"></a>

## MyFooInfo
Expand Down
44 changes: 44 additions & 0 deletions test/testdata/provider_basic_test/input.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,50 @@ MyVeryDocumentedInfo = provider(
},
)

def _my_custom_info_init(foo, bar = 42):
"""
MyCustomInfo constructor.
Args:
foo: Foo data; must be non-negative
bar: Bar data
"""
if foo < 0:
fail("foo must be non-negative")

return {"foo": foo, "bar": bar, "validated": True}

MyCustomInitInfo, _new_my_custom_init_info = provider(
doc = "A provider with a custom constructor.",
init = _my_custom_info_init,
fields = {
"foo": "Foo data",
"bar": "Bar data",
"validated": "Whether the data has been validated",
},
)

def _my_deprecated_info_init():
"""
MyDeprecatedInfo constructor.
Deprecated:
Do not construct!
"""
return {}

MyDeprecatedInfo, _new_my_deprecated_info = provider(
doc = """
You can read this info.
But should you really construct it?
""",
init = _my_deprecated_info_init,
fields = {
"foo": "Foo",
},
)

named_providers_are_hashable = {
MyFooInfo: "MyFooInfo is hashable",
MyVeryDocumentedInfo: "So is MyVeryDocumentedInfo",
Expand Down

0 comments on commit d3ba6d5

Please sign in to comment.