blob: c48a53295c0cca4100c61a1ec517e8eca43d4954 [file] [log] [blame]
// Copyright 2014 Google Inc. 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.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
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');
/**
* 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');
}
}
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;
}
}