All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.devtools.common.options.OptionsUsage Maven / Gradle / Ivy

The newest version!
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.common.options;

import static com.google.devtools.common.options.OptionsParserImpl.findConverter;

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;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * A renderer for usage messages. For now this is very simple.
 */
class OptionsUsage {

  private static final Splitter NEWLINE_SPLITTER = Splitter.on('\n');
  private static final Joiner COMMA_JOINER = Joiner.on(",");

  /**
   * Given an options class, render the usage string into the usage,
   * which is passed in as an argument.
   */
  static void getUsage(Class optionsClass, StringBuilder usage) {
    List optionFields =
        Lists.newArrayList(OptionsParser.getAllAnnotatedFields(optionsClass));
    Collections.sort(optionFields, BY_NAME);
    for (Field optionField : optionFields) {
      getUsage(optionField, usage, OptionsParser.HelpVerbosity.LONG);
    }
  }

  /**
   * Paragraph-fill the specified input text, indenting lines to 'indent' and
   * wrapping lines at 'width'.  Returns the formatted result.
   */
  static String paragraphFill(String in, int indent, int width) {
    String indentString = Strings.repeat(" ", indent);
    StringBuilder out = new StringBuilder();
    String sep = "";
    for (String paragraph : NEWLINE_SPLITTER.split(in)) {
      BreakIterator boundary = BreakIterator.getLineInstance(); // (factory)
      boundary.setText(paragraph);
      out.append(sep).append(indentString);
      int cursor = indent;
      for (int start = boundary.first(), end = boundary.next();
           end != BreakIterator.DONE;
           start = end, end = boundary.next()) {
        String word =
            paragraph.substring(start, end); // (may include trailing space)
        if (word.length() + cursor > width) {
          out.append('\n').append(indentString);
          cursor = indent;
        }
        out.append(word);
        cursor += word.length();
      }
      sep = "\n";
    }
    return out.toString();
  }

