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