1 /*
2  * Copyright (C) 2021 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 #pragma once
18 
19 #include <cstdint>
20 #include <functional>
21 #include <optional>
22 #include <ostream>
23 #include <string>
24 #include <vector>
25 
26 #include "common/libs/utils/result.h"
27 
28 /* Support for parsing individual flags out of a larger list of flags. This
29  * supports externally determining the order that flags are evaluated in, and
30  * incrementally integrating with existing flag parsing implementations.
31  *
32  * Start with Flag() or one of the GflagsCompatFlag(...) functions to create new
33  * flags. These flags should be aggregated through the application through some
34  * other mechanism and then evaluated individually with Flag::Parse or together
35  * with ConsumeFlags on arguments. */
36 
37 namespace cuttlefish {
38 
39 /* The matching behavior used with the name in `FlagAlias::name`. */
40 enum class FlagAliasMode {
41   /* Match arguments of the form `<name><value>`. In practice, <name> usually
42    * looks like "-flag=" or "--flag=", where the "-" and "=" are included in
43    * parsing. */
44   kFlagPrefix,
45   /* Match arguments of the form `<name>`. In practice, <name> will look like
46    * "-flag" or "--flag". */
47   kFlagExact,
48   /* Match a pair of arguments of the form `<name>` `<value>`. In practice,
49    * <name> will look like "-flag" or "--flag". */
50   kFlagConsumesFollowing,
51   /* Match a sequence of arguments of the form `<name>` `<value>` `<value>`.
52    * This uses heuristics to try to determine when `<value>` is actually another
53    * flag. */
54   kFlagConsumesArbitrary,
55 };
56 
57 /* A single matching rule for a `Flag`. One `Flag` can have multiple rules. */
58 struct FlagAlias {
59   FlagAliasMode mode;
60   std::string name;
61 };
62 
63 std::ostream& operator<<(std::ostream&, const FlagAlias&);
64 
65 /* A successful match in an argument list from a `FlagAlias` inside a `Flag`.
66  * The `key` value corresponds to `FlagAlias::name`. For a match of
67  * `FlagAliasMode::kFlagExact`, `key` and `value` will both be the `name`. */
68 struct FlagMatch {
69   std::string key;
70   std::string value;
71 };
72 
73 class Flag {
74  public:
75   /* Add an alias that triggers matches and calls to the `Setter` function. */
76   Flag& Alias(const FlagAlias& alias) &;
77   Flag Alias(const FlagAlias& alias) &&;
78   /* Set help text, visible in the class ostream writer method. Optional. */
79   Flag& Help(const std::string&) &;
80   Flag Help(const std::string&) &&;
81   /* Set a loader that displays the current value in help text. Optional. */
82   Flag& Getter(std::function<std::string()>) &;
83   Flag Getter(std::function<std::string()>) &&;
84   /* Set the callback for matches. The callback may be invoked multiple times.
85    */
86   Flag& Setter(std::function<Result<void>(const FlagMatch&)>) &;
87   Flag Setter(std::function<Result<void>(const FlagMatch&)>) &&;
88 
89   /* Examines a list of arguments, removing any matches from the list and
90    * invoking the `Setter` for every match. Returns `false` if the callback ever
91    * returns `false`. Non-matches are left in place. */
92   Result<void> Parse(std::vector<std::string>& flags) const;
93   Result<void> Parse(std::vector<std::string>&& flags) const;
94 
95   /* Write gflags `--helpxml` style output for a string-type flag. */
96   bool WriteGflagsCompatXml(std::ostream&) const;
97 
98  private:
99   /* Reports whether `Process` wants to consume zero, one, or two arguments. */
100   enum class FlagProcessResult {
101     /* Error in handling a flag, exit flag handling with an error result. */
102     kFlagSkip,                  /* Flag skipped; consume no arguments. */
103     kFlagConsumed,              /* Flag processed; consume one argument. */
104     kFlagConsumedWithFollowing, /* Flag processed; consume 2 arguments. */
105     kFlagConsumedOnlyFollowing, /* Flag processed; consume next argument. */
106   };
107 
108   void ValidateAlias(const FlagAlias& alias);
109   Flag& UnvalidatedAlias(const FlagAlias& alias) &;
110   Flag UnvalidatedAlias(const FlagAlias& alias) &&;
111 
112   /* Attempt to match a single argument. */
113   Result<FlagProcessResult> Process(
114       const std::string& argument,
115       const std::optional<std::string>& next_arg) const;
116 
117   bool HasAlias(const FlagAlias&) const;
118 
119   friend std::ostream& operator<<(std::ostream&, const Flag&);
120   friend Flag InvalidFlagGuard();
121   friend Flag UnexpectedArgumentGuard();
122 
123   std::vector<FlagAlias> aliases_;
124   std::optional<std::string> help_;
125   std::optional<std::function<std::string()>> getter_;
126   std::optional<std::function<Result<void>(const FlagMatch&)>> setter_;
127 };
128 
129 std::ostream& operator<<(std::ostream&, const Flag&);
130 
131 std::vector<std::string> ArgsToVec(int argc, char** argv);
132 
133 Result<bool> ParseBool(const std::string& value, const std::string& name);
134 
135 /* Handles a list of flags. Flags are matched in the order given in case two
136  * flags match the same argument. Matched flags are removed, leaving only
137  * unmatched arguments. */
138 Result<void> ConsumeFlags(const std::vector<Flag>& flags,
139                           std::vector<std::string>& args,
140                           const bool recognize_end_of_option_mark = false);
141 Result<void> ConsumeFlags(const std::vector<Flag>& flags,
142                           std::vector<std::string>&&,
143                           const bool recognize_end_of_option_mark = false);
144 
145 bool WriteGflagsCompatXml(const std::vector<Flag>&, std::ostream&);
146 
147 /* If -verbosity or --verbosity flags have a value, translates it to an android
148  * LogSeverity */
149 Flag VerbosityFlag(android::base::LogSeverity& value);
150 
151 /* If any of these are used, they should be evaluated after all other flags, and
152  * in the order defined here (help before invalid flags, invalid flags before
153  * unexpected arguments). */
154 
155 /* If a "-help" or "--help" flag is present, prints all the flags and fails. */
156 Flag HelpFlag(const std::vector<Flag>& flags, std::string text = "");
157 
158 /* If a "-helpxml" is present, prints all the flags in XML and fails. */
159 Flag HelpXmlFlag(const std::vector<Flag>& flags, std::ostream&, bool& value,
160                  std::string text = "");
161 
162 /* Catches unrecognized arguments that begin with `-`, and errors out. This
163  * effectively denies unknown flags. */
164 Flag InvalidFlagGuard();
165 /* Catches any arguments not extracted by other Flag matchers and errors out.
166  * This effectively denies unknown flags and any positional arguments. */
167 Flag UnexpectedArgumentGuard();
168 
169 // Create a flag resembling a gflags argument of the given type. This includes
170 // "-[-]flag=*",support for all types, "-[-]noflag" support for booleans, and
171 // "-flag *", "--flag *", support for other types. The value passed in the flag
172 // is saved to the defined reference.
173 Flag GflagsCompatFlag(const std::string& name);
174 Flag GflagsCompatFlag(const std::string& name, std::string& value);
175 Flag GflagsCompatFlag(const std::string& name, std::int32_t& value);
176 Flag GflagsCompatFlag(const std::string& name, bool& value);
177 Flag GflagsCompatFlag(const std::string& name, std::vector<std::string>& value);
178 Flag GflagsCompatFlag(const std::string& name, std::vector<bool>& value,
179                       const bool default_value);
180 
181 }  // namespace cuttlefish
182