Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 1 | /* |
| 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 | */ |
| 16 | package com.android.tradefed.config; |
| 17 | |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 18 | import java.util.ArrayList; |
| 19 | import java.util.Arrays; |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 20 | import java.util.Collection; |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 21 | import java.util.List; |
Brett Chabot | 51a4e3d | 2010-03-09 11:08:44 -0800 | [diff] [blame] | 22 | import java.util.ListIterator; |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 23 | |
| 24 | /** |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 25 | * 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 Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 112 | */ |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 113 | class ArgsOptionParser extends OptionSetter { |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 114 | |
| 115 | static final String SHORT_NAME_PREFIX = "-"; |
| 116 | static final String OPTION_NAME_PREFIX = "--"; |
| 117 | |
| 118 | /** |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 119 | * Creates a {@link ArgsOptionParser} for a collection of objects. |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 120 | * |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 121 | * @param optionSource the config objects. |
| 122 | * @throws ConfigurationException if config objects is improperly configured. |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 123 | */ |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 124 | 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 Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 136 | } |
| 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 Chabot | 51a4e3d | 2010-03-09 11:08:44 -0800 | [diff] [blame] | 146 | return parseOptions(Arrays.asList(args).listIterator()); |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 147 | } |
| 148 | |
Brett Chabot | 51a4e3d | 2010-03-09 11:08:44 -0800 | [diff] [blame] | 149 | private List<String> parseOptions(ListIterator<String> args) throws ConfigurationException { |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 150 | 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 Chabot | 51a4e3d | 2010-03-09 11:08:44 -0800 | [diff] [blame] | 178 | private void parseLongOption(String arg, ListIterator<String> args) |
| 179 | throws ConfigurationException { |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 180 | // 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 Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 191 | if (value == null) { |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 192 | if (isBooleanOption(name)) { |
Brett Chabot | a08f718 | 2010-03-08 18:55:38 -0800 | [diff] [blame] | 193 | value = name.startsWith(BOOL_FALSE_PREFIX) ? "false" : "true"; |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 194 | } else { |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 195 | value = grabNextValue(args, name); |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 196 | } |
| 197 | } |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 198 | setOptionValue(name, value); |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 199 | } |
| 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 Chabot | a08f718 | 2010-03-08 18:55:38 -0800 | [diff] [blame] | 205 | // (But not -abf=out.txt --- POSIX doesn't mention that either way, but GNU expressly forbids |
| 206 | // it.) |
Brett Chabot | 51a4e3d | 2010-03-09 11:08:44 -0800 | [diff] [blame] | 207 | private void parseGroupedShortOptions(String arg, ListIterator<String> args) |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 208 | throws ConfigurationException { |
| 209 | for (int i = 1; i < arg.length(); ++i) { |
Brett Chabot | a08f718 | 2010-03-08 18:55:38 -0800 | [diff] [blame] | 210 | final String name = String.valueOf(arg.charAt(i)); |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 211 | String value; |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 212 | if (isBooleanOption(name)) { |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 213 | value = "true"; |
| 214 | } else { |
Brett Chabot | a08f718 | 2010-03-08 18:55:38 -0800 | [diff] [blame] | 215 | // We need a value. If there's anything left, we take the rest of this |
| 216 | // "short option". |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 217 | if (i + 1 < arg.length()) { |
| 218 | value = arg.substring(i + 1); |
| 219 | i = arg.length() - 1; |
| 220 | } else { |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 221 | value = grabNextValue(args, name); |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 222 | } |
| 223 | } |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 224 | setOptionValue(name, value); |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 225 | } |
| 226 | } |
| 227 | |
| 228 | /** |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 229 | * Returns the next element of 'args' if there is one. Uses 'name' to construct a helpful error |
| 230 | * message. |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 231 | * |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 232 | * @param args the arg iterator |
| 233 | * @param name the name of current argument |
| 234 | * @throws ConfigurationException if no argument is present |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 235 | * |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 236 | * @returns the next element |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 237 | */ |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 238 | private String grabNextValue(ListIterator<String> args, String name) |
Brett Chabot | a08f718 | 2010-03-08 18:55:38 -0800 | [diff] [blame] | 239 | throws ConfigurationException { |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 240 | if (!args.hasNext()) { |
Brett Chabot | e278a5b | 2010-06-01 16:20:14 -0700 | [diff] [blame^] | 241 | String type = getTypeForOption(name); |
| 242 | throw new ConfigurationException(String.format("option '%s' requires a '%s' argument", |
Brett Chabot | a08f718 | 2010-03-08 18:55:38 -0800 | [diff] [blame] | 243 | name, type)); |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 244 | } |
| 245 | return args.next(); |
| 246 | } |
Brett Chabot | 74121d8 | 2010-01-28 20:14:27 -0800 | [diff] [blame] | 247 | } |