1 /* 2 * Copyright 2016, Google Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package org.jf.util.jcommander; 33 34 import com.beust.jcommander.JCommander; 35 import com.beust.jcommander.ParameterDescription; 36 import com.beust.jcommander.Parameters; 37 import com.beust.jcommander.internal.Lists; 38 import com.google.common.base.Joiner; 39 import com.google.common.collect.Iterables; 40 import org.jf.util.WrappedIndentingWriter; 41 42 import javax.annotation.Nonnull; 43 import java.io.IOException; 44 import java.io.StringWriter; 45 import java.util.Arrays; 46 import java.util.Collections; 47 import java.util.Comparator; 48 import java.util.List; 49 import java.util.Map.Entry; 50 import java.util.regex.Matcher; 51 import java.util.regex.Pattern; 52 53 public class HelpFormatter { 54 55 private int width = 80; 56 57 @Nonnull 58 public HelpFormatter width(int width) { 59 this.width = width; 60 return this; 61 } 62 63 @Nonnull 64 private static ExtendedParameters getExtendedParameters(JCommander jc) { 65 ExtendedParameters anno = jc.getObjects().get(0).getClass().getAnnotation(ExtendedParameters.class); 66 if (anno == null) { 67 throw new IllegalStateException("All commands should have an ExtendedParameters annotation"); 68 } 69 return anno; 70 } 71 72 @Nonnull 73 private static List<String> getCommandAliases(JCommander jc) { 74 return Lists.newArrayList(getExtendedParameters(jc).commandAliases()); 75 } 76 77 private static boolean includeParametersInUsage(@Nonnull JCommander jc) { 78 return getExtendedParameters(jc).includeParametersInUsage(); 79 } 80 81 @Nonnull 82 private static String getPostfixDescription(@Nonnull JCommander jc) { 83 return getExtendedParameters(jc).postfixDescription(); 84 } 85 86 private int getParameterArity(ParameterDescription param) { 87 if (param.getParameter().arity() > 0) { 88 return param.getParameter().arity(); 89 } 90 Class<?> type = param.getParameterized().getType(); 91 if ((type == boolean.class || type == Boolean.class)) { 92 return 0; 93 } 94 return 1; 95 } 96 97 private List<ParameterDescription> getSortedParameters(JCommander jc) { 98 List<ParameterDescription> parameters = Lists.newArrayList(jc.getParameters()); 99 100 final Pattern pattern = Pattern.compile("^-*(.*)$"); 101 102 Collections.sort(parameters, new Comparator<ParameterDescription>() { 103 @Override public int compare(ParameterDescription o1, ParameterDescription o2) { 104 String s1; 105 Matcher matcher = pattern.matcher(o1.getParameter().names()[0]); 106 if (matcher.matches()) { 107 s1 = matcher.group(1); 108 } else { 109 throw new IllegalStateException(); 110 } 111 112 String s2; 113 matcher = pattern.matcher(o2.getParameter().names()[0]); 114 if (matcher.matches()) { 115 s2 = matcher.group(1); 116 } else { 117 throw new IllegalStateException(); 118 } 119 120 return s1.compareTo(s2); 121 } 122 }); 123 return parameters; 124 } 125 126 @Nonnull 127 public String format(@Nonnull JCommander... jc) { 128 return format(Arrays.asList(jc)); 129 } 130 131 @Nonnull 132 public String format(@Nonnull List<JCommander> commandHierarchy) { 133 try { 134 StringWriter stringWriter = new StringWriter(); 135 WrappedIndentingWriter writer = new WrappedIndentingWriter(stringWriter, width - 5, width); 136 137 JCommander leafJc = Iterables.getLast(commandHierarchy); 138 139 writer.write("usage:"); 140 writer.indent(2); 141 142 for (JCommander jc: commandHierarchy) { 143 writer.write(" "); 144 writer.write(ExtendedCommands.commandName(jc)); 145 } 146 147 if (includeParametersInUsage(leafJc)) { 148 for (ParameterDescription param : leafJc.getParameters()) { 149 if (!param.getParameter().hidden()) { 150 writer.write(" ["); 151 writer.write(param.getParameter().getParameter().names()[0]); 152 writer.write("]"); 153 } 154 } 155 } else { 156 if (!leafJc.getParameters().isEmpty()) { 157 writer.write(" [<options>]"); 158 } 159 } 160 161 if (!leafJc.getCommands().isEmpty()) { 162 writer.write(" [<command [<args>]]"); 163 } 164 165 if (leafJc.getMainParameter() != null) { 166 String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc.getMainParameter()); 167 if (argumentNames.length == 0) { 168 writer.write(" <args>"); 169 } else { 170 String argumentName = argumentNames[0]; 171 boolean writeAngleBrackets = !argumentName.startsWith("<") && !argumentName.startsWith("["); 172 writer.write(" "); 173 if (writeAngleBrackets) { 174 writer.write("<"); 175 } 176 writer.write(argumentNames[0]); 177 if (writeAngleBrackets) { 178 writer.write(">"); 179 } 180 } 181 } 182 183 writer.deindent(2); 184 185 String commandDescription = ExtendedCommands.getCommandDescription(leafJc); 186 if (commandDescription != null) { 187 writer.write("\n"); 188 writer.write(commandDescription); 189 } 190 191 if (!leafJc.getParameters().isEmpty() || leafJc.getMainParameter() != null) { 192 writer.write("\n\nOptions:"); 193 writer.indent(2); 194 for (ParameterDescription param : getSortedParameters(leafJc)) { 195 if (!param.getParameter().hidden()) { 196 writer.write("\n"); 197 writer.indent(4); 198 if (!param.getNames().isEmpty()) { 199 writer.write(Joiner.on(',').join(param.getParameter().names())); 200 } 201 if (getParameterArity(param) > 0) { 202 String[] argumentNames = ExtendedCommands.parameterArgumentNames(param); 203 for (int i = 0; i < getParameterArity(param); i++) { 204 writer.write(" "); 205 if (i < argumentNames.length) { 206 writer.write("<"); 207 writer.write(argumentNames[i]); 208 writer.write(">"); 209 } else { 210 writer.write("<arg>"); 211 } 212 } 213 } 214 if (param.getDescription() != null && !param.getDescription().isEmpty()) { 215 writer.write(" - "); 216 writer.write(param.getDescription()); 217 } 218 if (param.getDefault() != null) { 219 String defaultValue = null; 220 if (param.getParameterized().getType() == Boolean.class || 221 param.getParameterized().getType() == Boolean.TYPE) { 222 if ((Boolean)param.getDefault()) { 223 defaultValue = "True"; 224 } 225 } else if (List.class.isAssignableFrom(param.getParameterized().getType())) { 226 if (!((List)param.getDefault()).isEmpty()) { 227 defaultValue = param.getDefault().toString(); 228 } 229 } else { 230 defaultValue = param.getDefault().toString(); 231 } 232 if (defaultValue != null) { 233 writer.write(" (default: "); 234 writer.write(defaultValue); 235 writer.write(")"); 236 } 237 } 238 writer.deindent(4); 239 } 240 } 241 242 if (leafJc.getMainParameter() != null) { 243 String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc.getMainParameter()); 244 writer.write("\n"); 245 writer.indent(4); 246 if (argumentNames.length > 0) { 247 writer.write("<"); 248 writer.write(argumentNames[0]); 249 writer.write(">"); 250 } else { 251 writer.write("<args>"); 252 } 253 254 if (leafJc.getMainParameterDescription() != null) { 255 writer.write(" - "); 256 writer.write(leafJc.getMainParameterDescription()); 257 } 258 writer.deindent(4); 259 } 260 writer.deindent(2); 261 } 262 263 if (!leafJc.getCommands().isEmpty()) { 264 writer.write("\n\nCommands:"); 265 writer.indent(2); 266 267 268 List<Entry<String, JCommander>> entryList = Lists.newArrayList(leafJc.getCommands().entrySet()); 269 Collections.sort(entryList, new Comparator<Entry<String, JCommander>>() { 270 @Override public int compare(Entry<String, JCommander> o1, Entry<String, JCommander> o2) { 271 return o1.getKey().compareTo(o2.getKey()); 272 } 273 }); 274 275 for (Entry<String, JCommander> entry : entryList) { 276 String commandName = entry.getKey(); 277 JCommander command = entry.getValue(); 278 279 Object arg = command.getObjects().get(0); 280 Parameters parametersAnno = arg.getClass().getAnnotation(Parameters.class); 281 if (!parametersAnno.hidden()) { 282 writer.write("\n"); 283 writer.indent(4); 284 writer.write(commandName); 285 List<String> aliases = getCommandAliases(command); 286 if (!aliases.isEmpty()) { 287 writer.write("("); 288 writer.write(Joiner.on(',').join(aliases)); 289 writer.write(")"); 290 } 291 292 String commandDesc = leafJc.getCommandDescription(commandName); 293 if (commandDesc != null) { 294 writer.write(" - "); 295 writer.write(commandDesc); 296 } 297 writer.deindent(4); 298 } 299 } 300 writer.deindent(2); 301 } 302 303 String postfixDescription = getPostfixDescription(leafJc); 304 if (!postfixDescription.isEmpty()) { 305 writer.write("\n\n"); 306 writer.write(postfixDescription); 307 } 308 309 writer.flush(); 310 311 return stringWriter.getBuffer().toString(); 312 } catch (IOException ex) { 313 throw new RuntimeException(ex); 314 } 315 } 316 } 317