1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | // Provides a simple class, |CommandLine|, for dealing with command lines (and |
6 | // flags and positional arguments). |
7 | // |
8 | // * Options (a.k.a. flags or switches) are all of the form "--name=<value>" (or |
9 | // "--name", but this is indistinguishable from "--name="), where <value> is a |
10 | // string. Not supported: "-name", "-n", "--name <value>", "-n <value>", etc. |
11 | // * Option order is preserved. |
12 | // * Option processing is stopped after the first positional argument[*]. Thus |
13 | // in the command line "my_program --foo bar --baz", only "--foo" is an option |
14 | // ("bar" and "--baz" are positional arguments). |
15 | // * Options can be looked up by name. If the same option occurs multiple times, |
16 | // convention is to use the last occurrence (and the provided look-up |
17 | // functions behave this way). |
18 | // * "--" may also be used to separate options from positional arguments. Thus |
19 | // in the command line "my_program --foo -- --bar", "--bar" is a positional |
20 | // argument. |
21 | // * |CommandLine|s store |argv[0]| and distinguish between not having |argv[0]| |
22 | // and |argv[0]| being empty. |
23 | // * Apart from being copyable and movable, |CommandLine|s are immutable. |
24 | // |
25 | // There are factory functions to turn raw arguments into |CommandLine|s, in |
26 | // accordance with the above rules. However, |CommandLine|s may be used more |
27 | // generically (with the user transforming arguments using different rules, |
28 | // e.g., accepting "-name" as an option), subject to certain limitations (e.g., |
29 | // not being able to distinguish "no value" from "empty value"). |
30 | // |
31 | // [*] This is somewhat annoying for users, but: a. it's standard Unix behavior |
32 | // for most command line parsers, b. it makes "my_program *" (etc.) safer (which |
33 | // mostly explains a.), c. it makes parsing "subcommands", like "my_program |
34 | // --flag_for_my_program subcommand --flag_for_subcommand" saner. |
35 | |
36 | #ifndef LIB_FML_COMMAND_LINE_H_ |
37 | #define LIB_FML_COMMAND_LINE_H_ |
38 | |
39 | #include <cstddef> |
40 | #include <initializer_list> |
41 | #include <optional> |
42 | #include <string> |
43 | #include <string_view> |
44 | #include <unordered_map> |
45 | #include <vector> |
46 | |
47 | #include "flutter/fml/macros.h" |
48 | |
49 | namespace fml { |
50 | |
51 | // CommandLine ----------------------------------------------------------------- |
52 | |
53 | // Class that stores processed command lines ("argv[0]", options, and positional |
54 | // arguments) and provides access to them. For more details, see the file-level |
55 | // comment above. This class is thread-safe. |
56 | class CommandLine final { |
57 | private: |
58 | class ConstructionHelper; |
59 | |
60 | public: |
61 | struct Option { |
62 | Option() {} |
63 | explicit Option(const std::string& name); |
64 | Option(const std::string& name, const std::string& value); |
65 | |
66 | bool operator==(const Option& other) const { |
67 | return name == other.name && value == other.value; |
68 | } |
69 | bool operator!=(const Option& other) const { return !operator==(other); } |
70 | |
71 | std::string name; |
72 | std::string value; |
73 | }; |
74 | |
75 | // Default, copy, and move constructors (to be out-of-lined). |
76 | CommandLine(); |
77 | CommandLine(const CommandLine& from); |
78 | CommandLine(CommandLine&& from); |
79 | |
80 | // Constructs a |CommandLine| from its "components". This is especially useful |
81 | // for creating a new |CommandLine| based on an existing |CommandLine| (e.g., |
82 | // adding options or arguments). |
83 | explicit CommandLine(const std::string& argv0, |
84 | const std::vector<Option>& options, |
85 | const std::vector<std::string>& positional_args); |
86 | |
87 | ~CommandLine(); |
88 | |
89 | // Copy and move assignment (to be out-of-lined). |
90 | CommandLine& operator=(const CommandLine& from); |
91 | CommandLine& operator=(CommandLine&& from); |
92 | |
93 | bool has_argv0() const { return has_argv0_; } |
94 | const std::string& argv0() const { return argv0_; } |
95 | const std::vector<Option>& options() const { return options_; } |
96 | const std::vector<std::string>& positional_args() const { |
97 | return positional_args_; |
98 | } |
99 | |
100 | bool operator==(const CommandLine& other) const { |
101 | // No need to compare |option_index_|. |
102 | return has_argv0_ == other.has_argv0_ && argv0_ == other.argv0_ && |
103 | options_ == other.options_ && |
104 | positional_args_ == other.positional_args_; |
105 | } |
106 | bool operator!=(const CommandLine& other) const { return !operator==(other); } |
107 | |
108 | // Returns true if this command line has the option |name| (and if |index| is |
109 | // non-null, sets |*index| to the index of the *last* occurrence of the given |
110 | // option in |options()|) and false if not. |
111 | bool HasOption(std::string_view name, size_t* index = nullptr) const; |
112 | |
113 | // Gets the value of the option |name|. Returns true (and sets |*value|) on |
114 | // success and false (leaving |*value| alone) on failure. |
115 | bool GetOptionValue(std::string_view name, std::string* value) const; |
116 | |
117 | // Gets all values of the option |name|. Returns all values, which may be |
118 | // empty if the option is not specified. |
119 | std::vector<std::string_view> GetOptionValues(std::string_view name) const; |
120 | |
121 | // Gets the value of the option |name|, with a default if the option is not |
122 | // specified. (Note: This doesn't return a const reference, since this would |
123 | // make the |default_value| argument inconvenient/dangerous.) |
124 | std::string GetOptionValueWithDefault(std::string_view name, |
125 | std::string_view default_value) const; |
126 | |
127 | private: |
128 | bool has_argv0_ = false; |
129 | // The following should all be empty if |has_argv0_| is false. |
130 | std::string argv0_; |
131 | std::vector<Option> options_; |
132 | std::vector<std::string> positional_args_; |
133 | |
134 | // Maps option names to position in |options_|. If a given name occurs |
135 | // multiple times, the index will be to the *last* occurrence. |
136 | std::unordered_map<std::string, size_t> option_index_; |
137 | |
138 | // Allow copy and assignment. |
139 | }; |
140 | |
141 | // Factory functions (etc.) ---------------------------------------------------- |
142 | |
143 | namespace internal { |
144 | |
145 | // Helper class for building command lines (finding options, etc.) from raw |
146 | // arguments. |
147 | class CommandLineBuilder final { |
148 | public: |
149 | CommandLineBuilder(); |
150 | ~CommandLineBuilder(); |
151 | |
152 | // Processes an additional argument in the command line. Returns true if |arg| |
153 | // is the *first* positional argument. |
154 | bool ProcessArg(const std::string& arg); |
155 | |
156 | // Builds a |CommandLine| from the arguments processed so far. |
157 | CommandLine Build() const; |
158 | |
159 | private: |
160 | bool has_argv0_ = false; |
161 | std::string argv0_; |
162 | std::vector<CommandLine::Option> options_; |
163 | std::vector<std::string> positional_args_; |
164 | |
165 | // True if we've started processing positional arguments. |
166 | bool started_positional_args_ = false; |
167 | |
168 | FML_DISALLOW_COPY_AND_ASSIGN(CommandLineBuilder); |
169 | }; |
170 | |
171 | } // namespace internal |
172 | |
173 | // The following factory functions create |CommandLine|s from raw arguments in |
174 | // accordance with the rules outlined at the top of this file. (Other ways of |
175 | // transforming raw arguments into options and positional arguments are |
176 | // possible.) |
177 | |
178 | // Like |CommandLineFromIterators()| (see below), but sets |
179 | // |*first_positional_arg| to point to the first positional argument seen (or |
180 | // |last| if none are seen). This is useful for processing "subcommands". |
181 | template <typename InputIterator> |
182 | inline CommandLine CommandLineFromIteratorsFindFirstPositionalArg( |
183 | InputIterator first, |
184 | InputIterator last, |
185 | InputIterator* first_positional_arg) { |
186 | if (first_positional_arg) { |
187 | *first_positional_arg = last; |
188 | } |
189 | internal::CommandLineBuilder builder; |
190 | for (auto it = first; it < last; ++it) { |
191 | if (builder.ProcessArg(arg: *it)) { |
192 | if (first_positional_arg) { |
193 | *first_positional_arg = it; |
194 | } |
195 | } |
196 | } |
197 | return builder.Build(); |
198 | } |
199 | |
200 | // Builds a |CommandLine| from first/last iterators (where |last| is really |
201 | // one-past-the-last, as usual) to |std::string|s or things that implicitly |
202 | // convert to |std::string|. |
203 | template <typename InputIterator> |
204 | inline CommandLine CommandLineFromIterators(InputIterator first, |
205 | InputIterator last) { |
206 | return CommandLineFromIteratorsFindFirstPositionalArg<InputIterator>( |
207 | first, last, nullptr); |
208 | } |
209 | |
210 | // Builds a |CommandLine| from first/last iterators (where |last| is really |
211 | // one-past-the-last, as usual) to |std::string|s or things that implicitly |
212 | // convert to |std::string|, where argv[0] is provided separately. |
213 | template <typename InputIterator> |
214 | inline CommandLine CommandLineFromIteratorsWithArgv0(const std::string& argv0, |
215 | InputIterator first, |
216 | InputIterator last) { |
217 | internal::CommandLineBuilder builder; |
218 | builder.ProcessArg(arg: argv0); |
219 | for (auto it = first; it < last; ++it) { |
220 | builder.ProcessArg(arg: *it); |
221 | } |
222 | return builder.Build(); |
223 | } |
224 | |
225 | // Builds a |CommandLine| by obtaining the arguments of the process using host |
226 | // platform APIs. The resulting |CommandLine| will be encoded in UTF-8. |
227 | // Returns an empty optional if this is not supported on the host platform. |
228 | // |
229 | // This can be useful on platforms where argv may not be provided as UTF-8. |
230 | std::optional<CommandLine> CommandLineFromPlatform(); |
231 | |
232 | // Builds a |CommandLine| from the usual argc/argv. |
233 | inline CommandLine CommandLineFromArgcArgv(int argc, const char* const* argv) { |
234 | return CommandLineFromIterators(first: argv, last: argv + argc); |
235 | } |
236 | |
237 | // Builds a |CommandLine| by first trying the platform specific implementation, |
238 | // and then falling back to the argc/argv. |
239 | // |
240 | // If the platform provides a special way of getting arguments, this method may |
241 | // discard the values passed in to argc/argv. |
242 | inline CommandLine CommandLineFromPlatformOrArgcArgv(int argc, |
243 | const char* const* argv) { |
244 | auto command_line = CommandLineFromPlatform(); |
245 | if (command_line.has_value()) { |
246 | return *command_line; |
247 | } |
248 | return CommandLineFromArgcArgv(argc, argv); |
249 | } |
250 | |
251 | // Builds a |CommandLine| from an initializer list of |std::string|s or things |
252 | // that implicitly convert to |std::string|. |
253 | template <typename StringType> |
254 | inline CommandLine CommandLineFromInitializerList( |
255 | std::initializer_list<StringType> argv) { |
256 | return CommandLineFromIterators(argv.begin(), argv.end()); |
257 | } |
258 | |
259 | // This is the "opposite" of the above factory functions, transforming a |
260 | // |CommandLine| into a vector of argument strings according to the rules |
261 | // outlined at the top of this file. |
262 | std::vector<std::string> CommandLineToArgv(const CommandLine& command_line); |
263 | |
264 | } // namespace fml |
265 | |
266 | #endif // LIB_FML_COMMAND_LINE_H_ |
267 | |