com.google.devtools.common.options.OptionsUsage.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.common.options.OptionsUsage.java

Source

// 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<? extends OptionsBase> optionsClass, StringBuilder usage) {
        List<Field> 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("<dt><code><a name=\"flag--").append(plainFlagName).append("\"></a>--");
        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("=&lt;").append(escaper.escape(typeDescription)).append("&gt");
        }
        usage.append("</code>");
        if (annotation.abbrev() != '\0') {
            usage.append(" [<code>-").append(annotation.abbrev()).append("</code>]");
        }
        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("</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:<br/>\n");
            for (String exp : annotation.expansion()) {
                // TODO(ulfjack): Can we link to the expanded flags here?
                expandsMsg.append("&nbsp;&nbsp;<code>").append(escaper.escape(exp)).append("</code><br/>\n");
            }
            usage.append(expandsMsg.toString()); // (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
     * 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.,
     * <code>label</code>, <code>float</ode>, <code>path</code>...). Example outputs of this
     * function are for, respectively, a tristate flag <code>tristate_flag</code>, a enum
     * flag <code>enum_flag</code> which can take <code>value1</code>, <code>value2</code> and
     * <code>value3</code>, a path fragment flag <code>path_flag</code>, a string flag
     * <code>string_flag</code> and a void flag <code>void_flag</code>:
     * <pre>
     *   --tristate_flag={auto,yes,no}
     *   --notristate_flag
     *   --enum_flag={value1,value2,value3}
     *   --path_flag=path
     *   --string_flag=
     *   --void_flag
     * </pre>
     *
     * @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<Field> BY_NAME = new Comparator<Field>() {
        @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<Field> BY_CATEGORY = new Comparator<Field>() {
        @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;
    }

}