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 #include "common/libs/utils/flag_parser.h"
18
19 #include <algorithm>
20 #include <cerrno>
21 #include <cstdlib>
22 #include <cstring>
23 #include <functional>
24 #include <iostream>
25 #include <iterator>
26 #include <sstream>
27 #include <string>
28 #include <string_view>
29 #include <type_traits>
30 #include <unordered_map>
31 #include <unordered_set>
32 #include <utility>
33 #include <vector>
34
35 #include <android-base/logging.h>
36 #include <android-base/parsebool.h>
37 #include <android-base/scopeguard.h>
38 #include <android-base/strings.h>
39 #include <fmt/format.h>
40
41 #include "common/libs/utils/result.h"
42 #include "common/libs/utils/tee_logging.h"
43
44 namespace cuttlefish {
45
operator <<(std::ostream & out,const FlagAlias & alias)46 std::ostream& operator<<(std::ostream& out, const FlagAlias& alias) {
47 switch (alias.mode) {
48 case FlagAliasMode::kFlagExact:
49 return out << alias.name;
50 case FlagAliasMode::kFlagPrefix:
51 return out << alias.name << "*";
52 case FlagAliasMode::kFlagConsumesFollowing:
53 return out << alias.name << " *";
54 default:
55 LOG(FATAL) << "Unexpected flag alias mode " << (int)alias.mode;
56 }
57 return out;
58 }
59
UnvalidatedAlias(const FlagAlias & alias)60 Flag& Flag::UnvalidatedAlias(const FlagAlias& alias) & {
61 aliases_.push_back(alias);
62 return *this;
63 }
UnvalidatedAlias(const FlagAlias & alias)64 Flag Flag::UnvalidatedAlias(const FlagAlias& alias) && {
65 aliases_.push_back(alias);
66 return *this;
67 }
68
ValidateAlias(const FlagAlias & alias)69 void Flag::ValidateAlias(const FlagAlias& alias) {
70 using android::base::EndsWith;
71 using android::base::StartsWith;
72
73 CHECK(StartsWith(alias.name, "-")) << "Flags should start with \"-\"";
74 if (alias.mode == FlagAliasMode::kFlagPrefix) {
75 CHECK(EndsWith(alias.name, "=")) << "Prefix flags should end with \"=\"";
76 }
77
78 CHECK(!HasAlias(alias)) << "Duplicate flag alias: " << alias.name;
79 if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
80 CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
81 << "Overlapping flag aliases for " << alias.name;
82 CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name}))
83 << "Overlapping flag aliases for " << alias.name;
84 } else if (alias.mode == FlagAliasMode::kFlagExact) {
85 CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name}))
86 << "Overlapping flag aliases for " << alias.name;
87 CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name}))
88 << "Overlapping flag aliases for " << alias.name;
89 } else if (alias.mode == FlagAliasMode::kFlagConsumesArbitrary) {
90 CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
91 << "Overlapping flag aliases for " << alias.name;
92 CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name}))
93 << "Overlapping flag aliases for " << alias.name;
94 }
95 }
96
Alias(const FlagAlias & alias)97 Flag& Flag::Alias(const FlagAlias& alias) & {
98 ValidateAlias(alias);
99 aliases_.push_back(alias);
100 return *this;
101 }
Alias(const FlagAlias & alias)102 Flag Flag::Alias(const FlagAlias& alias) && {
103 ValidateAlias(alias);
104 aliases_.push_back(alias);
105 return *this;
106 }
107
Help(const std::string & help)108 Flag& Flag::Help(const std::string& help) & {
109 help_ = help;
110 return *this;
111 }
Help(const std::string & help)112 Flag Flag::Help(const std::string& help) && {
113 help_ = help;
114 return *this;
115 }
116
Getter(std::function<std::string ()> fn)117 Flag& Flag::Getter(std::function<std::string()> fn) & {
118 getter_ = std::move(fn);
119 return *this;
120 }
Getter(std::function<std::string ()> fn)121 Flag Flag::Getter(std::function<std::string()> fn) && {
122 getter_ = std::move(fn);
123 return *this;
124 }
125
Setter(std::function<Result<void> (const FlagMatch &)> setter)126 Flag& Flag::Setter(std::function<Result<void>(const FlagMatch&)> setter) & {
127 setter_ = std::move(setter);
128 return *this;
129 }
Setter(std::function<Result<void> (const FlagMatch &)> setter)130 Flag Flag::Setter(std::function<Result<void>(const FlagMatch&)> setter) && {
131 setter_ = std::move(setter);
132 return *this;
133 }
134
LikelyFlag(const std::string & next_arg)135 static bool LikelyFlag(const std::string& next_arg) {
136 return android::base::StartsWith(next_arg, "-");
137 }
138
ParseBool(const std::string & value,const std::string & name)139 Result<bool> ParseBool(const std::string& value, const std::string& name) {
140 auto result = android::base::ParseBool(value);
141 CF_EXPECT(result != android::base::ParseBoolResult::kError,
142 "Failed to parse value \"" << value << "\" for " << name);
143 if (result == android::base::ParseBoolResult::kTrue) {
144 return true;
145 }
146 return false;
147 }
148
Process(const std::string & arg,const std::optional<std::string> & next_arg) const149 Result<Flag::FlagProcessResult> Flag::Process(
150 const std::string& arg, const std::optional<std::string>& next_arg) const {
151 using android::base::StringReplace;
152 auto normalized_arg = StringReplace(arg, "-", "_", true);
153 if (!setter_ && aliases_.size() > 0) {
154 return CF_ERRF("No setter for flag with alias {}", aliases_[0].name);
155 }
156 for (auto& alias : aliases_) {
157 auto normalized_alias = StringReplace(alias.name, "-", "_", true);
158 switch (alias.mode) {
159 case FlagAliasMode::kFlagConsumesArbitrary:
160 if (normalized_arg != normalized_alias) {
161 continue;
162 }
163 if (!next_arg || LikelyFlag(*next_arg)) {
164 CF_EXPECTF((*setter_)({arg, ""}), "Processing \"{}\" failed", arg);
165 return FlagProcessResult::kFlagConsumed;
166 }
167 CF_EXPECTF((*setter_)({arg, *next_arg}),
168 "Processing \"{}\" \"{}\" failed", arg, *next_arg);
169 return FlagProcessResult::kFlagConsumedOnlyFollowing;
170 case FlagAliasMode::kFlagConsumesFollowing:
171 if (normalized_arg != normalized_alias) {
172 continue;
173 }
174 CF_EXPECTF(next_arg.has_value(), "Expected an argument after \"{}\"",
175 arg);
176 CF_EXPECTF((*setter_)({arg, *next_arg}),
177 "Processing \"{}\" \"{}\" failed", arg, *next_arg);
178 return FlagProcessResult::kFlagConsumedWithFollowing;
179 case FlagAliasMode::kFlagExact:
180 if (normalized_arg != normalized_alias) {
181 continue;
182 }
183 CF_EXPECTF((*setter_)({arg, arg}), "Processing \"{}\" failed", arg);
184 return FlagProcessResult::kFlagConsumed;
185 case FlagAliasMode::kFlagPrefix:
186 if (!android::base::StartsWith(normalized_arg, normalized_alias)) {
187 continue;
188 }
189 CF_EXPECTF((*setter_)({alias.name, arg.substr(alias.name.size())}),
190 "Processing \"{}\" failed", arg);
191 return FlagProcessResult::kFlagConsumed;
192 default:
193 return CF_ERRF("Unknown flag alias mode: {}", (int)alias.mode);
194 }
195 }
196 return FlagProcessResult::kFlagSkip;
197 }
198
Parse(std::vector<std::string> & arguments) const199 Result<void> Flag::Parse(std::vector<std::string>& arguments) const {
200 for (int i = 0; i < arguments.size();) {
201 std::string arg = arguments[i];
202 std::optional<std::string> next_arg;
203 if (i < arguments.size() - 1) {
204 next_arg = arguments[i + 1];
205 }
206 switch (CF_EXPECT(Process(arg, next_arg))) {
207 case FlagProcessResult::kFlagConsumed:
208 arguments.erase(arguments.begin() + i);
209 break;
210 case FlagProcessResult::kFlagConsumedWithFollowing:
211 arguments.erase(arguments.begin() + i, arguments.begin() + i + 2);
212 break;
213 case FlagProcessResult::kFlagConsumedOnlyFollowing:
214 arguments.erase(arguments.begin() + i + 1, arguments.begin() + i + 2);
215 break;
216 case FlagProcessResult::kFlagSkip:
217 i++;
218 break;
219 }
220 }
221 return {};
222 }
Parse(std::vector<std::string> && arguments) const223 Result<void> Flag::Parse(std::vector<std::string>&& arguments) const {
224 CF_EXPECT(Parse(static_cast<std::vector<std::string>&>(arguments)));
225 return {};
226 }
227
HasAlias(const FlagAlias & test) const228 bool Flag::HasAlias(const FlagAlias& test) const {
229 for (const auto& alias : aliases_) {
230 if (alias.mode == test.mode && alias.name == test.name) {
231 return true;
232 }
233 }
234 return false;
235 }
236
XmlEscape(const std::string & s)237 static std::string XmlEscape(const std::string& s) {
238 using android::base::StringReplace;
239 return StringReplace(StringReplace(s, "<", "<", true), ">", ">", true);
240 }
241
WriteGflagsCompatXml(std::ostream & out) const242 bool Flag::WriteGflagsCompatXml(std::ostream& out) const {
243 std::unordered_set<std::string> name_guesses;
244 for (const auto& alias : aliases_) {
245 std::string_view name = alias.name;
246 if (!android::base::ConsumePrefix(&name, "-")) {
247 continue;
248 }
249 android::base::ConsumePrefix(&name, "-");
250 if (alias.mode == FlagAliasMode::kFlagExact) {
251 android::base::ConsumePrefix(&name, "no");
252 name_guesses.insert(std::string{name});
253 } else if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
254 name_guesses.insert(std::string{name});
255 } else if (alias.mode == FlagAliasMode::kFlagPrefix) {
256 if (!android::base::ConsumeSuffix(&name, "=")) {
257 continue;
258 }
259 name_guesses.insert(std::string{name});
260 }
261 }
262 bool found_alias = false;
263 for (const auto& name : name_guesses) {
264 bool has_bool_aliases =
265 HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) &&
266 HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) &&
267 HasAlias({FlagAliasMode::kFlagExact, "-" + name}) &&
268 HasAlias({FlagAliasMode::kFlagExact, "--" + name}) &&
269 HasAlias({FlagAliasMode::kFlagExact, "-no" + name}) &&
270 HasAlias({FlagAliasMode::kFlagExact, "--no" + name});
271 bool has_other_aliases =
272 HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) &&
273 HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) &&
274 HasAlias({FlagAliasMode::kFlagConsumesFollowing, "-" + name}) &&
275 HasAlias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
276 bool has_help_aliases = HasAlias({FlagAliasMode::kFlagExact, "-help"}) &&
277 HasAlias({FlagAliasMode::kFlagExact, "--help"});
278 std::vector<bool> has_aliases = {has_bool_aliases, has_other_aliases,
279 has_help_aliases};
280 const auto true_count =
281 std::count(has_aliases.cbegin(), has_aliases.cend(), true);
282 if (true_count > 1) {
283 LOG(ERROR) << "Expected exactly one of has_bool_aliases, "
284 << "has_other_aliases, and has_help_aliases, got "
285 << true_count << " for \"" << name << "\".";
286 return false;
287 }
288 if (true_count == 0) {
289 continue;
290 }
291 found_alias = true;
292 std::string type_str =
293 (has_bool_aliases || has_help_aliases) ? "bool" : "string";
294 // Lifted from external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML
295 out << "<flag>\n";
296 out << " <file>file.cc</file>\n";
297 out << " <name>" << XmlEscape(name) << "</name>\n";
298 auto help = help_ ? XmlEscape(*help_) : std::string{""};
299 out << " <meaning>" << help << "</meaning>\n";
300 auto value = getter_ ? XmlEscape((*getter_)()) : std::string{""};
301 out << " <default>" << value << "</default>\n";
302 out << " <current>" << value << "</current>\n";
303 out << " <type>" << type_str << "</type>\n";
304 out << "</flag>\n";
305 }
306 return found_alias;
307 }
308
operator <<(std::ostream & out,const Flag & flag)309 std::ostream& operator<<(std::ostream& out, const Flag& flag) {
310 out << "[";
311 for (auto it = flag.aliases_.begin(); it != flag.aliases_.end(); it++) {
312 if (it != flag.aliases_.begin()) {
313 out << ", ";
314 }
315 out << *it;
316 }
317 out << "]\n";
318 if (flag.help_) {
319 out << "(" << *flag.help_ << ")\n";
320 }
321 if (flag.getter_) {
322 out << "(Current value: \"" << (*flag.getter_)() << "\")\n";
323 }
324 return out;
325 }
326
ArgsToVec(int argc,char ** argv)327 std::vector<std::string> ArgsToVec(int argc, char** argv) {
328 std::vector<std::string> args;
329 args.reserve(argc);
330 for (int i = 0; i < argc; i++) {
331 args.push_back(argv[i]);
332 }
333 return args;
334 }
335
336 struct Separated {
337 std::vector<std::string> args_before_mark;
338 std::vector<std::string> args_after_mark;
339 };
SeparateByEndOfOptionMark(std::vector<std::string> args)340 static Separated SeparateByEndOfOptionMark(std::vector<std::string> args) {
341 std::vector<std::string> args_before_mark;
342 std::vector<std::string> args_after_mark;
343
344 auto itr = std::find(args.begin(), args.end(), "--");
345 bool has_mark = (itr != args.end());
346 if (!has_mark) {
347 args_before_mark = std::move(args);
348 } else {
349 args_before_mark.insert(args_before_mark.end(), args.begin(), itr);
350 args_after_mark.insert(args_after_mark.end(), itr + 1, args.end());
351 }
352
353 return Separated{
354 .args_before_mark = std::move(args_before_mark),
355 .args_after_mark = std::move(args_after_mark),
356 };
357 }
358
ConsumeFlagsImpl(const std::vector<Flag> & flags,std::vector<std::string> & args)359 static Result<void> ConsumeFlagsImpl(const std::vector<Flag>& flags,
360 std::vector<std::string>& args) {
361 for (const auto& flag : flags) {
362 CF_EXPECT(flag.Parse(args));
363 }
364 return {};
365 }
366
ConsumeFlagsImpl(const std::vector<Flag> & flags,std::vector<std::string> && args)367 static Result<void> ConsumeFlagsImpl(const std::vector<Flag>& flags,
368 std::vector<std::string>&& args) {
369 for (const auto& flag : flags) {
370 CF_EXPECT(flag.Parse(args));
371 }
372 return {};
373 }
374
ConsumeFlags(const std::vector<Flag> & flags,std::vector<std::string> & args,const bool recognize_end_of_option_mark)375 Result<void> ConsumeFlags(const std::vector<Flag>& flags,
376 std::vector<std::string>& args,
377 const bool recognize_end_of_option_mark) {
378 if (!recognize_end_of_option_mark) {
379 CF_EXPECT(ConsumeFlagsImpl(flags, args));
380 return {};
381 }
382 auto separated = SeparateByEndOfOptionMark(std::move(args));
383 args.clear();
384 auto result = ConsumeFlagsImpl(flags, separated.args_before_mark);
385 args = std::move(separated.args_before_mark);
386 args.insert(args.end(),
387 std::make_move_iterator(separated.args_after_mark.begin()),
388 std::make_move_iterator(separated.args_after_mark.end()));
389 CF_EXPECT(std::move(result));
390 return {};
391 }
392
ConsumeFlags(const std::vector<Flag> & flags,std::vector<std::string> && args,const bool recognize_end_of_option_mark)393 Result<void> ConsumeFlags(const std::vector<Flag>& flags,
394 std::vector<std::string>&& args,
395 const bool recognize_end_of_option_mark) {
396 if (!recognize_end_of_option_mark) {
397 CF_EXPECT(ConsumeFlagsImpl(flags, std::move(args)));
398 return {};
399 }
400 auto separated = SeparateByEndOfOptionMark(std::move(args));
401 CF_EXPECT(ConsumeFlagsImpl(flags, std::move(separated.args_before_mark)));
402 return {};
403 }
404
WriteGflagsCompatXml(const std::vector<Flag> & flags,std::ostream & out)405 bool WriteGflagsCompatXml(const std::vector<Flag>& flags, std::ostream& out) {
406 for (const auto& flag : flags) {
407 if (!flag.WriteGflagsCompatXml(out)) {
408 return false;
409 }
410 }
411 return true;
412 }
413
VerbosityFlag(android::base::LogSeverity & value)414 Flag VerbosityFlag(android::base::LogSeverity& value) {
415 return GflagsCompatFlag("verbosity")
416 .Getter([&value]() { return FromSeverity(value); })
417 .Setter([&value](const FlagMatch& match) -> Result<void> {
418 value = CF_EXPECT(ToSeverity(match.value));
419 return {};
420 })
421 .Help("Used to set the verbosity level for logging.");
422 }
423
HelpFlag(const std::vector<Flag> & flags,std::string text)424 Flag HelpFlag(const std::vector<Flag>& flags, std::string text) {
425 auto setter = [&flags, text](FlagMatch) -> Result<void> {
426 if (text.size() > 0) {
427 LOG(INFO) << text;
428 }
429 for (const auto& flag : flags) {
430 LOG(INFO) << flag;
431 }
432 return CF_ERR("user requested early exit");
433 };
434 return Flag()
435 .Alias({FlagAliasMode::kFlagExact, "-help"})
436 .Alias({FlagAliasMode::kFlagExact, "--help"})
437 .Setter(setter);
438 }
439
GflagsCompatBoolFlagSetter(const std::string & name,bool & value,const FlagMatch & match)440 static Result<void> GflagsCompatBoolFlagSetter(const std::string& name,
441 bool& value,
442 const FlagMatch& match) {
443 const auto& key = match.key;
444 if (key == "-" + name || key == "--" + name) {
445 value = true;
446 return {};
447 } else if (key == "-no" + name || key == "--no" + name) {
448 value = false;
449 return {};
450 } else if (key == "-" + name + "=" || key == "--" + name + "=") {
451 if (match.value == "true") {
452 value = true;
453 return {};
454 } else if (match.value == "false") {
455 value = false;
456 return {};
457 } else {
458 return CF_ERRF("Unexpected boolean value \"{}\" for \{}\"", match.value,
459 name);
460 }
461 }
462 return CF_ERRF("Unexpected key \"{}\" for \"{}\"", match.key, name);
463 }
464
GflagsCompatBoolFlagBase(const std::string & name)465 static Flag GflagsCompatBoolFlagBase(const std::string& name) {
466 return Flag()
467 .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
468 .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
469 .Alias({FlagAliasMode::kFlagExact, "-" + name})
470 .Alias({FlagAliasMode::kFlagExact, "--" + name})
471 .Alias({FlagAliasMode::kFlagExact, "-no" + name})
472 .Alias({FlagAliasMode::kFlagExact, "--no" + name});
473 }
474
HelpXmlFlag(const std::vector<Flag> & flags,std::ostream & out,bool & value,std::string text)475 Flag HelpXmlFlag(const std::vector<Flag>& flags, std::ostream& out, bool& value,
476 std::string text) {
477 const std::string name = "helpxml";
478 auto setter = [name, &out, &value, text,
479 &flags](const FlagMatch& match) -> Result<void> {
480 bool print_xml = false;
481 CF_EXPECT(GflagsCompatBoolFlagSetter(name, print_xml, match));
482 if (!print_xml) {
483 return {};
484 }
485 if (!text.empty()) {
486 out << text << std::endl;
487 }
488 value = print_xml;
489 out << "<?xml version=\"1.0\"?>" << std::endl << "<AllFlags>" << std::endl;
490 WriteGflagsCompatXml(flags, out);
491 out << "</AllFlags>" << std::flush;
492 return CF_ERR("Requested early exit");
493 };
494 return GflagsCompatBoolFlagBase(name).Setter(setter);
495 }
496
InvalidFlagGuard()497 Flag InvalidFlagGuard() {
498 return Flag()
499 .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, "-"})
500 .Help(
501 "This executable only supports the flags in `-help`. Positional "
502 "arguments may be supported.")
503 .Setter([](const FlagMatch& match) -> Result<void> {
504 return CF_ERRF("Unknown flag \"{}\"", match.value);
505 });
506 }
507
UnexpectedArgumentGuard()508 Flag UnexpectedArgumentGuard() {
509 return Flag()
510 .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, ""})
511 .Help(
512 "This executable only supports the flags in `-help`. Positional "
513 "arguments are not supported.")
514 .Setter([](const FlagMatch& match) -> Result<void> {
515 return CF_ERRF("Unexpected argument \"{}\"", match.value);
516 });
517 }
518
GflagsCompatFlag(const std::string & name)519 Flag GflagsCompatFlag(const std::string& name) {
520 return Flag()
521 .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
522 .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
523 .Alias({FlagAliasMode::kFlagConsumesFollowing, "-" + name})
524 .Alias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
525 };
526
GflagsCompatFlag(const std::string & name,std::string & value)527 Flag GflagsCompatFlag(const std::string& name, std::string& value) {
528 return GflagsCompatFlag(name)
529 .Getter([&value]() { return value; })
530 .Setter([&value](const FlagMatch& match) -> Result<void> {
531 value = match.value;
532 return {};
533 });
534 }
535
536 template <typename T>
ParseInteger(const std::string & value)537 std::optional<T> ParseInteger(const std::string& value) {
538 if (value.size() == 0) {
539 return {};
540 }
541 const char* base = value.c_str();
542 char* end = nullptr;
543 errno = 0;
544 auto r = strtoll(base, &end, /* auto-detect */ 0);
545 if (errno != 0 || end != base + value.size()) {
546 return {};
547 }
548 if (static_cast<T>(r) != r) {
549 return {};
550 }
551 return r;
552 }
553
554 template <typename T>
GflagsCompatNumericFlagGeneric(const std::string & name,T & value)555 static Flag GflagsCompatNumericFlagGeneric(const std::string& name, T& value) {
556 return GflagsCompatFlag(name)
557 .Getter([&value]() { return std::to_string(value); })
558 .Setter([&value](const FlagMatch& match) -> Result<void> {
559 value = CF_EXPECTF(ParseInteger<T>(match.value),
560 "Failed to parse \"{}\" as an integer", match.value);
561 return {};
562 });
563 }
564
GflagsCompatFlag(const std::string & name,int32_t & value)565 Flag GflagsCompatFlag(const std::string& name, int32_t& value) {
566 return GflagsCompatNumericFlagGeneric(name, value);
567 }
568
GflagsCompatFlag(const std::string & name,bool & value)569 Flag GflagsCompatFlag(const std::string& name, bool& value) {
570 return GflagsCompatBoolFlagBase(name)
571 .Getter([&value]() { return fmt::format("{}", value); })
572 .Setter([name, &value](const FlagMatch& match) {
573 return GflagsCompatBoolFlagSetter(name, value, match);
574 });
575 };
576
GflagsCompatFlag(const std::string & name,std::vector<std::string> & value)577 Flag GflagsCompatFlag(const std::string& name,
578 std::vector<std::string>& value) {
579 return GflagsCompatFlag(name)
580 .Getter([&value]() { return android::base::Join(value, ','); })
581 .Setter([&value](const FlagMatch& match) -> Result<void> {
582 if (match.value.empty()) {
583 value.clear();
584 return {};
585 }
586 std::vector<std::string> str_vals =
587 android::base::Split(match.value, ",");
588 value = std::move(str_vals);
589 return {};
590 });
591 }
592
GflagsCompatFlag(const std::string & name,std::vector<bool> & value,const bool def_val)593 Flag GflagsCompatFlag(const std::string& name, std::vector<bool>& value,
594 const bool def_val) {
595 return GflagsCompatFlag(name)
596 .Getter([&value]() { return fmt::format("{}", fmt::join(value, ",")); })
597 .Setter([&name, &value, def_val](const FlagMatch& match) -> Result<void> {
598 if (match.value.empty()) {
599 value.clear();
600 return {};
601 }
602 std::vector<std::string> str_vals =
603 android::base::Split(match.value, ",");
604 value.clear();
605 std::vector<bool> output_vals;
606 output_vals.reserve(str_vals.size());
607 for (const auto& str_val : str_vals) {
608 if (str_val.empty()) {
609 output_vals.push_back(def_val);
610 } else {
611 output_vals.push_back(CF_EXPECT(ParseBool(str_val, name)));
612 }
613 }
614 value = output_vals;
615 return {};
616 });
617 }
618
619 } // namespace cuttlefish
620