1 /*
2  * Copyright (C) 2018 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 
17 package com.android.dialer.commandline;
18 
19 import android.support.annotation.Nullable;
20 import com.android.dialer.commandline.Command.IllegalCommandLineArgumentException;
21 import com.google.auto.value.AutoValue;
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.ImmutableMap;
24 import com.google.common.collect.Iterators;
25 import com.google.common.collect.PeekingIterator;
26 import com.google.common.collect.UnmodifiableIterator;
27 
28 /**
29  * Parses command line arguments into optional flags (--foo, --key=value, --key value) and required
30  * positionals (which must be passed in order). Flags must start with "--" and are always before
31  * positionals. If flags are used "--" must be placed before positionals.
32  *
33  * <p>--flag will be interpreted as --flag=true, and --noflag as --flag=false
34  *
35  * <p>Grammar:<br>
36  * dialer-cmd.py <cmd> <args><br>
37  * <args> = (<flags> -- <positionals>) | <positionals><br>
38  * <flags> = "no"?<name>(<separator><value>)?<br>
39  * <separator> = " " | "="
40  */
41 @AutoValue
42 public abstract class Arguments {
43 
44   public static final Arguments EMPTY =
45       new AutoValue_Arguments(ImmutableMap.of(), ImmutableList.of());
46 
getFlags()47   public abstract ImmutableMap<String, String> getFlags();
48 
getPositionals()49   public abstract ImmutableList<String> getPositionals();
50 
51   /**
52    * Return the positional at {@code position}. Throw {@link IllegalCommandLineArgumentException} if
53    * it is absent and reports to the user {@code name} is expected.
54    */
expectPositional(int position, String name)55   public String expectPositional(int position, String name)
56       throws IllegalCommandLineArgumentException {
57     if (getPositionals().size() <= position) {
58       throw new IllegalCommandLineArgumentException(name + " expected");
59     }
60     return getPositionals().get(position);
61   }
62 
getBoolean(String flag, boolean defaultValue)63   public Boolean getBoolean(String flag, boolean defaultValue)
64       throws IllegalCommandLineArgumentException {
65     if (!getFlags().containsKey(flag)) {
66       return defaultValue;
67     }
68     switch (getFlags().get(flag)) {
69       case "true":
70         return true;
71       case "false":
72         return false;
73       default:
74         throw new IllegalCommandLineArgumentException("boolean value expected for " + flag);
75     }
76   }
77 
parse(@ullable String[] rawArguments)78   public static Arguments parse(@Nullable String[] rawArguments)
79       throws IllegalCommandLineArgumentException {
80     if (rawArguments == null) {
81       return EMPTY;
82     }
83     return parse(Iterators.forArray(rawArguments));
84   }
85 
parse(Iterable<String> rawArguments)86   public static Arguments parse(Iterable<String> rawArguments)
87       throws IllegalCommandLineArgumentException {
88     return parse(Iterators.unmodifiableIterator(rawArguments.iterator()));
89   }
90 
parse(UnmodifiableIterator<String> iterator)91   public static Arguments parse(UnmodifiableIterator<String> iterator)
92       throws IllegalCommandLineArgumentException {
93     PeekingIterator<String> peekingIterator = Iterators.peekingIterator(iterator);
94     ImmutableMap<String, String> flags = parseFlags(peekingIterator);
95     ImmutableList<String> positionals = parsePositionals(peekingIterator);
96 
97     return new AutoValue_Arguments(flags, positionals);
98   }
99 
parseFlags(PeekingIterator<String> iterator)100   private static ImmutableMap<String, String> parseFlags(PeekingIterator<String> iterator)
101       throws IllegalCommandLineArgumentException {
102     ImmutableMap.Builder<String, String> flags = ImmutableMap.builder();
103     if (!iterator.hasNext()) {
104       return flags.build();
105     }
106     if (!iterator.peek().startsWith("--")) {
107       return flags.build();
108     }
109 
110     while (iterator.hasNext()) {
111       String peek = iterator.peek();
112       if (peek.equals("--")) {
113         iterator.next();
114         return flags.build();
115       }
116       if (peek.startsWith("--")) {
117         String key = iterator.next().substring(2);
118         String value;
119         if (iterator.hasNext() && !iterator.peek().startsWith("--")) {
120           value = iterator.next();
121         } else if (key.contains("=")) {
122           String[] entry = key.split("=", 2);
123           key = entry[0];
124           value = entry[1];
125         } else if (key.startsWith("no")) {
126           key = key.substring(2);
127           value = "false";
128         } else {
129           value = "true";
130         }
131         flags.put(key, value);
132       } else {
133         throw new IllegalCommandLineArgumentException("flag or '--' expected");
134       }
135     }
136     return flags.build();
137   }
138 
parsePositionals(PeekingIterator<String> iterator)139   private static ImmutableList<String> parsePositionals(PeekingIterator<String> iterator) {
140     ImmutableList.Builder<String> positionals = ImmutableList.builder();
141     positionals.addAll(iterator);
142     return positionals.build();
143   }
144 }
145