/* * Copyright 2016, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jf.util.jcommander; import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterDescription; import com.beust.jcommander.Parameters; import com.beust.jcommander.internal.Lists; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import org.jf.util.WrappedIndentingWriter; import javax.annotation.Nonnull; import java.io.IOException; import java.io.StringWriter; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; public class HelpFormatter { private int width = 80; @Nonnull public HelpFormatter width(int width) { this.width = width; return this; } @Nonnull private static ExtendedParameters getExtendedParameters(JCommander jc) { ExtendedParameters anno = jc.getObjects().get(0).getClass().getAnnotation(ExtendedParameters.class); if (anno == null) { throw new IllegalStateException("All commands should have an ExtendedParameters annotation"); } return anno; } @Nonnull private static List<String> getCommandAliases(JCommander jc) { return Lists.newArrayList(getExtendedParameters(jc).commandAliases()); } private static boolean includeParametersInUsage(@Nonnull JCommander jc) { return getExtendedParameters(jc).includeParametersInUsage(); } @Nonnull private static String getPostfixDescription(@Nonnull JCommander jc) { return getExtendedParameters(jc).postfixDescription(); } private int getParameterArity(ParameterDescription param) { if (param.getParameter().arity() > 0) { return param.getParameter().arity(); } Class<?> type = param.getParameterized().getType(); if ((type == boolean.class || type == Boolean.class)) { return 0; } return 1; } private List<ParameterDescription> getSortedParameters(JCommander jc) { List<ParameterDescription> parameters = Lists.newArrayList(jc.getParameters()); final Pattern pattern = Pattern.compile("^-*(.*)$"); Collections.sort(parameters, new Comparator<ParameterDescription>() { @Override public int compare(ParameterDescription o1, ParameterDescription o2) { String s1; Matcher matcher = pattern.matcher(o1.getParameter().names()[0]); if (matcher.matches()) { s1 = matcher.group(1); } else { throw new IllegalStateException(); } String s2; matcher = pattern.matcher(o2.getParameter().names()[0]); if (matcher.matches()) { s2 = matcher.group(1); } else { throw new IllegalStateException(); } return s1.compareTo(s2); } }); return parameters; } @Nonnull public String format(@Nonnull JCommander... jc) { return format(Arrays.asList(jc)); } @Nonnull public String format(@Nonnull List<JCommander> commandHierarchy) { try { StringWriter stringWriter = new StringWriter(); WrappedIndentingWriter writer = new WrappedIndentingWriter(stringWriter, width - 5, width); JCommander leafJc = Iterables.getLast(commandHierarchy); writer.write("usage:"); writer.indent(2); for (JCommander jc: commandHierarchy) { writer.write(" "); writer.write(ExtendedCommands.commandName(jc)); } if (includeParametersInUsage(leafJc)) { for (ParameterDescription param : leafJc.getParameters()) { if (!param.getParameter().hidden()) { writer.write(" ["); writer.write(param.getParameter().getParameter().names()[0]); writer.write("]"); } } } else { if (!leafJc.getParameters().isEmpty()) { writer.write(" [<options>]"); } } if (!leafJc.getCommands().isEmpty()) { writer.write(" [<command [<args>]]"); } if (leafJc.getMainParameter() != null) { String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc.getMainParameter()); if (argumentNames.length == 0) { writer.write(" <args>"); } else { String argumentName = argumentNames[0]; boolean writeAngleBrackets = !argumentName.startsWith("<") && !argumentName.startsWith("["); writer.write(" "); if (writeAngleBrackets) { writer.write("<"); } writer.write(argumentNames[0]); if (writeAngleBrackets) { writer.write(">"); } } } writer.deindent(2); String commandDescription = ExtendedCommands.getCommandDescription(leafJc); if (commandDescription != null) { writer.write("\n"); writer.write(commandDescription); } if (!leafJc.getParameters().isEmpty() || leafJc.getMainParameter() != null) { writer.write("\n\nOptions:"); writer.indent(2); for (ParameterDescription param : getSortedParameters(leafJc)) { if (!param.getParameter().hidden()) { writer.write("\n"); writer.indent(4); if (!param.getNames().isEmpty()) { writer.write(Joiner.on(',').join(param.getParameter().names())); } if (getParameterArity(param) > 0) { String[] argumentNames = ExtendedCommands.parameterArgumentNames(param); for (int i = 0; i < getParameterArity(param); i++) { writer.write(" "); if (i < argumentNames.length) { writer.write("<"); writer.write(argumentNames[i]); writer.write(">"); } else { writer.write("<arg>"); } } } if (param.getDescription() != null && !param.getDescription().isEmpty()) { writer.write(" - "); writer.write(param.getDescription()); } if (param.getDefault() != null) { String defaultValue = null; if (param.getParameterized().getType() == Boolean.class || param.getParameterized().getType() == Boolean.TYPE) { if ((Boolean)param.getDefault()) { defaultValue = "True"; } } else if (List.class.isAssignableFrom(param.getParameterized().getType())) { if (!((List)param.getDefault()).isEmpty()) { defaultValue = param.getDefault().toString(); } } else { defaultValue = param.getDefault().toString(); } if (defaultValue != null) { writer.write(" (default: "); writer.write(defaultValue); writer.write(")"); } } writer.deindent(4); } } if (leafJc.getMainParameter() != null) { String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc.getMainParameter()); writer.write("\n"); writer.indent(4); if (argumentNames.length > 0) { writer.write("<"); writer.write(argumentNames[0]); writer.write(">"); } else { writer.write("<args>"); } if (leafJc.getMainParameterDescription() != null) { writer.write(" - "); writer.write(leafJc.getMainParameterDescription()); } writer.deindent(4); } writer.deindent(2); } if (!leafJc.getCommands().isEmpty()) { writer.write("\n\nCommands:"); writer.indent(2); List<Entry<String, JCommander>> entryList = Lists.newArrayList(leafJc.getCommands().entrySet()); Collections.sort(entryList, new Comparator<Entry<String, JCommander>>() { @Override public int compare(Entry<String, JCommander> o1, Entry<String, JCommander> o2) { return o1.getKey().compareTo(o2.getKey()); } }); for (Entry<String, JCommander> entry : entryList) { String commandName = entry.getKey(); JCommander command = entry.getValue(); Object arg = command.getObjects().get(0); Parameters parametersAnno = arg.getClass().getAnnotation(Parameters.class); if (!parametersAnno.hidden()) { writer.write("\n"); writer.indent(4); writer.write(commandName); List<String> aliases = getCommandAliases(command); if (!aliases.isEmpty()) { writer.write("("); writer.write(Joiner.on(',').join(aliases)); writer.write(")"); } String commandDesc = leafJc.getCommandDescription(commandName); if (commandDesc != null) { writer.write(" - "); writer.write(commandDesc); } writer.deindent(4); } } writer.deindent(2); } String postfixDescription = getPostfixDescription(leafJc); if (!postfixDescription.isEmpty()) { writer.write("\n\n"); writer.write(postfixDescription); } writer.flush(); return stringWriter.getBuffer().toString(); } catch (IOException ex) { throw new RuntimeException(ex); } } }