Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | package com.google.devtools.common.options; |
| 15 | |
Damien Martin-Guillerez | 29728d4 | 2015-04-09 20:48:04 +0000 | [diff] [blame] | 16 | import com.google.common.base.Joiner; |
ccalvarin | c69dbf8 | 2017-08-16 03:03:49 +0200 | [diff] [blame] | 17 | import com.google.common.base.Preconditions; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 18 | import com.google.common.base.Splitter; |
| 19 | import com.google.common.base.Strings; |
Googler | 69faad0 | 2017-06-07 12:44:24 -0400 | [diff] [blame] | 20 | import com.google.common.collect.ImmutableList; |
Jingwen Chen | bcad221 | 2018-12-18 14:56:55 -0800 | [diff] [blame] | 21 | import com.google.common.collect.Iterables; |
Ulf Adams | 352211d | 2016-06-22 09:24:28 +0000 | [diff] [blame] | 22 | import com.google.common.escape.Escaper; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 23 | import java.text.BreakIterator; |
brandjon | d1b34d4 | 2017-04-18 15:52:57 +0200 | [diff] [blame] | 24 | import java.util.ArrayList; |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 25 | import java.util.Arrays; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 26 | import java.util.List; |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 27 | import java.util.Locale; |
| 28 | import java.util.stream.Collectors; |
| 29 | import java.util.stream.Stream; |
Jon Brandvein | 22d261c | 2017-03-21 23:15:28 +0000 | [diff] [blame] | 30 | import javax.annotation.Nullable; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 31 | |
ccalvarin | c69dbf8 | 2017-08-16 03:03:49 +0200 | [diff] [blame] | 32 | /** A renderer for usage messages for any combination of options classes. */ |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 33 | class OptionsUsage { |
| 34 | |
| 35 | private static final Splitter NEWLINE_SPLITTER = Splitter.on('\n'); |
Damien Martin-Guillerez | 29728d4 | 2015-04-09 20:48:04 +0000 | [diff] [blame] | 36 | private static final Joiner COMMA_JOINER = Joiner.on(","); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 37 | |
| 38 | /** |
Jon Brandvein | 22d261c | 2017-03-21 23:15:28 +0000 | [diff] [blame] | 39 | * Given an options class, render the usage string into the usage, which is passed in as an |
| 40 | * argument. This will not include information about expansions for options using expansion |
| 41 | * functions (it would be unsafe to report this as we cannot know what options from other {@link |
| 42 | * OptionsBase} subclasses they depend on until a complete parser is constructed). |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 43 | */ |
| 44 | static void getUsage(Class<? extends OptionsBase> optionsClass, StringBuilder usage) { |
brandjon | d1b34d4 | 2017-04-18 15:52:57 +0200 | [diff] [blame] | 45 | OptionsData data = OptionsParser.getOptionsDataInternal(optionsClass); |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 46 | List<OptionDefinition> optionDefinitions = |
ccalvarin | 987f09f | 2017-08-31 19:50:39 +0200 | [diff] [blame] | 47 | new ArrayList<>(OptionsData.getAllOptionDefinitionsForClass(optionsClass)); |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 48 | optionDefinitions.sort(OptionDefinition.BY_OPTION_NAME); |
| 49 | for (OptionDefinition optionDefinition : optionDefinitions) { |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 50 | getUsage(optionDefinition, usage, OptionsParser.HelpVerbosity.LONG, data, false); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 51 | } |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * Paragraph-fill the specified input text, indenting lines to 'indent' and |
| 56 | * wrapping lines at 'width'. Returns the formatted result. |
| 57 | */ |
| 58 | static String paragraphFill(String in, int indent, int width) { |
| 59 | String indentString = Strings.repeat(" ", indent); |
| 60 | StringBuilder out = new StringBuilder(); |
| 61 | String sep = ""; |
| 62 | for (String paragraph : NEWLINE_SPLITTER.split(in)) { |
ccalvarin | c65147b | 2017-08-16 21:31:52 +0200 | [diff] [blame] | 63 | // TODO(ccalvarin) break iterators expect hyphenated words to be line-breakable, which looks |
| 64 | // funny for --flag |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 65 | BreakIterator boundary = BreakIterator.getLineInstance(); // (factory) |
| 66 | boundary.setText(paragraph); |
| 67 | out.append(sep).append(indentString); |
| 68 | int cursor = indent; |
| 69 | for (int start = boundary.first(), end = boundary.next(); |
| 70 | end != BreakIterator.DONE; |
| 71 | start = end, end = boundary.next()) { |
| 72 | String word = |
| 73 | paragraph.substring(start, end); // (may include trailing space) |
| 74 | if (word.length() + cursor > width) { |
| 75 | out.append('\n').append(indentString); |
| 76 | cursor = indent; |
| 77 | } |
| 78 | out.append(word); |
| 79 | cursor += word.length(); |
| 80 | } |
| 81 | sep = "\n"; |
| 82 | } |
| 83 | return out.toString(); |
| 84 | } |
| 85 | |
| 86 | /** |
ccalvarin | c69dbf8 | 2017-08-16 03:03:49 +0200 | [diff] [blame] | 87 | * Returns the expansion for an option, if any, regardless of if the expansion is from a function |
| 88 | * or is statically declared in the annotation. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 89 | */ |
Googler | 69faad0 | 2017-06-07 12:44:24 -0400 | [diff] [blame] | 90 | private static @Nullable ImmutableList<String> getExpansionIfKnown( |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 91 | OptionDefinition optionDefinition, OptionsData optionsData) { |
| 92 | Preconditions.checkNotNull(optionDefinition); |
ccalvarin | 34a9fea | 2017-10-17 23:27:19 +0200 | [diff] [blame] | 93 | return optionsData.getEvaluatedExpansion(optionDefinition); |
Jon Brandvein | 22d261c | 2017-03-21 23:15:28 +0000 | [diff] [blame] | 94 | } |
| 95 | |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 96 | // Placeholder tag "UNKNOWN" is ignored. |
| 97 | private static boolean shouldEffectTagBeListed(OptionEffectTag effectTag) { |
| 98 | return !effectTag.equals(OptionEffectTag.UNKNOWN); |
| 99 | } |
| 100 | |
| 101 | // Tags that only apply to undocumented options are excluded. |
| 102 | private static boolean shouldMetadataTagBeListed(OptionMetadataTag metadataTag) { |
| 103 | return !metadataTag.equals(OptionMetadataTag.HIDDEN) |
| 104 | && !metadataTag.equals(OptionMetadataTag.INTERNAL); |
| 105 | } |
| 106 | |
ccalvarin | c69dbf8 | 2017-08-16 03:03:49 +0200 | [diff] [blame] | 107 | /** Appends the usage message for a single option-field message to 'usage'. */ |
Jon Brandvein | 22d261c | 2017-03-21 23:15:28 +0000 | [diff] [blame] | 108 | static void getUsage( |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 109 | OptionDefinition optionDefinition, |
Jon Brandvein | 22d261c | 2017-03-21 23:15:28 +0000 | [diff] [blame] | 110 | StringBuilder usage, |
| 111 | OptionsParser.HelpVerbosity helpVerbosity, |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 112 | OptionsData optionsData, |
| 113 | boolean includeTags) { |
ccalvarin | 0044349 | 2017-08-30 00:23:40 +0200 | [diff] [blame] | 114 | String flagName = getFlagName(optionDefinition); |
| 115 | String typeDescription = getTypeDescription(optionDefinition); |
Jonathan Bluett-Duncan | 0df3ddbd | 2017-08-09 11:13:54 +0200 | [diff] [blame] | 116 | usage.append(" --").append(flagName); |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 117 | if (helpVerbosity == OptionsParser.HelpVerbosity.SHORT) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 118 | usage.append('\n'); |
| 119 | return; |
| 120 | } |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 121 | |
| 122 | // Add the option's type and default information. Stop there for "medium" verbosity. |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 123 | if (optionDefinition.getAbbreviation() != '\0') { |
| 124 | usage.append(" [-").append(optionDefinition.getAbbreviation()).append(']'); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 125 | } |
| 126 | if (!typeDescription.equals("")) { |
Jonathan Bluett-Duncan | 0df3ddbd | 2017-08-09 11:13:54 +0200 | [diff] [blame] | 127 | usage.append(" (").append(typeDescription).append("; "); |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 128 | if (optionDefinition.allowsMultiple()) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 129 | usage.append("may be used multiple times"); |
| 130 | } else { |
| 131 | // Don't call the annotation directly (we must allow overrides to certain defaults) |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 132 | String defaultValueString = optionDefinition.getUnparsedDefaultValue(); |
| 133 | if (optionDefinition.isSpecialNullDefault()) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 134 | usage.append("default: see description"); |
| 135 | } else { |
Jonathan Bluett-Duncan | 0df3ddbd | 2017-08-09 11:13:54 +0200 | [diff] [blame] | 136 | usage.append("default: \"").append(defaultValueString).append("\""); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 137 | } |
| 138 | } |
| 139 | usage.append(")"); |
| 140 | } |
| 141 | usage.append("\n"); |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 142 | if (helpVerbosity == OptionsParser.HelpVerbosity.MEDIUM) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 143 | return; |
| 144 | } |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 145 | |
| 146 | // For verbosity "long," add the full description and expansion, along with the tag |
| 147 | // information if requested. |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 148 | if (!optionDefinition.getHelpText().isEmpty()) { |
| 149 | usage.append(paragraphFill(optionDefinition.getHelpText(), /*indent=*/ 4, /*width=*/ 80)); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 150 | usage.append('\n'); |
| 151 | } |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 152 | ImmutableList<String> expansion = getExpansionIfKnown(optionDefinition, optionsData); |
Jon Brandvein | 22d261c | 2017-03-21 23:15:28 +0000 | [diff] [blame] | 153 | if (expansion == null) { |
ccalvarin | c65147b | 2017-08-16 21:31:52 +0200 | [diff] [blame] | 154 | usage.append(paragraphFill("Expands to unknown options.", /*indent=*/ 6, /*width=*/ 80)); |
| 155 | usage.append('\n'); |
Googler | 69faad0 | 2017-06-07 12:44:24 -0400 | [diff] [blame] | 156 | } else if (!expansion.isEmpty()) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 157 | StringBuilder expandsMsg = new StringBuilder("Expands to: "); |
Jon Brandvein | 22d261c | 2017-03-21 23:15:28 +0000 | [diff] [blame] | 158 | for (String exp : expansion) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 159 | expandsMsg.append(exp).append(" "); |
| 160 | } |
ccalvarin | c65147b | 2017-08-16 21:31:52 +0200 | [diff] [blame] | 161 | usage.append(paragraphFill(expandsMsg.toString(), /*indent=*/ 6, /*width=*/ 80)); |
| 162 | usage.append('\n'); |
| 163 | } |
ccalvarin | 4acb36c | 2017-09-21 00:35:35 +0200 | [diff] [blame] | 164 | if (optionDefinition.hasImplicitRequirements()) { |
ccalvarin | c65147b | 2017-08-16 21:31:52 +0200 | [diff] [blame] | 165 | StringBuilder requiredMsg = new StringBuilder("Using this option will also add: "); |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 166 | for (String req : optionDefinition.getImplicitRequirements()) { |
ccalvarin | c65147b | 2017-08-16 21:31:52 +0200 | [diff] [blame] | 167 | requiredMsg.append(req).append(" "); |
| 168 | } |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 169 | usage.append(paragraphFill(requiredMsg.toString(), 6, 80)); // (indent, width) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 170 | usage.append('\n'); |
| 171 | } |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 172 | if (!includeTags) { |
| 173 | return; |
| 174 | } |
| 175 | |
| 176 | // If we are expected to include the tags, add them for high verbosity. |
| 177 | Stream<OptionEffectTag> effectTagStream = |
| 178 | Arrays.stream(optionDefinition.getOptionEffectTags()) |
| 179 | .filter(OptionsUsage::shouldEffectTagBeListed); |
| 180 | Stream<OptionMetadataTag> metadataTagStream = |
| 181 | Arrays.stream(optionDefinition.getOptionMetadataTags()) |
| 182 | .filter(OptionsUsage::shouldMetadataTagBeListed); |
| 183 | String tagList = |
| 184 | Stream.concat(effectTagStream, metadataTagStream) |
| 185 | .map(tag -> tag.toString().toLowerCase()) |
| 186 | .collect(Collectors.joining(", ")); |
| 187 | if (!tagList.isEmpty()) { |
| 188 | usage.append(paragraphFill("Tags: " + tagList, 6, 80)); // (indent, width) |
| 189 | usage.append("\n"); |
| 190 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 191 | } |
| 192 | |
ccalvarin | c69dbf8 | 2017-08-16 03:03:49 +0200 | [diff] [blame] | 193 | /** Append the usage message for a single option-field message to 'usage'. */ |
Jon Brandvein | 22d261c | 2017-03-21 23:15:28 +0000 | [diff] [blame] | 194 | static void getUsageHtml( |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 195 | OptionDefinition optionDefinition, |
| 196 | StringBuilder usage, |
| 197 | Escaper escaper, |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 198 | OptionsData optionsData, |
| 199 | boolean includeTags) { |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 200 | String plainFlagName = optionDefinition.getOptionName(); |
ccalvarin | 0044349 | 2017-08-30 00:23:40 +0200 | [diff] [blame] | 201 | String flagName = getFlagName(optionDefinition); |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 202 | String valueDescription = optionDefinition.getValueTypeHelpText(); |
ccalvarin | 0044349 | 2017-08-30 00:23:40 +0200 | [diff] [blame] | 203 | String typeDescription = getTypeDescription(optionDefinition); |
Jingwen Chen | bcad221 | 2018-12-18 14:56:55 -0800 | [diff] [blame] | 204 | |
| 205 | // String.format is a lot slower, sometimes up to 10x. |
| 206 | // https://stackoverflow.com/questions/925423/is-it-better-practice-to-use-string-format-over-string-concatenation-in-java |
| 207 | // |
| 208 | // Considering that this runs for every flag in the CLI reference, it's better to use regular |
| 209 | // appends here. |
| 210 | usage |
| 211 | // Add the id of the flag to point anchor hrefs to it |
| 212 | .append("<dt id=\"flag--") |
| 213 | .append(plainFlagName) |
| 214 | .append("\">") |
| 215 | // Add the href to the id hash |
| 216 | .append("<code><a href=\"#flag--") |
| 217 | .append(plainFlagName) |
| 218 | .append("\">") |
| 219 | .append("--") |
| 220 | .append(flagName) |
| 221 | .append("</a>"); |
| 222 | |
ccalvarin | 5fe8e66 | 2017-09-14 15:56:43 +0200 | [diff] [blame] | 223 | if (optionDefinition.usesBooleanValueSyntax() || optionDefinition.isVoidField()) { |
Ulf Adams | 6f09666 | 2016-06-27 15:51:23 +0000 | [diff] [blame] | 224 | // Nothing for boolean, tristate, boolean_or_enum, or void options. |
| 225 | } else if (!valueDescription.isEmpty()) { |
| 226 | usage.append("=").append(escaper.escape(valueDescription)); |
| 227 | } else if (!typeDescription.isEmpty()) { |
| 228 | // Generic fallback, which isn't very good. |
| 229 | usage.append("=<").append(escaper.escape(typeDescription)).append(">"); |
| 230 | } |
| 231 | usage.append("</code>"); |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 232 | if (optionDefinition.getAbbreviation() != '\0') { |
| 233 | usage.append(" [<code>-").append(optionDefinition.getAbbreviation()).append("</code>]"); |
Ulf Adams | 352211d | 2016-06-22 09:24:28 +0000 | [diff] [blame] | 234 | } |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 235 | if (optionDefinition.allowsMultiple()) { |
Ulf Adams | 6f09666 | 2016-06-27 15:51:23 +0000 | [diff] [blame] | 236 | // Allow-multiple options can't have a default value. |
| 237 | usage.append(" multiple uses are accumulated"); |
| 238 | } else { |
| 239 | // Don't call the annotation directly (we must allow overrides to certain defaults). |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 240 | String defaultValueString = optionDefinition.getUnparsedDefaultValue(); |
| 241 | if (optionDefinition.isVoidField()) { |
Ulf Adams | 6f09666 | 2016-06-27 15:51:23 +0000 | [diff] [blame] | 242 | // Void options don't have a default. |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 243 | } else if (optionDefinition.isSpecialNullDefault()) { |
Ulf Adams | 6f09666 | 2016-06-27 15:51:23 +0000 | [diff] [blame] | 244 | usage.append(" default: see description"); |
Ulf Adams | 352211d | 2016-06-22 09:24:28 +0000 | [diff] [blame] | 245 | } else { |
Ulf Adams | 6f09666 | 2016-06-27 15:51:23 +0000 | [diff] [blame] | 246 | usage.append(" default: \"").append(escaper.escape(defaultValueString)).append("\""); |
Ulf Adams | 352211d | 2016-06-22 09:24:28 +0000 | [diff] [blame] | 247 | } |
Ulf Adams | 352211d | 2016-06-22 09:24:28 +0000 | [diff] [blame] | 248 | } |
| 249 | usage.append("</dt>\n"); |
| 250 | usage.append("<dd>\n"); |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 251 | if (!optionDefinition.getHelpText().isEmpty()) { |
| 252 | usage.append( |
| 253 | paragraphFill( |
| 254 | escaper.escape(optionDefinition.getHelpText()), /*indent=*/ 0, /*width=*/ 80)); |
Ulf Adams | 352211d | 2016-06-22 09:24:28 +0000 | [diff] [blame] | 255 | usage.append('\n'); |
| 256 | } |
ccalvarin | 62f72bc | 2017-08-17 17:59:05 +0200 | [diff] [blame] | 257 | |
ccalvarin | 34a9fea | 2017-10-17 23:27:19 +0200 | [diff] [blame] | 258 | if (!optionsData.getEvaluatedExpansion(optionDefinition).isEmpty()) { |
ccalvarin | 62f72bc | 2017-08-17 17:59:05 +0200 | [diff] [blame] | 259 | // If this is an expansion option, list the expansion if known, or at least specify that we |
| 260 | // don't know. |
Ulf Adams | 352211d | 2016-06-22 09:24:28 +0000 | [diff] [blame] | 261 | usage.append("<br/>\n"); |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 262 | ImmutableList<String> expansion = getExpansionIfKnown(optionDefinition, optionsData); |
ccalvarin | 62f72bc | 2017-08-17 17:59:05 +0200 | [diff] [blame] | 263 | StringBuilder expandsMsg; |
| 264 | if (expansion == null) { |
| 265 | expandsMsg = new StringBuilder("Expands to unknown options.<br/>\n"); |
| 266 | } else { |
| 267 | Preconditions.checkArgument(!expansion.isEmpty()); |
| 268 | expandsMsg = new StringBuilder("Expands to:<br/>\n"); |
| 269 | for (String exp : expansion) { |
Jingwen Chen | bcad221 | 2018-12-18 14:56:55 -0800 | [diff] [blame] | 270 | // TODO(jingwen): We link to the expanded flags here, but unfortunately we don't |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 271 | // currently guarantee that all flags are only printed once. A flag in an OptionBase that |
| 272 | // is included by 2 different commands, but not inherited through a parent command, will |
Jingwen Chen | bcad221 | 2018-12-18 14:56:55 -0800 | [diff] [blame] | 273 | // be printed multiple times. Clicking on the flag will bring the user to its first |
| 274 | // definition. |
ccalvarin | 62f72bc | 2017-08-17 17:59:05 +0200 | [diff] [blame] | 275 | expandsMsg |
Jingwen Chen | bcad221 | 2018-12-18 14:56:55 -0800 | [diff] [blame] | 276 | .append(" ") |
| 277 | .append("<code><a href=\"#flag") |
| 278 | // Link to the '#flag--flag_name' hash. |
| 279 | // Some expansions are in the form of '--flag_name=value', so we drop everything from |
| 280 | // '=' onwards. |
| 281 | .append(Iterables.get(Splitter.on('=').split(escaper.escape(exp)), 0)) |
| 282 | .append("\">") |
ccalvarin | 62f72bc | 2017-08-17 17:59:05 +0200 | [diff] [blame] | 283 | .append(escaper.escape(exp)) |
Jingwen Chen | bcad221 | 2018-12-18 14:56:55 -0800 | [diff] [blame] | 284 | .append("</a></code><br/>\n"); |
ccalvarin | 62f72bc | 2017-08-17 17:59:05 +0200 | [diff] [blame] | 285 | } |
Ulf Adams | 352211d | 2016-06-22 09:24:28 +0000 | [diff] [blame] | 286 | } |
ccalvarin | c65147b | 2017-08-16 21:31:52 +0200 | [diff] [blame] | 287 | usage.append(expandsMsg.toString()); |
Ulf Adams | 352211d | 2016-06-22 09:24:28 +0000 | [diff] [blame] | 288 | } |
ccalvarin | 62f72bc | 2017-08-17 17:59:05 +0200 | [diff] [blame] | 289 | |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 290 | // Add effect tags, if not UNKNOWN, and metadata tags, if not empty. |
| 291 | if (includeTags) { |
| 292 | Stream<OptionEffectTag> effectTagStream = |
| 293 | Arrays.stream(optionDefinition.getOptionEffectTags()) |
| 294 | .filter(OptionsUsage::shouldEffectTagBeListed); |
| 295 | Stream<OptionMetadataTag> metadataTagStream = |
| 296 | Arrays.stream(optionDefinition.getOptionMetadataTags()) |
| 297 | .filter(OptionsUsage::shouldMetadataTagBeListed); |
| 298 | String tagList = |
| 299 | Stream.concat( |
| 300 | effectTagStream.map( |
| 301 | tag -> |
| 302 | String.format( |
| 303 | "<a href=\"#effect_tag_%s\"><code>%s</code></a>", |
| 304 | tag, tag.name().toLowerCase())), |
| 305 | metadataTagStream.map( |
| 306 | tag -> |
| 307 | String.format( |
| 308 | "<a href=\"#metadata_tag_%s\"><code>%s</code></a>", |
| 309 | tag, tag.name().toLowerCase()))) |
| 310 | .collect(Collectors.joining(", ")); |
| 311 | if (!tagList.isEmpty()) { |
| 312 | usage.append("<br>Tags: \n").append(tagList); |
| 313 | } |
| 314 | } |
| 315 | |
Ulf Adams | 352211d | 2016-06-22 09:24:28 +0000 | [diff] [blame] | 316 | usage.append("</dd>\n"); |
| 317 | } |
| 318 | |
| 319 | /** |
Damien Martin-Guillerez | 29728d4 | 2015-04-09 20:48:04 +0000 | [diff] [blame] | 320 | * Returns the available completion for the given option field. The completions are the exact |
| 321 | * command line option (with the prepending '--') that one should pass. It is suitable for |
| 322 | * completion script to use. If the option expect an argument, the kind of argument is given |
| 323 | * after the equals. If the kind is a enum, the various enum values are given inside an accolade |
| 324 | * in a comma separated list. For other special kind, the type is given as a name (e.g., |
| 325 | * <code>label</code>, <code>float</ode>, <code>path</code>...). Example outputs of this |
| 326 | * function are for, respectively, a tristate flag <code>tristate_flag</code>, a enum |
| 327 | * flag <code>enum_flag</code> which can take <code>value1</code>, <code>value2</code> and |
| 328 | * <code>value3</code>, a path fragment flag <code>path_flag</code>, a string flag |
| 329 | * <code>string_flag</code> and a void flag <code>void_flag</code>: |
| 330 | * <pre> |
| 331 | * --tristate_flag={auto,yes,no} |
| 332 | * --notristate_flag |
| 333 | * --enum_flag={value1,value2,value3} |
| 334 | * --path_flag=path |
| 335 | * --string_flag= |
| 336 | * --void_flag |
| 337 | * </pre> |
| 338 | * |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 339 | * @param optionDefinition The field to return completion for |
Damien Martin-Guillerez | 29728d4 | 2015-04-09 20:48:04 +0000 | [diff] [blame] | 340 | * @param builder the string builder to store the completion values |
| 341 | */ |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 342 | static void getCompletion(OptionDefinition optionDefinition, StringBuilder builder) { |
Damien Martin-Guillerez | 29728d4 | 2015-04-09 20:48:04 +0000 | [diff] [blame] | 343 | // Return the list of possible completions for this option |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 344 | String flagName = optionDefinition.getOptionName(); |
| 345 | Class<?> fieldType = optionDefinition.getType(); |
Damien Martin-Guillerez | 29728d4 | 2015-04-09 20:48:04 +0000 | [diff] [blame] | 346 | builder.append("--").append(flagName); |
| 347 | if (fieldType.equals(boolean.class)) { |
| 348 | builder.append("\n"); |
| 349 | builder.append("--no").append(flagName).append("\n"); |
| 350 | } else if (fieldType.equals(TriState.class)) { |
| 351 | builder.append("={auto,yes,no}\n"); |
| 352 | builder.append("--no").append(flagName).append("\n"); |
| 353 | } else if (fieldType.isEnum()) { |
ccalvarin | 77e3a5c | 2017-09-26 18:11:53 -0400 | [diff] [blame] | 354 | builder |
| 355 | .append("={") |
| 356 | .append(COMMA_JOINER.join(fieldType.getEnumConstants()).toLowerCase(Locale.ENGLISH)) |
| 357 | .append("}\n"); |
Damien Martin-Guillerez | 29728d4 | 2015-04-09 20:48:04 +0000 | [diff] [blame] | 358 | } else if (fieldType.getSimpleName().equals("Label")) { |
| 359 | // String comparison so we don't introduce a dependency to com.google.devtools.build.lib. |
| 360 | builder.append("=label\n"); |
| 361 | } else if (fieldType.getSimpleName().equals("PathFragment")) { |
| 362 | builder.append("=path\n"); |
| 363 | } else if (Void.class.isAssignableFrom(fieldType)) { |
| 364 | builder.append("\n"); |
| 365 | } else { |
| 366 | // TODO(bazel-team): add more types. Maybe even move the completion type |
| 367 | // to the @Option annotation? |
| 368 | builder.append("=\n"); |
| 369 | } |
| 370 | } |
| 371 | |
ccalvarin | 0044349 | 2017-08-30 00:23:40 +0200 | [diff] [blame] | 372 | private static String getTypeDescription(OptionDefinition optionsDefinition) { |
| 373 | return optionsDefinition.getConverter().getTypeDescription(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 374 | } |
| 375 | |
ccalvarin | 0044349 | 2017-08-30 00:23:40 +0200 | [diff] [blame] | 376 | static String getFlagName(OptionDefinition optionDefinition) { |
ccalvarin | e8aae03 | 2017-08-22 07:17:44 +0200 | [diff] [blame] | 377 | String name = optionDefinition.getOptionName(); |
ccalvarin | 5fe8e66 | 2017-09-14 15:56:43 +0200 | [diff] [blame] | 378 | return optionDefinition.usesBooleanValueSyntax() ? "[no]" + name : name; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 379 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 380 | } |