aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google
diff options
context:
space:
mode:
authorGravatar Ulf Adams <ulfjack@google.com>2016-06-22 09:24:28 +0000
committerGravatar Philipp Wollermann <philwo@google.com>2016-06-22 10:49:24 +0000
commit352211d7662490ae87115f761aa4cc1e08142529 (patch)
tree5903f7334ee45db808cfb7eb370e4697ab10c0ec /src/main/java/com/google
parentbb08e15859096be7a8c04fa54c21af6483febac6 (diff)
The help command can now output html for the command-line reference page.
An upcoming change will pipe this to an actual page. -- MOS_MIGRATED_REVID=125545220
Diffstat (limited to 'src/main/java/com/google')
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java9
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java125
-rw-r--r--src/main/java/com/google/devtools/common/options/OptionsParser.java48
-rw-r--r--src/main/java/com/google/devtools/common/options/OptionsUsage.java46
4 files changed, 212 insertions, 16 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java
index f7bcd1d224..722baea810 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java
@@ -24,7 +24,6 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -35,7 +34,7 @@ public class BlazeCommandUtils {
/**
* Options classes used as startup options in Blaze core.
*/
- private static final List<Class<? extends OptionsBase>> DEFAULT_STARTUP_OPTIONS =
+ private static final ImmutableList<Class<? extends OptionsBase>> DEFAULT_STARTUP_OPTIONS =
ImmutableList.<Class<? extends OptionsBase>>of(
BlazeServerStartupOptions.class,
HostJvmStartupOptions.class);
@@ -43,7 +42,7 @@ public class BlazeCommandUtils {
/**
* The set of option-classes that are common to all Blaze commands.
*/
- private static final Collection<Class<? extends OptionsBase>> COMMON_COMMAND_OPTIONS =
+ private static final ImmutableList<Class<? extends OptionsBase>> COMMON_COMMAND_OPTIONS =
ImmutableList.of(CommonCommandOptions.class, BlazeCommandEventHandler.Options.class);
@@ -60,6 +59,10 @@ public class BlazeCommandUtils {
return ImmutableList.copyOf(options);
}
+ public static ImmutableList<Class<? extends OptionsBase>> getCommonOptions() {
+ return COMMON_COMMAND_OPTIONS;
+ }
+
/**
* Returns the set of all options (including those inherited directly and
* transitively) for this AbstractCommand's @Command annotation.
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java
index 8e11e08cde..ba199923a1 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java
@@ -16,6 +16,10 @@ package com.google.devtools.build.lib.runtime.commands;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Iterables;
+import com.google.common.escape.Escaper;
+import com.google.common.html.HtmlEscapers;
import com.google.devtools.build.docgen.BlazeRuleHelpPrinter;
import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
@@ -29,6 +33,7 @@ import com.google.devtools.build.lib.runtime.BlazeRuntime;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.util.ExitCode;
+import com.google.devtools.build.lib.util.StringUtil;
import com.google.devtools.build.lib.util.io.OutErr;
import com.google.devtools.common.options.Converters;
import com.google.devtools.common.options.Option;
@@ -38,7 +43,9 @@ import com.google.devtools.common.options.OptionsProvider;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -56,6 +63,12 @@ import java.util.Set;
public final class HelpCommand implements BlazeCommand {
private static final Joiner SPACE_JOINER = Joiner.on(" ");
+ /**
+ * Only to be used to escape the internal hard-coded help texts when outputting HTML from help,
+ * which don't pose a security risk.
+ */
+ private static final Escaper HTML_ESCAPER = HtmlEscapers.htmlEscaper();
+
public static class Options extends OptionsBase {
@Option(name = "help_verbosity",
@@ -85,7 +98,7 @@ public final class HelpCommand implements BlazeCommand {
* Returns a map that maps option categories to descriptive help strings for categories that
* are not part of the Bazel core.
*/
- private ImmutableMap<String, String> getOptionCategories(BlazeRuntime runtime) {
+ private static ImmutableMap<String, String> getOptionCategories(BlazeRuntime runtime) {
ImmutableMap.Builder<String, String> optionCategoriesBuilder = ImmutableMap.builder();
String name = runtime.getProductName();
optionCategoriesBuilder
@@ -141,7 +154,7 @@ public final class HelpCommand implements BlazeCommand {
Options helpOptions = options.getOptions(Options.class);
if (options.getResidue().isEmpty()) {
emitBlazeVersionInfo(outErr, runtime.getProductName());
- emitGenericHelp(runtime, outErr);
+ emitGenericHelp(outErr, runtime);
return ExitCode.SUCCESS;
}
if (options.getResidue().size() != 1) {
@@ -151,7 +164,8 @@ public final class HelpCommand implements BlazeCommand {
String helpSubject = options.getResidue().get(0);
if (helpSubject.equals("startup_options")) {
emitBlazeVersionInfo(outErr, runtime.getProductName());
- emitStartupOptions(outErr, helpOptions.helpVerbosity, runtime, getOptionCategories(runtime));
+ emitStartupOptions(
+ outErr, helpOptions.helpVerbosity, runtime, getOptionCategories(runtime));
return ExitCode.SUCCESS;
} else if (helpSubject.equals("target-syntax")) {
emitBlazeVersionInfo(outErr, runtime.getProductName());
@@ -163,6 +177,9 @@ public final class HelpCommand implements BlazeCommand {
} else if (helpSubject.equals("completion")) {
emitCompletionHelp(runtime, outErr);
return ExitCode.SUCCESS;
+ } else if (helpSubject.equals("everything-as-html")) {
+ new HtmlEmitter(runtime).emit(outErr);
+ return ExitCode.SUCCESS;
}
BlazeCommand command = runtime.getCommandMap().get(helpSubject);
@@ -196,7 +213,6 @@ public final class HelpCommand implements BlazeCommand {
outErr.printOut(String.format("%80s\n", line));
}
- @SuppressWarnings("unchecked") // varargs generic array creation
private void emitStartupOptions(OutErr outErr, OptionsParser.HelpVerbosity helpVerbosity,
BlazeRuntime runtime, ImmutableMap<String, String> optionCategories) {
outErr.printOut(
@@ -213,10 +229,9 @@ public final class HelpCommand implements BlazeCommand {
// First startup_options
Iterable<BlazeModule> blazeModules = runtime.getBlazeModules();
ConfiguredRuleClassProvider ruleClassProvider = runtime.getRuleClassProvider();
- Map<String, BlazeCommand> commandsByName = runtime.getCommandMap();
- Set<String> commands = commandsByName.keySet();
+ Map<String, BlazeCommand> commandsByName = getSortedCommands(runtime);
- outErr.printOutLn("BAZEL_COMMAND_LIST=\"" + SPACE_JOINER.join(commands) + "\"");
+ outErr.printOutLn("BAZEL_COMMAND_LIST=\"" + SPACE_JOINER.join(commandsByName.keySet()) + "\"");
outErr.printOutLn("BAZEL_INFO_KEYS=\"");
for (String name : InfoCommand.getHardwiredInfoItemNames(runtime.getProductName())) {
@@ -230,9 +245,9 @@ public final class HelpCommand implements BlazeCommand {
outErr.printOut(OptionsParser.newOptionsParser(options).getOptionsCompletion());
outErr.printOutLn("\"");
- for (String name : commands) {
- BlazeCommand command = commandsByName.get(name);
- String varName = name.toUpperCase().replace('-', '_');
+ for (Map.Entry<String, BlazeCommand> e : commandsByName.entrySet()) {
+ BlazeCommand command = e.getValue();
+ String varName = e.getKey().toUpperCase(Locale.US).replace('-', '_');
Command annotation = command.getClass().getAnnotation(Command.class);
if (!annotation.completion().isEmpty()) {
outErr.printOutLn("BAZEL_COMMAND_" + varName + "_ARGUMENT=\""
@@ -245,6 +260,10 @@ public final class HelpCommand implements BlazeCommand {
}
}
+ private static Map<String, BlazeCommand> getSortedCommands(BlazeRuntime runtime) {
+ return ImmutableSortedMap.copyOf(runtime.getCommandMap());
+ }
+
private void emitTargetSyntaxHelp(OutErr outErr, ImmutableMap<String, String> optionCategories,
String productName) {
outErr.printOut(BlazeCommandUtils.expandHelpTopic("target-syntax",
@@ -264,10 +283,9 @@ public final class HelpCommand implements BlazeCommand {
}
}
- private void emitGenericHelp(BlazeRuntime runtime, OutErr outErr) {
+ private void emitGenericHelp(OutErr outErr, BlazeRuntime runtime) {
outErr.printOut(String.format("Usage: %s <command> <options> ...\n\n",
runtime.getProductName()));
-
outErr.printOut("Available commands:\n");
Map<String, BlazeCommand> commandsByName = runtime.getCommandMap();
@@ -298,4 +316,87 @@ public final class HelpCommand implements BlazeCommand {
outErr.printOut(String.format(" %s help info-keys\n", runtime.getProductName()));
outErr.printOut(" Displays a list of keys used by the info command.\n");
}
+
+ private static final class HtmlEmitter {
+ private final BlazeRuntime runtime;
+ private final ImmutableMap<String, String> optionCategories;
+
+ private HtmlEmitter(BlazeRuntime runtime) {
+ this.runtime = runtime;
+ this.optionCategories = getOptionCategories(runtime);
+ }
+
+ private void emit(OutErr outErr) {
+ Map<String, BlazeCommand> commandsByName = getSortedCommands(runtime);
+ StringBuilder result = new StringBuilder();
+ result.append("<h2>Commands</h2>\n");
+ result.append("<table>\n");
+ for (Map.Entry<String, BlazeCommand> e : commandsByName.entrySet()) {
+ BlazeCommand command = e.getValue();
+ Command annotation = command.getClass().getAnnotation(Command.class);
+ if (annotation.hidden()) {
+ continue;
+ }
+ String shortDescription = annotation.shortDescription().
+ replace("%{product}", runtime.getProductName());
+
+ result.append("<tr>\n");
+ result.append(
+ String.format(
+ " <td><a href=\"#%s\"><code>%s</code></a></td>\n", e.getKey(), e.getKey()));
+ result.append(" <td>").append(HTML_ESCAPER.escape(shortDescription)).append("</td>\n");
+ result.append("</tr>\n");
+ }
+ result.append("</table>\n");
+ result.append("\n");
+
+ result.append("<h2>Startup Options</h2>\n");
+ appendOptionsHtml(result, BlazeCommandUtils.getStartupOptions(runtime.getBlazeModules()));
+ result.append("\n");
+
+ result.append("<h2><a name=\"common_options\">Options Common to all Commands</a></h2>\n");
+ appendOptionsHtml(result, BlazeCommandUtils.getCommonOptions());
+ result.append("\n");
+
+ for (Map.Entry<String, BlazeCommand> e : commandsByName.entrySet()) {
+ result.append(
+ String.format(
+ "<h2><a name=\"%s\">%s Options</a></h2>\n", e.getKey(), capitalize(e.getKey())));
+ BlazeCommand command = e.getValue();
+ Command annotation = command.getClass().getAnnotation(Command.class);
+ if (annotation.hidden()) {
+ continue;
+ }
+ List<String> inheritedCmdNames = new ArrayList<>();
+ for (Class<? extends BlazeCommand> base : annotation.inherits()) {
+ String name = base.getAnnotation(Command.class).name();
+ inheritedCmdNames.add(String.format("<a href=\"#%s\">%s</a>", name, name));
+ }
+ if (!inheritedCmdNames.isEmpty()) {
+ result.append("<p>Inherits all options from ");
+ result.append(StringUtil.joinEnglishList(inheritedCmdNames, "and"));
+ result.append(".</p>\n\n");
+ }
+ Set<Class<? extends OptionsBase>> options = new HashSet<>();
+ Collections.addAll(options, annotation.options());
+ for (BlazeModule blazeModule : runtime.getBlazeModules()) {
+ Iterables.addAll(options, blazeModule.getCommandOptions(annotation));
+ }
+ appendOptionsHtml(result, options);
+ result.append("\n");
+ }
+ outErr.printOut(result.toString());
+ }
+
+ private void appendOptionsHtml(
+ StringBuilder result, Iterable<Class<? extends OptionsBase>> optionsClasses) {
+ OptionsParser parser = OptionsParser.newOptionsParser(optionsClasses);
+ result.append(parser.describeOptionsHtml(optionCategories, HTML_ESCAPER)
+ .replace("%{product}", runtime.getProductName()));
+ }
+
+ private static String capitalize(String s) {
+ return s.substring(0, 1).toUpperCase(Locale.US) + s.substring(1);
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParser.java b/src/main/java/com/google/devtools/common/options/OptionsParser.java
index 83a7b08b3a..301f2d49be 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParser.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsParser.java
@@ -21,6 +21,7 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.escape.Escaper;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -436,7 +437,6 @@ public class OptionsParser implements OptionsProvider {
HelpVerbosity helpVerbosity) {
StringBuilder desc = new StringBuilder();
if (!impl.getOptionsClasses().isEmpty()) {
-
List<Field> allFields = Lists.newArrayList();
for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) {
allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass));
@@ -466,6 +466,52 @@ public class OptionsParser implements OptionsProvider {
}
/**
+ * Returns a description of all the options this parser can digest.
+ * In addition to {@link Option} annotations, this method also
+ * interprets {@link OptionsUsage} annotations which give an intuitive short
+ * description for the options.
+ *
+ * @param categoryDescriptions a mapping from category names to category
+ * descriptions. Options of the same category (see {@link
+ * Option#category}) will be grouped together, preceded by the description
+ * of the category.
+ */
+ public String describeOptionsHtml(Map<String, String> categoryDescriptions, Escaper escaper) {
+ StringBuilder desc = new StringBuilder();
+ if (!impl.getOptionsClasses().isEmpty()) {
+ List<Field> allFields = Lists.newArrayList();
+ for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) {
+ allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass));
+ }
+ Collections.sort(allFields, OptionsUsage.BY_CATEGORY);
+ String prevCategory = null;
+
+ for (Field optionField : allFields) {
+ String category = optionField.getAnnotation(Option.class).category();
+ DocumentationLevel level = documentationLevel(category);
+ if (!category.equals(prevCategory) && level == DocumentationLevel.DOCUMENTED) {
+ String description = categoryDescriptions.get(category);
+ if (description == null) {
+ description = "Options category '" + category + "'";
+ }
+ if (prevCategory != null) {
+ desc.append("</dl>\n\n");
+ }
+ desc.append(escaper.escape(description)).append(":\n");
+ desc.append("<dl>");
+ prevCategory = category;
+ }
+
+ if (level == DocumentationLevel.DOCUMENTED) {
+ OptionsUsage.getUsageHtml(optionField, desc, escaper);
+ }
+ }
+ desc.append("</dl>\n");
+ }
+ return desc.toString();
+ }
+
+ /**
* Returns a string listing the possible flag completion for this command along with the command
* completion if any. See {@link OptionsUsage#getCompletion(Field, StringBuilder)} for more
* details on the format for the flag completion.
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 e79a062ce4..b64506dd1e 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsUsage.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsUsage.java
@@ -19,6 +19,7 @@ import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
+import com.google.common.escape.Escaper;
import java.lang.reflect.Field;
import java.text.BreakIterator;
@@ -127,6 +128,51 @@ class OptionsUsage {
}
/**
+ * Append the usage message for a single option-field message to 'usage'.
+ */
+ static void getUsageHtml(Field optionField, StringBuilder usage, Escaper escaper) {
+ String flagName = getFlagName(optionField);
+ String typeDescription = getTypeDescription(optionField);
+ Option annotation = optionField.getAnnotation(Option.class);
+ usage.append("<dt><code>--").append(flagName).append("</code>");
+ if (annotation.abbrev() != '\0') {
+ usage.append(" [<code>-").append(annotation.abbrev()).append("</code>]");
+ }
+ if (!typeDescription.isEmpty()) {
+ usage.append(" (").append(escaper.escape(typeDescription)).append("; ");
+ if (annotation.allowMultiple()) {
+ // Allow-multiple options can't have a default value.
+ usage.append("may be used multiple times");
+ } else {
+ // Don't call the annotation directly (we must allow overrides to certain defaults).
+ String defaultValueString = OptionsParserImpl.getDefaultOptionString(optionField);
+ if (OptionsParserImpl.isSpecialNullDefault(defaultValueString, optionField)) {
+ usage.append("default: see description");
+ } else {
+ usage.append("default: \"").append(escaper.escape(defaultValueString)).append("\"");
+ }
+ }
+ usage.append(")");
+ }
+ usage.append("</dt>\n");
+ usage.append("<dd>\n");
+ if (!annotation.help().isEmpty()) {
+ usage.append(paragraphFill(escaper.escape(annotation.help()), 0, 80)); // (indent, width)
+ usage.append('\n');
+ }
+ if (annotation.expansion().length > 0) {
+ usage.append("<br/>\n");
+ StringBuilder expandsMsg = new StringBuilder("Expands to:");
+ for (String exp : annotation.expansion()) {
+ expandsMsg.append(" ").append(exp);
+ }
+ usage.append(paragraphFill(escaper.escape(expandsMsg.toString()), 0, 80)); // (indent, width)
+ usage.append('\n');
+ }
+ usage.append("</dd>\n");
+ }
+
+ /**
* Returns the available completion for the given option field. The completions are the exact
* command line option (with the prepending '--') that one should pass. It is suitable for
* completion script to use. If the option expect an argument, the kind of argument is given