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, "<", "&lt;", true), ">", "&gt;", 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