blob: 868778fc1f6e881370fd231c72cf9a5b35e705a1 [file] [log] [blame]
Brett Chabot74121d82010-01-28 20:14:27 -08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.tradefed.config;
17
Brett Chabot74121d82010-01-28 20:14:27 -080018import java.util.ArrayList;
19import java.util.Arrays;
Brett Chabote278a5b2010-06-01 16:20:14 -070020import java.util.Collection;
Brett Chabot74121d82010-01-28 20:14:27 -080021import java.util.List;
Brett Chabot51a4e3d2010-03-09 11:08:44 -080022import java.util.ListIterator;
Brett Chabot74121d82010-01-28 20:14:27 -080023
24/**
Brett Chabote278a5b2010-06-01 16:20:14 -070025 * Populates {@link Option} fields from parsed command line arguments.
26 * <p/>
27 * Strings in the passed-in String[] are parsed left-to-right. Each String is classified as a short
28 * option (such as "-v"), a long option (such as "--verbose"), an argument to an option (such as
29 * "out.txt" in "-f out.txt"), or a non-option positional argument.
30 * <p/>
31 * Each option argument must map to exactly one {@link Option} field. A long option maps to the
32 * {@link Option#name()}, and a short option maps to {@link Option#shortName()}. Each
33 * {@link Option#name()} and {@link Option@shortName()} must be unique with respect to all other
34 * {@link Option} fields.
35 * <p/>
36 * A simple short option is a "-" followed by a short option character. If the option requires an
37 * argument (which is true of any non-boolean option), it may be written as a separate parameter,
38 * but need not be. That is, "-f out.txt" and "-fout.txt" are both acceptable.
39 * <p/>
40 * It is possible to specify multiple short options after a single "-" as long as all (except
41 * possibly the last) do not require arguments.
42 * <p/>
43 * A long option begins with "--" followed by several characters. If the option requires an
44 * argument, it may be written directly after the option name, separated by "=", or as the next
45 * argument. (That is, "--file=out.txt" or "--file out.txt".)
46 * <p/>
47 * A boolean long option '--name' automatically gets a '--no-name' companion. Given an option
48 * "--flag", then, "--flag", "--no-flag", "--flag=true" and "--flag=false" are all valid, though
49 * neither "--flag true" nor "--flag false" are allowed (since "--flag" by itself is sufficient,
50 * the following "true" or "false" is interpreted separately). You can use "yes" and "no" as
51 * synonyms for "true" and "false".
52 * <p/>
53 * Each String not starting with a "-" and not a required argument of a previous option is a
54 * non-option positional argument, as are all successive Strings. Each String after a "--" is a
55 * non-option positional argument.
56 * <p/>
57 * The fields corresponding to options are updated as their options are processed. Any remaining
58 * positional arguments are returned as a List<String>.
59 * <p/>
60 * Here's a simple example:
61 * <p/>
62 * <pre>
63 * // Non-@Option fields will be ignored.
64 * class Options {
65 * @Option(name = "quiet", shortName = 'q')
66 * boolean quiet = false;
67 *
68 * // Here the user can use --no-color.
69 * @Option(name = "color")
70 * boolean color = true;
71 *
72 * @Option(name = "mode", shortName = 'm')
73 * String mode = "standard; // Supply a default just by setting the field.
74 *
75 * @Option(name = "port", shortName = 'p')
76 * int portNumber = 8888;
77 *
78 * // There's no need to offer a short name for rarely-used options.
79 * @Option(name = "timeout" )
80 * double timeout = 1.0;
81 *
82 * @Option(name = "output-file", shortName = 'o' })
83 * File output;
84 *
85 * // Multiple options are added to the collection.
86 * // The collection field itself must be non-null.
87 * @Option(name = "input-file", shortName = 'i')
88 * List<File> inputs = new ArrayList<File>();
89 *
90 * }
91 *
92 * Options options = new Options();
93 * List<String> posArgs = new OptionParser(options).parse("--input-file", "/tmp/file1.txt");
94 * for (File inputFile : options.inputs) {
95 * if (!options.quiet) {
96 * ...
97 * }
98 * ...
99 *
100 * }
101 *
102 * </pre>
103 * See also:
104 * <ul>
105 * <li>the getopt(1) man page
106 * <li>Python's "optparse" module (http://docs.python.org/library/optparse.html)
107 * <li>the POSIX "Utility Syntax Guidelines" (http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap12.html#tag_12_02)
108 * <li>the GNU "Standards for Command Line Interfaces" (http://www.gnu.org/prep/standards/standards.html#Command_002dLine-Interfaces)
109 * </ul>
110 *
111 * @see {@link OptionSetter}
Brett Chabot74121d82010-01-28 20:14:27 -0800112 */
Brett Chabote278a5b2010-06-01 16:20:14 -0700113class ArgsOptionParser extends OptionSetter {
Brett Chabot74121d82010-01-28 20:14:27 -0800114
115 static final String SHORT_NAME_PREFIX = "-";
116 static final String OPTION_NAME_PREFIX = "--";
117
118 /**
Brett Chabote278a5b2010-06-01 16:20:14 -0700119 * Creates a {@link ArgsOptionParser} for a collection of objects.
Brett Chabot74121d82010-01-28 20:14:27 -0800120 *
Brett Chabote278a5b2010-06-01 16:20:14 -0700121 * @param optionSource the config objects.
122 * @throws ConfigurationException if config objects is improperly configured.
Brett Chabot74121d82010-01-28 20:14:27 -0800123 */
Brett Chabote278a5b2010-06-01 16:20:14 -0700124 public ArgsOptionParser(Collection<Object> optionSources) throws ConfigurationException {
125 super(optionSources);
126 }
127
128 /**
129 * Creates a {@link ArgsOptionParser} for one or more objects.
130 *
131 * @param optionSource the config objects.
132 * @throws ConfigurationException if config objects is improperly configured.
133 */
134 public ArgsOptionParser(Object... optionSources) throws ConfigurationException {
135 super(optionSources);
Brett Chabot74121d82010-01-28 20:14:27 -0800136 }
137
138 /**
139 * Parses the command-line arguments 'args', setting the @Option fields of the 'optionSource'
140 * provided to the constructor.
141 *
142 * @returns a {@link List} of the positional arguments left over after processing all options.
143 * @throws ConfigurationException if error occurred parsing the arguments.
144 */
145 public List<String> parse(String[] args) throws ConfigurationException {
Brett Chabot51a4e3d2010-03-09 11:08:44 -0800146 return parseOptions(Arrays.asList(args).listIterator());
Brett Chabot74121d82010-01-28 20:14:27 -0800147 }
148
Brett Chabot51a4e3d2010-03-09 11:08:44 -0800149 private List<String> parseOptions(ListIterator<String> args) throws ConfigurationException {
Brett Chabot74121d82010-01-28 20:14:27 -0800150 final List<String> leftovers = new ArrayList<String>();
151
152 // Scan 'args'.
153 while (args.hasNext()) {
154 final String arg = args.next();
155 if (arg.equals(OPTION_NAME_PREFIX)) {
156 // "--" marks the end of options and the beginning of positional arguments.
157 break;
158 } else if (arg.startsWith(OPTION_NAME_PREFIX)) {
159 // A long option.
160 parseLongOption(arg, args);
161 } else if (arg.startsWith(SHORT_NAME_PREFIX)) {
162 // A short option.
163 parseGroupedShortOptions(arg, args);
164 } else {
165 // The first non-option marks the end of options.
166 leftovers.add(arg);
167 break;
168 }
169 }
170
171 // Package up the leftovers.
172 while (args.hasNext()) {
173 leftovers.add(args.next());
174 }
175 return leftovers;
176 }
177
Brett Chabot51a4e3d2010-03-09 11:08:44 -0800178 private void parseLongOption(String arg, ListIterator<String> args)
179 throws ConfigurationException {
Brett Chabot74121d82010-01-28 20:14:27 -0800180 // remove prefix to just get name
181 String name = arg.replaceFirst("^" + OPTION_NAME_PREFIX, "");
182 String value = null;
183
184 // Support "--name=value" as well as "--name value".
185 final int equalsIndex = name.indexOf('=');
186 if (equalsIndex != -1) {
187 value = name.substring(equalsIndex + 1);
188 name = name.substring(0, equalsIndex);
189 }
190
Brett Chabot74121d82010-01-28 20:14:27 -0800191 if (value == null) {
Brett Chabote278a5b2010-06-01 16:20:14 -0700192 if (isBooleanOption(name)) {
Brett Chabota08f7182010-03-08 18:55:38 -0800193 value = name.startsWith(BOOL_FALSE_PREFIX) ? "false" : "true";
Brett Chabot74121d82010-01-28 20:14:27 -0800194 } else {
Brett Chabote278a5b2010-06-01 16:20:14 -0700195 value = grabNextValue(args, name);
Brett Chabot74121d82010-01-28 20:14:27 -0800196 }
197 }
Brett Chabote278a5b2010-06-01 16:20:14 -0700198 setOptionValue(name, value);
Brett Chabot74121d82010-01-28 20:14:27 -0800199 }
200
201 // Given boolean options a and b, and non-boolean option f, we want to allow:
202 // -ab
203 // -abf out.txt
204 // -abfout.txt
Brett Chabota08f7182010-03-08 18:55:38 -0800205 // (But not -abf=out.txt --- POSIX doesn't mention that either way, but GNU expressly forbids
206 // it.)
Brett Chabot51a4e3d2010-03-09 11:08:44 -0800207 private void parseGroupedShortOptions(String arg, ListIterator<String> args)
Brett Chabot74121d82010-01-28 20:14:27 -0800208 throws ConfigurationException {
209 for (int i = 1; i < arg.length(); ++i) {
Brett Chabota08f7182010-03-08 18:55:38 -0800210 final String name = String.valueOf(arg.charAt(i));
Brett Chabot74121d82010-01-28 20:14:27 -0800211 String value;
Brett Chabote278a5b2010-06-01 16:20:14 -0700212 if (isBooleanOption(name)) {
Brett Chabot74121d82010-01-28 20:14:27 -0800213 value = "true";
214 } else {
Brett Chabota08f7182010-03-08 18:55:38 -0800215 // We need a value. If there's anything left, we take the rest of this
216 // "short option".
Brett Chabot74121d82010-01-28 20:14:27 -0800217 if (i + 1 < arg.length()) {
218 value = arg.substring(i + 1);
219 i = arg.length() - 1;
220 } else {
Brett Chabote278a5b2010-06-01 16:20:14 -0700221 value = grabNextValue(args, name);
Brett Chabot74121d82010-01-28 20:14:27 -0800222 }
223 }
Brett Chabote278a5b2010-06-01 16:20:14 -0700224 setOptionValue(name, value);
Brett Chabot74121d82010-01-28 20:14:27 -0800225 }
226 }
227
228 /**
Brett Chabote278a5b2010-06-01 16:20:14 -0700229 * Returns the next element of 'args' if there is one. Uses 'name' to construct a helpful error
230 * message.
Brett Chabot74121d82010-01-28 20:14:27 -0800231 *
Brett Chabote278a5b2010-06-01 16:20:14 -0700232 * @param args the arg iterator
233 * @param name the name of current argument
234 * @throws ConfigurationException if no argument is present
Brett Chabot74121d82010-01-28 20:14:27 -0800235 *
Brett Chabote278a5b2010-06-01 16:20:14 -0700236 * @returns the next element
Brett Chabot74121d82010-01-28 20:14:27 -0800237 */
Brett Chabote278a5b2010-06-01 16:20:14 -0700238 private String grabNextValue(ListIterator<String> args, String name)
Brett Chabota08f7182010-03-08 18:55:38 -0800239 throws ConfigurationException {
Brett Chabot74121d82010-01-28 20:14:27 -0800240 if (!args.hasNext()) {
Brett Chabote278a5b2010-06-01 16:20:14 -0700241 String type = getTypeForOption(name);
242 throw new ConfigurationException(String.format("option '%s' requires a '%s' argument",
Brett Chabota08f7182010-03-08 18:55:38 -0800243 name, type));
Brett Chabot74121d82010-01-28 20:14:27 -0800244 }
245 return args.next();
246 }
Brett Chabot74121d82010-01-28 20:14:27 -0800247}