James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
7613df 1 /*
DO 2  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
3  *
4  * (Taken from JGit org.eclipse.jgit.pgm.opt.CmdLineParser.)
5  *
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * - Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
14  * - Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution.
17  *
18  * - Neither the name of the Git Development Community nor the names of its
19  * contributors may be used to endorse or promote products derived from this
20  * software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  */
34
35 package com.gitblit.utils.cli;
36
37 import java.io.StringWriter;
38 import java.io.Writer;
39 import java.lang.annotation.Annotation;
40 import java.lang.reflect.AnnotatedElement;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.ResourceBundle;
45
46 import org.kohsuke.args4j.Argument;
47 import org.kohsuke.args4j.CmdLineException;
48 import org.kohsuke.args4j.IllegalAnnotationError;
49 import org.kohsuke.args4j.NamedOptionDef;
50 import org.kohsuke.args4j.Option;
51 import org.kohsuke.args4j.OptionDef;
52 import org.kohsuke.args4j.spi.BooleanOptionHandler;
53 import org.kohsuke.args4j.spi.EnumOptionHandler;
54 import org.kohsuke.args4j.spi.FieldSetter;
55 import org.kohsuke.args4j.spi.OptionHandler;
56 import org.kohsuke.args4j.spi.Setter;
57
58 import com.google.common.base.Strings;
59 import com.google.common.collect.LinkedHashMultimap;
60 import com.google.common.collect.Lists;
61 import com.google.common.collect.Maps;
62 import com.google.common.collect.Multimap;
63
64 /**
65  * Extended command line parser which handles --foo=value arguments.
66  * <p>
67  * The args4j package does not natively handle --foo=value and instead prefers
68  * to see --foo value on the command line. Many users are used to the GNU style
69  * --foo=value long option, so we convert from the GNU style format to the
70  * args4j style format prior to invoking args4j for parsing.
71  */
72 public class CmdLineParser {
ec9703 73     public interface Factory {
JM 74         CmdLineParser create(Object bean);
75     }
7613df 76
ec9703 77     private final MyParser parser;
7613df 78
ec9703 79     @SuppressWarnings("rawtypes")
JM 80     private Map<String, OptionHandler> options;
7613df 81
ec9703 82     /**
JM 83      * Creates a new command line owner that parses arguments/options and set
84      * them into the given object.
85      *
86      * @param bean
87      *            instance of a class annotated by
88      *            {@link org.kohsuke.args4j.Option} and
89      *            {@link org.kohsuke.args4j.Argument}. this object will receive
90      *            values.
91      *
92      * @throws IllegalAnnotationError
93      *             if the option bean class is using args4j annotations
94      *             incorrectly.
95      */
96     public CmdLineParser(Object bean) throws IllegalAnnotationError {
97         this.parser = new MyParser(bean);
98     }
7613df 99
ec9703 100     public void addArgument(Setter<?> setter, Argument a) {
JM 101         parser.addArgument(setter, a);
102     }
7613df 103
ec9703 104     public void addOption(Setter<?> setter, Option o) {
JM 105         parser.addOption(setter, o);
106     }
7613df 107
ec9703 108     public void printSingleLineUsage(Writer w, ResourceBundle rb) {
JM 109         parser.printSingleLineUsage(w, rb);
110     }
7613df 111
ec9703 112     public void printUsage(Writer out, ResourceBundle rb) {
JM 113         parser.printUsage(out, rb);
114     }
7613df 115
ec9703 116     public void printDetailedUsage(String name, StringWriter out) {
JM 117         out.write(name);
118         printSingleLineUsage(out, null);
119         out.write('\n');
120         out.write('\n');
121         printUsage(out, null);
122         out.write('\n');
123     }
7613df 124
ec9703 125     public void printQueryStringUsage(String name, StringWriter out) {
JM 126         out.write(name);
7613df 127
ec9703 128         char next = '?';
JM 129         List<NamedOptionDef> booleans = new ArrayList<NamedOptionDef>();
130         for (@SuppressWarnings("rawtypes")
131         OptionHandler handler : parser.options) {
132             if (handler.option instanceof NamedOptionDef) {
133                 NamedOptionDef n = (NamedOptionDef) handler.option;
7613df 134
ec9703 135                 if (handler instanceof BooleanOptionHandler) {
JM 136                     booleans.add(n);
137                     continue;
138                 }
7613df 139
ec9703 140                 if (!n.required()) {
JM 141                     out.write('[');
142                 }
143                 out.write(next);
144                 next = '&';
145                 if (n.name().startsWith("--")) {
146                     out.write(n.name().substring(2));
147                 } else if (n.name().startsWith("-")) {
148                     out.write(n.name().substring(1));
149                 } else {
150                     out.write(n.name());
151                 }
152                 out.write('=');
7613df 153
ec9703 154                 out.write(metaVar(handler, n));
JM 155                 if (!n.required()) {
156                     out.write(']');
157                 }
158                 if (n.isMultiValued()) {
159                     out.write('*');
160                 }
161             }
162         }
163         for (NamedOptionDef n : booleans) {
164             if (!n.required()) {
165                 out.write('[');
166             }
167             out.write(next);
168             next = '&';
169             if (n.name().startsWith("--")) {
170                 out.write(n.name().substring(2));
171             } else if (n.name().startsWith("-")) {
172                 out.write(n.name().substring(1));
173             } else {
174                 out.write(n.name());
175             }
176             if (!n.required()) {
177                 out.write(']');
178             }
179         }
180     }
7613df 181
ec9703 182     private static String metaVar(OptionHandler<?> handler, NamedOptionDef n) {
JM 183         String var = n.metaVar();
184         if (Strings.isNullOrEmpty(var)) {
185             var = handler.getDefaultMetaVariable();
186             if (handler instanceof EnumOptionHandler) {
187                 var = var.substring(1, var.length() - 1).replace(" ", "");
188             }
189         }
190         return var;
191     }
7613df 192
ec9703 193     public boolean wasHelpRequestedByOption() {
JM 194         return parser.help.value;
195     }
7613df 196
ec9703 197     public void parseArgument(final String... args) throws CmdLineException {
JM 198         List<String> tmp = Lists.newArrayListWithCapacity(args.length);
199         for (int argi = 0; argi < args.length; argi++) {
200             final String str = args[argi];
201             if (str.equals("--")) {
202                 while (argi < args.length)
203                     tmp.add(args[argi++]);
204                 break;
205             }
7613df 206
ec9703 207             if (str.startsWith("--")) {
JM 208                 final int eq = str.indexOf('=');
209                 if (eq > 0) {
210                     tmp.add(str.substring(0, eq));
211                     tmp.add(str.substring(eq + 1));
212                     continue;
213                 }
214             }
7613df 215
ec9703 216             tmp.add(str);
JM 217         }
218         parser.parseArgument(tmp.toArray(new String[tmp.size()]));
219     }
7613df 220
ec9703 221     public void parseOptionMap(Map<String, String[]> parameters) throws CmdLineException {
JM 222         Multimap<String, String> map = LinkedHashMultimap.create();
223         for (Map.Entry<String, String[]> ent : parameters.entrySet()) {
224             for (String val : ent.getValue()) {
225                 map.put(ent.getKey(), val);
226             }
227         }
228         parseOptionMap(map);
229     }
7613df 230
ec9703 231     public void parseOptionMap(Multimap<String, String> params) throws CmdLineException {
JM 232         List<String> tmp = Lists.newArrayListWithCapacity(2 * params.size());
233         for (final String key : params.keySet()) {
234             String name = makeOption(key);
7613df 235
ec9703 236             if (isBoolean(name)) {
JM 237                 boolean on = false;
238                 for (String value : params.get(key)) {
239                     on = toBoolean(key, value);
240                 }
241                 if (on) {
242                     tmp.add(name);
243                 }
244             } else {
245                 for (String value : params.get(key)) {
246                     tmp.add(name);
247                     tmp.add(value);
248                 }
249             }
250         }
251         parser.parseArgument(tmp.toArray(new String[tmp.size()]));
252     }
7613df 253
ec9703 254     public boolean isBoolean(String name) {
JM 255         return findHandler(makeOption(name)) instanceof BooleanOptionHandler;
256     }
7613df 257
ec9703 258     private String makeOption(String name) {
JM 259         if (!name.startsWith("-")) {
260             if (name.length() == 1) {
261                 name = "-" + name;
262             } else {
263                 name = "--" + name;
264             }
265         }
266         return name;
267     }
7613df 268
ec9703 269     @SuppressWarnings("rawtypes")
JM 270     private OptionHandler findHandler(String name) {
271         if (options == null) {
272             options = index(parser.options);
273         }
274         return options.get(name);
275     }
7613df 276
ec9703 277     @SuppressWarnings("rawtypes")
JM 278     private static Map<String, OptionHandler> index(List<OptionHandler> in) {
279         Map<String, OptionHandler> m = Maps.newHashMap();
280         for (OptionHandler handler : in) {
281             if (handler.option instanceof NamedOptionDef) {
282                 NamedOptionDef def = (NamedOptionDef) handler.option;
283                 if (!def.isArgument()) {
284                     m.put(def.name(), handler);
285                     for (String alias : def.aliases()) {
286                         m.put(alias, handler);
287                     }
288                 }
289             }
290         }
291         return m;
292     }
7613df 293
ec9703 294     private boolean toBoolean(String name, String value) throws CmdLineException {
JM 295         if ("true".equals(value) || "t".equals(value) || "yes".equals(value) || "y".equals(value) || "on".equals(value)
296                 || "1".equals(value) || value == null || "".equals(value)) {
297             return true;
298         }
7613df 299
ec9703 300         if ("false".equals(value) || "f".equals(value) || "no".equals(value) || "n".equals(value)
JM 301                 || "off".equals(value) || "0".equals(value)) {
302             return false;
303         }
7613df 304
ec9703 305         throw new CmdLineException(parser, String.format("invalid boolean \"%s=%s\"", name, value));
JM 306     }
7613df 307
ec9703 308     private class MyParser extends org.kohsuke.args4j.CmdLineParser {
JM 309         @SuppressWarnings("rawtypes")
310         private List<OptionHandler> options;
311         private HelpOption help;
7613df 312
ec9703 313         MyParser(final Object bean) {
JM 314             super(bean);
315             ensureOptionsInitialized();
316         }
7613df 317
ec9703 318         @SuppressWarnings({ "unchecked", "rawtypes" })
JM 319         @Override
320         protected OptionHandler createOptionHandler(final OptionDef option, final Setter setter) {
321             if (isHandlerSpecified(option) || isEnum(setter) || isPrimitive(setter)) {
322                 return add(super.createOptionHandler(option, setter));
323             }
7613df 324
ec9703 325             // OptionHandlerFactory<?> factory = handlers.get(setter.getType());
JM 326             // if (factory != null) {
327             // return factory.create(this, option, setter);
328             // }
329             return add(super.createOptionHandler(option, setter));
330         }
7613df 331
ec9703 332         @SuppressWarnings("rawtypes")
JM 333         private OptionHandler add(OptionHandler handler) {
334             ensureOptionsInitialized();
335             options.add(handler);
336             return handler;
337         }
7613df 338
ec9703 339         private void ensureOptionsInitialized() {
JM 340             if (options == null) {
341                 help = new HelpOption();
342                 options = Lists.newArrayList();
343                 addOption(help, help);
344             }
345         }
7613df 346
ec9703 347         private boolean isHandlerSpecified(final OptionDef option) {
JM 348             return option.handler() != OptionHandler.class;
349         }
7613df 350
ec9703 351         private <T> boolean isEnum(Setter<T> setter) {
JM 352             return Enum.class.isAssignableFrom(setter.getType());
353         }
7613df 354
ec9703 355         private <T> boolean isPrimitive(Setter<T> setter) {
JM 356             return setter.getType().isPrimitive();
357         }
358     }
7613df 359
ec9703 360     private static class HelpOption implements Option, Setter<Boolean> {
JM 361         private boolean value;
7613df 362
ec9703 363         @Override
JM 364         public String name() {
365             return "--help";
366         }
7613df 367
ec9703 368         @Override
JM 369         public String[] aliases() {
370             return new String[] { "-h" };
371         }
7613df 372
ec9703 373         @Override
JM 374         public String[] depends() {
375             return new String[] {};
376         }
7613df 377
ec9703 378         @Override
JM 379         public boolean hidden() {
380             return false;
381         }
7613df 382
ec9703 383         @Override
JM 384         public String usage() {
385             return "display this help text";
386         }
7613df 387
ec9703 388         @Override
JM 389         public void addValue(Boolean val) {
390             value = val;
391         }
7613df 392
ec9703 393         @Override
JM 394         public Class<? extends OptionHandler<Boolean>> handler() {
395             return BooleanOptionHandler.class;
396         }
7613df 397
ec9703 398         @Override
JM 399         public String metaVar() {
400             return "";
401         }
7613df 402
ec9703 403         @Override
JM 404         public boolean required() {
405             return false;
406         }
7613df 407
ec9703 408         @Override
JM 409         public Class<? extends Annotation> annotationType() {
410             return Option.class;
411         }
7613df 412
ec9703 413         @Override
JM 414         public FieldSetter asFieldSetter() {
415             throw new UnsupportedOperationException();
416         }
7613df 417
ec9703 418         @Override
JM 419         public AnnotatedElement asAnnotatedElement() {
420             throw new UnsupportedOperationException();
421         }
7613df 422
ec9703 423         @Override
JM 424         public Class<Boolean> getType() {
425             return Boolean.class;
426         }
7613df 427
ec9703 428         @Override
JM 429         public boolean isMultiValued() {
430             return false;
431         }
006651 432
JM 433         @Override
434         public boolean help() {
435             return true;
436         }
437
438         @Override
439         public String[] forbids() {
440             return new String [0];
441         }
ec9703 442     }
7613df 443 }