Home | History | Annotate | Download | only in jcommander
      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