// 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 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'); } } 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; } }