Linkify all flags in the Command Line Reference
This updates the option html emitter to add anchor links and id tags to
each generated flag in the command line reference. It also fixes its
color to be consistently red in the code block, instead of the standard
Bazel green for links.
Live demo:
https://bazel-docs-staging.netlify.com/versions/master/command-line-reference.html#flag--all_incompatible_changes
https://bazel-docs-staging.netlify.com/versions/master/command-line-reference.html#flag--experimental_oom_more_eagerly_threshold
When not hovering:

When hovering:

and that generates the link:
`https://docs.bazel.build/versions/<version>/command-line-reference.html#flag--experimental_oom_more_eagerly_threshold`
Expanded flags are also linkified:

There may be a concern that we don't guarantee that flags are only generated once, and can be duplicated, but because flag definitions don't change, linking to the first instance of the flag is safe.
Fixes https://github.com/bazelbuild/bazel/issues/3502
Change-Id: I6775feaa48558029b943b3206891e8b6860bb5d9
RELNOTES: None.
Closes #6954.
Change-Id: I37057365b0a117e269bdaa56bdec8153fe0a5b85
PiperOrigin-RevId: 226065220
diff --git a/site/_sass/style.scss b/site/_sass/style.scss
index d2feac9..474a8af 100644
--- a/site/_sass/style.scss
+++ b/site/_sass/style.scss
@@ -172,3 +172,11 @@
box-sizing: content-box !important;
}
}
+
+// Used for the Command-line Reference flag anchors. Linkifying the flag name
+// causes it to turn $link-color (green), but we turn it red here to be consistent
+// with the rest of the code block.
+dd > code > a,
+dl > dt > code > a {
+ color: #c7254e;
+}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsUsage.java b/src/main/java/com/google/devtools/common/options/OptionsUsage.java
index 6dee0eb..543b7a8 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsUsage.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsUsage.java
@@ -18,6 +18,7 @@
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.escape.Escaper;
import java.text.BreakIterator;
import java.util.ArrayList;
@@ -200,8 +201,25 @@
String flagName = getFlagName(optionDefinition);
String valueDescription = optionDefinition.getValueTypeHelpText();
String typeDescription = getTypeDescription(optionDefinition);
- usage.append("<dt><code><a name=\"flag--").append(plainFlagName).append("\"></a>--");
- usage.append(flagName);
+
+ // String.format is a lot slower, sometimes up to 10x.
+ // https://stackoverflow.com/questions/925423/is-it-better-practice-to-use-string-format-over-string-concatenation-in-java
+ //
+ // Considering that this runs for every flag in the CLI reference, it's better to use regular
+ // appends here.
+ usage
+ // Add the id of the flag to point anchor hrefs to it
+ .append("<dt id=\"flag--")
+ .append(plainFlagName)
+ .append("\">")
+ // Add the href to the id hash
+ .append("<code><a href=\"#flag--")
+ .append(plainFlagName)
+ .append("\">")
+ .append("--")
+ .append(flagName)
+ .append("</a>");
+
if (optionDefinition.usesBooleanValueSyntax() || optionDefinition.isVoidField()) {
// Nothing for boolean, tristate, boolean_or_enum, or void options.
} else if (!valueDescription.isEmpty()) {
@@ -249,14 +267,21 @@
Preconditions.checkArgument(!expansion.isEmpty());
expandsMsg = new StringBuilder("Expands to:<br/>\n");
for (String exp : expansion) {
- // TODO(ulfjack): We should link to the expanded flags, but unfortunately we don't
+ // TODO(jingwen): We link to the expanded flags here, but unfortunately we don't
// currently guarantee that all flags are only printed once. A flag in an OptionBase that
// is included by 2 different commands, but not inherited through a parent command, will
- // be printed multiple times.
+ // be printed multiple times. Clicking on the flag will bring the user to its first
+ // definition.
expandsMsg
- .append(" <code>")
+ .append(" ")
+ .append("<code><a href=\"#flag")
+ // Link to the '#flag--flag_name' hash.
+ // Some expansions are in the form of '--flag_name=value', so we drop everything from
+ // '=' onwards.
+ .append(Iterables.get(Splitter.on('=').split(escaper.escape(exp)), 0))
+ .append("\">")
.append(escaper.escape(exp))
- .append("</code><br/>\n");
+ .append("</a></code><br/>\n");
}
}
usage.append(expandsMsg.toString());
diff --git a/src/test/java/com/google/devtools/common/options/OptionsUsageTest.java b/src/test/java/com/google/devtools/common/options/OptionsUsageTest.java
index 6f35506..1f30d72 100644
--- a/src/test/java/com/google/devtools/common/options/OptionsUsageTest.java
+++ b/src/test/java/com/google/devtools/common/options/OptionsUsageTest.java
@@ -100,15 +100,15 @@
public void stringValue_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("test_string"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_string\"></a>"
- + "--test_string=<a string></code> default: \"test string default\"</dt>\n"
+ "<dt id=\"flag--test_string\"><code><a href=\"#flag--test_string\">--test_string</a>"
+ + "=<a string></code> default: \"test string default\"</dt>\n"
+ "<dd>\n"
+ "a string-valued option to test simple option operations\n"
+ "</dd>\n");
assertThat(getHtmlUsageWithTags("test_string"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_string\"></a>"
- + "--test_string=<a string></code> default: \"test string default\"</dt>\n"
+ "<dt id=\"flag--test_string\"><code><a href=\"#flag--test_string\">--test_string</a>"
+ + "=<a string></code> default: \"test string default\"</dt>\n"
+ "<dd>\n"
+ "a string-valued option to test simple option operations\n"
+ "<br>Tags: \n"
@@ -149,15 +149,17 @@
public void intValue_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("expanded_c"))
.isEqualTo(
- "<dt><code><a name=\"flag--expanded_c\"></a>"
- + "--expanded_c=<an integer></code> default: \"12\"</dt>\n"
+ "<dt id=\"flag--expanded_c\"><code>"
+ + "<a href=\"#flag--expanded_c\">--expanded_c</a>"
+ + "=<an integer></code> default: \"12\"</dt>\n"
+ "<dd>\n"
+ "an int-value'd flag used to test expansion logic\n"
+ "</dd>\n");
assertThat(getHtmlUsageWithTags("expanded_c"))
.isEqualTo(
- "<dt><code><a name=\"flag--expanded_c\"></a>"
- + "--expanded_c=<an integer></code> default: \"12\"</dt>\n"
+ "<dt id=\"flag--expanded_c\"><code>"
+ + "<a href=\"#flag--expanded_c\">--expanded_c</a>"
+ + "=<an integer></code> default: \"12\"</dt>\n"
+ "<dd>\n"
+ "an int-value'd flag used to test expansion logic\n"
+ "<br>Tags: \n"
@@ -197,8 +199,8 @@
public void booleanValue_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("expanded_a"))
.isEqualTo(
- "<dt><code><a name=\"flag--expanded_a\"></a>"
- + "--[no]expanded_a</code> default: \"true\"</dt>\n"
+ "<dt id=\"flag--expanded_a\"><code><a href=\"#flag--expanded_a\">"
+ + "--[no]expanded_a</a></code> default: \"true\"</dt>\n"
+ "<dd>\n"
+ "A boolean flag with unknown effect to test tagless usage text.\n"
+ "</dd>\n");
@@ -238,16 +240,18 @@
public void multipleValue_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("test_multiple_string"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_multiple_string\"></a>"
- + "--test_multiple_string=<a string></code> "
+ "<dt id=\"flag--test_multiple_string\"><code>"
+ + "<a href=\"#flag--test_multiple_string\">--test_multiple_string</a>"
+ + "=<a string></code> "
+ "multiple uses are accumulated</dt>\n"
+ "<dd>\n"
+ "a repeatable string-valued flag with its own unhelpful help text\n"
+ "</dd>\n");
assertThat(getHtmlUsageWithTags("test_multiple_string"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_multiple_string\"></a>"
- + "--test_multiple_string=<a string></code> "
+ "<dt id=\"flag--test_multiple_string\"><code>"
+ + "<a href=\"#flag--test_multiple_string\">--test_multiple_string</a>"
+ + "=<a string></code> "
+ "multiple uses are accumulated</dt>\n"
+ "<dd>\n"
+ "a repeatable string-valued flag with its own unhelpful help text\n"
@@ -291,8 +295,9 @@
public void customConverterValue_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("test_list_converters"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_list_converters\"></a>"
- + "--test_list_converters=<a list of strings></code> "
+ "<dt id=\"flag--test_list_converters\"><code>"
+ + "<a href=\"#flag--test_list_converters\">--test_list_converters</a>"
+ + "=<a list of strings></code> "
+ "multiple uses are accumulated</dt>\n"
+ "<dd>\n"
+ "a repeatable flag that accepts lists, but doesn't want to have lists of \n"
@@ -300,8 +305,9 @@
+ "</dd>\n");
assertThat(getHtmlUsageWithTags("test_list_converters"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_list_converters\"></a>"
- + "--test_list_converters=<a list of strings></code> "
+ "<dt id=\"flag--test_list_converters\"><code>"
+ + "<a href=\"#flag--test_list_converters\">--test_list_converters</a>"
+ + "=<a list of strings></code> "
+ "multiple uses are accumulated</dt>\n"
+ "<dd>\n"
+ "a repeatable flag that accepts lists, but doesn't want to have lists of \n"
@@ -348,33 +354,37 @@
public void staticExpansionOption_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("test_expansion"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_expansion\"></a>"
- + "--test_expansion</code></dt>\n"
+ "<dt id=\"flag--test_expansion\"><code><a href=\"#flag--test_expansion\">"
+ + "--test_expansion</a></code></dt>\n"
+ "<dd>\n"
+ "this expands to an alphabet soup.\n"
+ "<br/>\n"
+ "Expands to:<br/>\n"
- + " <code>--noexpanded_a</code><br/>\n"
- + " <code>--expanded_b=false</code><br/>\n"
- + " <code>--expanded_c</code><br/>\n"
- + " <code>42</code><br/>\n"
- + " <code>--expanded_d</code><br/>\n"
- + " <code>bar</code><br/>\n"
+ + " <code>"
+ + "<a href=\"#flag--noexpanded_a\">--noexpanded_a</a></code><br/>\n"
+ + " <code>"
+ + "<a href=\"#flag--expanded_b\">--expanded_b=false</a></code><br/>\n"
+ + " <code><a href=\"#flag--expanded_c\">--expanded_c</a></code><br/>\n"
+ + " <code><a href=\"#flag42\">42</a></code><br/>\n"
+ + " <code><a href=\"#flag--expanded_d\">--expanded_d</a></code><br/>\n"
+ + " <code><a href=\"#flagbar\">bar</a></code><br/>\n"
+ "</dd>\n");
assertThat(getHtmlUsageWithTags("test_expansion"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_expansion\"></a>"
- + "--test_expansion</code></dt>\n"
+ "<dt id=\"flag--test_expansion\"><code><a href=\"#flag--test_expansion\">"
+ + "--test_expansion</a></code></dt>\n"
+ "<dd>\n"
+ "this expands to an alphabet soup.\n"
+ "<br/>\n"
+ "Expands to:<br/>\n"
- + " <code>--noexpanded_a</code><br/>\n"
- + " <code>--expanded_b=false</code><br/>\n"
- + " <code>--expanded_c</code><br/>\n"
- + " <code>42</code><br/>\n"
- + " <code>--expanded_d</code><br/>\n"
- + " <code>bar</code><br/>\n"
+ + " <code>"
+ + "<a href=\"#flag--noexpanded_a\">--noexpanded_a</a></code><br/>\n"
+ + " <code>"
+ + "<a href=\"#flag--expanded_b\">--expanded_b=false</a></code><br/>\n"
+ + " <code><a href=\"#flag--expanded_c\">--expanded_c</a></code><br/>\n"
+ + " <code><a href=\"#flag42\">42</a></code><br/>\n"
+ + " <code><a href=\"#flag--expanded_d\">--expanded_d</a></code><br/>\n"
+ + " <code><a href=\"#flagbar\">bar</a></code><br/>\n"
+ "<br>Tags: \n"
+ "<a href=\"#effect_tag_NO_OP\"><code>no_op</code></a>"
+ "</dd>\n");
@@ -424,25 +434,27 @@
public void recursiveExpansionOption_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("test_recursive_expansion_top_level"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_recursive_expansion_top_level\"></a>"
- + "--test_recursive_expansion_top_level</code></dt>\n"
+ "<dt id=\"flag--test_recursive_expansion_top_level\">"
+ + "<code><a href=\"#flag--test_recursive_expansion_top_level\">"
+ + "--test_recursive_expansion_top_level</a></code></dt>\n"
+ "<dd>\n"
+ "Lets the children do all the work.\n"
+ "<br/>\n"
+ "Expands to:<br/>\n"
- + " <code>--test_recursive_expansion_middle1</code><br/>\n"
- + " <code>--test_recursive_expansion_middle2</code><br/>\n"
+ + " <code><a href=\"#flag--test_recursive_expansion_middle1\">--test_recursive_expansion_middle1</a></code><br/>\n"
+ + " <code><a href=\"#flag--test_recursive_expansion_middle2\">--test_recursive_expansion_middle2</a></code><br/>\n"
+ "</dd>\n");
assertThat(getHtmlUsageWithTags("test_recursive_expansion_top_level"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_recursive_expansion_top_level\"></a>"
- + "--test_recursive_expansion_top_level</code></dt>\n"
+ "<dt id=\"flag--test_recursive_expansion_top_level\">"
+ + "<code><a href=\"#flag--test_recursive_expansion_top_level\">"
+ + "--test_recursive_expansion_top_level</a></code></dt>\n"
+ "<dd>\n"
+ "Lets the children do all the work.\n"
+ "<br/>\n"
+ "Expands to:<br/>\n"
- + " <code>--test_recursive_expansion_middle1</code><br/>\n"
- + " <code>--test_recursive_expansion_middle2</code><br/>\n"
+ + " <code><a href=\"#flag--test_recursive_expansion_middle1\">--test_recursive_expansion_middle1</a></code><br/>\n"
+ + " <code><a href=\"#flag--test_recursive_expansion_middle2\">--test_recursive_expansion_middle2</a></code><br/>\n"
+ "<br>Tags: \n"
+ "<a href=\"#effect_tag_NO_OP\"><code>no_op</code></a>"
+ "</dd>\n");
@@ -485,25 +497,25 @@
public void expansionToMultipleValue_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("test_expansion_to_repeatable"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_expansion_to_repeatable\"></a>"
- + "--test_expansion_to_repeatable</code></dt>\n"
+ "<dt id=\"flag--test_expansion_to_repeatable\"><code><a href=\"#flag--test_expansion_to_repeatable\">--test_expansion_to_repeatable</a>"
+ + "</code></dt>\n"
+ "<dd>\n"
+ "Go forth and multiply, they said.\n"
+ "<br/>\n"
+ "Expands to:<br/>\n"
- + " <code>--test_multiple_string=expandedFirstValue</code><br/>\n"
- + " <code>--test_multiple_string=expandedSecondValue</code><br/>\n"
+ + " <code><a href=\"#flag--test_multiple_string\">--test_multiple_string=expandedFirstValue</a></code><br/>\n"
+ + " <code><a href=\"#flag--test_multiple_string\">--test_multiple_string=expandedSecondValue</a></code><br/>\n"
+ "</dd>\n");
assertThat(getHtmlUsageWithTags("test_expansion_to_repeatable"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_expansion_to_repeatable\"></a>"
- + "--test_expansion_to_repeatable</code></dt>\n"
+ "<dt id=\"flag--test_expansion_to_repeatable\"><code><a href=\"#flag--test_expansion_to_repeatable\">--test_expansion_to_repeatable</a>"
+ + "</code></dt>\n"
+ "<dd>\n"
+ "Go forth and multiply, they said.\n"
+ "<br/>\n"
+ "Expands to:<br/>\n"
- + " <code>--test_multiple_string=expandedFirstValue</code><br/>\n"
- + " <code>--test_multiple_string=expandedSecondValue</code><br/>\n"
+ + " <code><a href=\"#flag--test_multiple_string\">--test_multiple_string=expandedFirstValue</a></code><br/>\n"
+ + " <code><a href=\"#flag--test_multiple_string\">--test_multiple_string=expandedSecondValue</a></code><br/>\n"
+ "<br>Tags: \n"
+ "<a href=\"#effect_tag_NO_OP\"><code>no_op</code></a>"
+ "</dd>\n");
@@ -546,16 +558,18 @@
public void implicitRequirementOption_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("test_implicit_requirement"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_implicit_requirement\"></a>"
- + "--test_implicit_requirement=<a string></code> "
+ "<dt id=\"flag--test_implicit_requirement\"><code>"
+ + "<a href=\"#flag--test_implicit_requirement\">--test_implicit_requirement</a>"
+ + "=<a string></code> "
+ "default: \"direct implicit\"</dt>\n"
+ "<dd>\n"
+ "this option really needs that other one, isolation of purpose has failed.\n"
+ "</dd>\n");
assertThat(getHtmlUsageWithTags("test_implicit_requirement"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_implicit_requirement\"></a>"
- + "--test_implicit_requirement=<a string></code> "
+ "<dt id=\"flag--test_implicit_requirement\"><code>"
+ + "<a href=\"#flag--test_implicit_requirement\">--test_implicit_requirement</a>"
+ + "=<a string></code> "
+ "default: \"direct implicit\"</dt>\n"
+ "<dd>\n"
+ "this option really needs that other one, isolation of purpose has failed.\n"
@@ -599,25 +613,31 @@
public void expansionFunctionOptionThatExpandsBasedOnOtherLoadedOptions_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("prefix_expansion"))
.isEqualTo(
- "<dt><code><a name=\"flag--prefix_expansion\"></a>"
- + "--prefix_expansion</code></dt>\n"
+ "<dt id=\"flag--prefix_expansion\"><code>"
+ + "<a href=\"#flag--prefix_expansion\">--prefix_expansion</a>"
+ + "</code></dt>\n"
+ "<dd>\n"
+ "Expands to all options with a specific prefix.\n"
+ "<br/>\n"
+ "Expands to:<br/>\n"
- + " <code>--specialexp_bar</code><br/>\n"
- + " <code>--specialexp_foo</code><br/>\n"
+ + " <code>"
+ + "<a href=\"#flag--specialexp_bar\">--specialexp_bar</a></code><br/>\n"
+ + " <code>"
+ + "<a href=\"#flag--specialexp_foo\">--specialexp_foo</a></code><br/>\n"
+ "</dd>\n");
assertThat(getHtmlUsageWithTags("prefix_expansion"))
.isEqualTo(
- "<dt><code><a name=\"flag--prefix_expansion\"></a>"
- + "--prefix_expansion</code></dt>\n"
+ "<dt id=\"flag--prefix_expansion\"><code>"
+ + "<a href=\"#flag--prefix_expansion\">--prefix_expansion</a>"
+ + "</code></dt>\n"
+ "<dd>\n"
+ "Expands to all options with a specific prefix.\n"
+ "<br/>\n"
+ "Expands to:<br/>\n"
- + " <code>--specialexp_bar</code><br/>\n"
- + " <code>--specialexp_foo</code><br/>\n"
+ + " <code>"
+ + "<a href=\"#flag--specialexp_bar\">--specialexp_bar</a></code><br/>\n"
+ + " <code>"
+ + "<a href=\"#flag--specialexp_foo\">--specialexp_foo</a></code><br/>\n"
+ "<br>Tags: \n"
+ "<a href=\"#effect_tag_NO_OP\"><code>no_op</code></a>"
+ "</dd>\n");
@@ -658,25 +678,27 @@
public void tagHeavyExpansionOption_htmlOutput() {
assertThat(getHtmlUsageWithoutTags("test_void_expansion_function"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_void_expansion_function\"></a>"
- + "--test_void_expansion_function</code></dt>\n"
+ "<dt id=\"flag--test_void_expansion_function\"><code>"
+ + "<a href=\"#flag--test_void_expansion_function\">--test_void_expansion_function</a>"
+ + "</code></dt>\n"
+ "<dd>\n"
+ "Listing a ton of random tags to test the usage output.\n"
+ "<br/>\n"
+ "Expands to:<br/>\n"
- + " <code>--expanded_d</code><br/>\n"
- + " <code>void expanded</code><br/>\n"
+ + " <code><a href=\"#flag--expanded_d\">--expanded_d</a></code><br/>\n"
+ + " <code><a href=\"#flagvoid expanded\">void expanded</a></code><br/>\n"
+ "</dd>\n");
assertThat(getHtmlUsageWithTags("test_void_expansion_function"))
.isEqualTo(
- "<dt><code><a name=\"flag--test_void_expansion_function\"></a>"
- + "--test_void_expansion_function</code></dt>\n"
+ "<dt id=\"flag--test_void_expansion_function\"><code>"
+ + "<a href=\"#flag--test_void_expansion_function\">--test_void_expansion_function</a>"
+ + "</code></dt>\n"
+ "<dd>\n"
+ "Listing a ton of random tags to test the usage output.\n"
+ "<br/>\n"
+ "Expands to:<br/>\n"
- + " <code>--expanded_d</code><br/>\n"
- + " <code>void expanded</code><br/>\n"
+ + " <code><a href=\"#flag--expanded_d\">--expanded_d</a></code><br/>\n"
+ + " <code><a href=\"#flagvoid expanded\">void expanded</a></code><br/>\n"
+ "<br>Tags: \n"
+ "<a href=\"#effect_tag_ACTION_COMMAND_LINES\"><code>action_command_lines</code>"
+ "</a>, "