  /**
   * Append the usage message for a single option-field message to 'usage'.
   */
  static void getUsage(Field optionField, StringBuilder usage,
                       OptionsParser.HelpVerbosity helpVerbosity) {
    String flagName = getFlagName(optionField);
    String typeDescription = getTypeDescription(optionField);
    Option annotation = optionField.getAnnotation(Option.class);
    usage.append("  --" + flagName);
    if (helpVerbosity == OptionsParser.HelpVerbosity.SHORT) { // just the name
      usage.append('\n');
      return;
    }
    if (annotation.abbrev() != '\0') {
      usage.append(" [-").append(annotation.abbrev()).append(']');
    }
    if (!typeDescription.equals("")) {
      usage.append(" (" + typeDescription + "; ");
      if (annotation.allowMultiple()) {
        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: \"" + defaultValueString + "\"");
        }
      }
      usage.append(")");
    }
    usage.append("\n");
    if (helpVerbosity == OptionsParser.HelpVerbosity.MEDIUM) { // just the name and type.
      return;
    }
    if (!annotation.help().equals("")) {
      usage.append(paragraphFill(annotation.help(), 4, 80)); // (indent, width)
      usage.append('\n');
    }
    if (annotation.expansion().length > 0) {
      StringBuilder expandsMsg = new StringBuilder("Expands to: ");
      for (String exp : annotation.expansion()) {
        expandsMsg.append(exp).append(" ");
      }
      usage.append(paragraphFill(expandsMsg.toString(), 4, 80)); // (indent, width)
      usage.append('\n');
    }
  }

  /**
   * Append the usage message for a single option-field message to 'usage'.
   */
  static void getUsageHtml(Field optionField, StringBuilder usage, Escaper escaper) {
    String plainFlagName = optionField.getAnnotation(Option.class).name();
    String flagName = getFlagName(optionField);
    String valueDescription = optionField.getAnnotation(Option.class).valueHelp();
    String typeDescription = getTypeDescription(optionField);
    Option annotation = optionField.getAnnotation(Option.class);
    usage.append("
--"); usage.append(flagName); if (OptionsParserImpl.isBooleanField(optionField) || OptionsParserImpl.isVoidField(optionField)) { // Nothing for boolean, tristate, boolean_or_enum, or void options. } else if (!valueDescription.isEmpty()) { usage.append("=").append(escaper.escape(valueDescription)); } else if (!typeDescription.isEmpty()) { // Generic fallback, which isn't very good. usage.append("=<").append(escaper.escape(typeDescription)).append(">"); } usage.append(""); if (annotation.abbrev() != '\0') { usage.append(" [-").append(annotation.abbrev()).append("]"); } if (annotation.allowMultiple()) { // Allow-multiple options can't have a default value. usage.append(" multiple uses are accumulated"); } else { // Don't call the annotation directly (we must allow overrides to certain defaults). String defaultValueString = OptionsParserImpl.getDefaultOptionString(optionField); if (OptionsParserImpl.isVoidField(optionField)) { // Void options don't have a default. } else if (OptionsParserImpl.isSpecialNullDefault(defaultValueString, optionField)) { usage.append(" default: see description"); } else { usage.append(" default: \"").append(escaper.escape(defaultValueString)).append("\""); } } usage.append("
\n"); usage.append("
\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("
\n"); StringBuilder expandsMsg = new StringBuilder("Expands to:
\n"); for (String exp : annotation.expansion()) { // TODO(ulfjack): Can we link to the expanded flags here? expandsMsg .append("  ") .append(escaper.escape(exp)) .append("
\n"); } usage.append(expandsMsg.toString()); // (indent, width) usage.append('\n'); } usage.append("
\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 * after the equals. If the kind is a enum, the various enum values are given inside an accolade * in a comma separated list. For other special kind, the type is given as a name (e.g., * label, float, path...). Example outputs of this * function are for, respectively, a tristate flag tristate_flag, a enum * flag enum_flag which can take value1, value2 and * value3, a path fragment flag path_flag, a string flag * string_flag and a void flag void_flag: *
   *   --tristate_flag={auto,yes,no}
   *   --notristate_flag
   *   --enum_flag={value1,value2,value3}
   *   --path_flag=path
   *   --string_flag=
   *   --void_flag
   * 
* * @param field The field to return completion for * @param builder the string builder to store the completion values */ static void getCompletion(Field field, StringBuilder builder) { // Return the list of possible completions for this option String flagName = field.getAnnotation(Option.class).name(); Class fieldType = field.getType(); builder.append("--").append(flagName); if (fieldType.equals(boolean.class)) { builder.append("\n"); builder.append("--no").append(flagName).append("\n"); } else if (fieldType.equals(TriState.class)) { builder.append("={auto,yes,no}\n"); builder.append("--no").append(flagName).append("\n"); } else if (fieldType.isEnum()) { builder.append("={") .append(COMMA_JOINER.join(fieldType.getEnumConstants()).toLowerCase()).append("}\n"); } else if (fieldType.getSimpleName().equals("Label")) { // String comparison so we don't introduce a dependency to com.google.devtools.build.lib. builder.append("=label\n"); } else if (fieldType.getSimpleName().equals("PathFragment")) { builder.append("=path\n"); } else if (Void.class.isAssignableFrom(fieldType)) { builder.append("\n"); } else { // TODO(bazel-team): add more types. Maybe even move the completion type // to the @Option annotation? builder.append("=\n"); } } private static final Comparator BY_NAME = new Comparator() { @Override public int compare(Field left, Field right) { return left.getName().compareTo(right.getName()); } }; /** * An ordering relation for option-field fields that first groups together * options of the same category, then sorts by name within the category. */ static final Comparator BY_CATEGORY = new Comparator() { @Override public int compare(Field left, Field right) { int r = left.getAnnotation(Option.class).category().compareTo( right.getAnnotation(Option.class).category()); return r == 0 ? BY_NAME.compare(left, right) : r; } }; private static String getTypeDescription(Field optionsField) { return findConverter(optionsField).getTypeDescription(); } static String getFlagName(Field field) { String name = field.getAnnotation(Option.class).name(); return OptionsParserImpl.isBooleanField(field) ? "[no]" + name : name; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy