| 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 | |