• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 /// Command line argument parsing.
17 ///
18 /// The objects defined below can be used to parse command line arguments of
19 /// different types. These objects are "just enough" defined for current use
20 /// cases, but the design is intended to be extensible as new types and traits
21 /// are needed.
22 ///
23 /// Example:
24 ///
25 /// Given a boolean flag "verbose", a numerical flag "runs", and a positional
26 /// "port" argument to be parsed, we can create a vector of parsers. In this
27 /// example, we modify the parsers during creation to set default values:
28 ///
29 /// @code
30 ///   Vector<ArgParserVariant, 3> parsers = {
31 ///     BoolParser("-v", "--verbose").set_default(false),
32 ///     UnsignedParser<size_t>("-r", "--runs").set_default(1000),
33 ///     UnsignedParser<uint16_t>("port").set_default(11111),
34 ///   };
35 /// @endcode
36 ///
37 /// With this vector, we can then parse command line arguments and extract
38 /// the values of arguments that were set, e.g.:
39 ///
40 /// @code
41 ///   if (!ParseArgs(parsers, argc, argv).ok()) {
42 ///     PrintUsage(parsers, argv[0]);
43 ///     return 1;
44 ///   }
45 ///   bool verbose;
46 ///   size_t runs;
47 ///   uint16_t port;
48 ///   if (!GetArg(parsers, "--verbose", &verbose).ok() ||
49 ///       !GetArg(parsers, "--runs", &runs).ok() ||
50 ///       !GetArg(parsers, "port", &port).ok()) {
51 ///     // Shouldn't happen unless names do not match.
52 ///     return 1;
53 ///   }
54 ///
55 ///   // Do stuff with `verbose`, `runs`, and `port`...
56 /// @endcode
57 
58 #include <cstddef>
59 #include <cstdint>
60 #include <string_view>
61 #include <variant>
62 
63 #include "pw_containers/vector.h"
64 #include "pw_status/status.h"
65 
66 namespace pw::rpc::fuzz {
67 
68 /// Enumerates the results of trying to parse a specific command line argument
69 /// with a particular parsers.
70 enum ParseStatus {
71   /// The argument matched the parser and was successfully parsed without a
72   /// value.
73   kParsedOne,
74 
75   /// The argument matched the parser and was successfully parsed with a value.
76   kParsedTwo,
77 
78   /// The argument did not match the parser. This is not necessarily an error;
79   /// the argument may match a different parser.
80   kParseMismatch,
81 
82   /// The argument matched a parser, but could not be parsed. This may be due to
83   /// a missing value for a flag, a value of the wrong type, a provided value
84   /// being out of range, etc. Parsers should log additional details before
85   /// returning this value.
86   kParseFailure,
87 };
88 
89 /// Holds parsed argument values of different types.
90 using ArgVariant = std::variant<std::monostate, bool, uint64_t>;
91 
92 /// Base class for argument parsers.
93 class ArgParserBase {
94  public:
95   virtual ~ArgParserBase() = default;
96 
short_name()97   std::string_view short_name() const { return short_name_; }
long_name()98   std::string_view long_name() const { return long_name_; }
positional()99   bool positional() const { return positional_; }
100 
101   /// Clears the value. Typically, command line arguments are only parsed once,
102   /// but this method is useful for testing.
Reset()103   void Reset() { value_ = std::monostate(); }
104 
105  protected:
106   /// Defines an argument parser with a single name. This may be a positional
107   /// argument or a flag.
108   ArgParserBase(std::string_view name);
109 
110   /// Defines an argument parser for a flag with short and long names.
111   ArgParserBase(std::string_view shortopt, std::string_view longopt);
112 
set_initial(ArgVariant initial)113   void set_initial(ArgVariant initial) { initial_ = initial; }
set_value(ArgVariant value)114   void set_value(ArgVariant value) { value_ = value; }
115 
116   /// Examines if the given `arg` matches this parser. A parser for a flag can
117   /// match the short name (e.g. '-f') if set, or the long name (e.g. '--foo').
118   /// A parser for a positional argument will match anything until it has a
119   /// value set.
120   bool Match(std::string_view arg);
121 
122   /// Returns the parsed value.
123   template <typename T>
Get()124   T Get() const {
125     return std::get<T>(GetValue());
126   }
127 
128  private:
129   const ArgVariant& GetValue() const;
130 
131   std::string_view short_name_;
132   std::string_view long_name_;
133   bool positional_;
134 
135   ArgVariant initial_;
136   ArgVariant value_;
137 };
138 
139 // Argument parsers for boolean arguments. These arguments are always flags, and
140 // can be specified as, e.g. "-f" (true), "--foo" (true) or "--no-foo" (false).
141 class BoolParser : public ArgParserBase {
142  public:
143   BoolParser(std::string_view optname);
144   BoolParser(std::string_view shortopt, std::string_view longopt);
145 
value()146   bool value() const { return Get<bool>(); }
147   BoolParser& set_default(bool value);
148 
149   ParseStatus Parse(std::string_view arg0,
150                     std::string_view arg1 = std::string_view());
151 };
152 
153 // Type-erasing argument parser for unsigned integer arguments. This object
154 // always parses values as `uint64_t`s and should not be used directly.
155 // Instead, use `UnsignedParser<T>` with a type to explicitly narrow to.
156 class UnsignedParserBase : public ArgParserBase {
157  protected:
158   UnsignedParserBase(std::string_view name);
159   UnsignedParserBase(std::string_view shortopt, std::string_view longopt);
160 
161   ParseStatus Parse(std::string_view arg0, std::string_view arg1, uint64_t max);
162 };
163 
164 // Argument parser for unsigned integer arguments. These arguments may be flags
165 // or positional arguments.
166 template <typename T, typename std::enable_if_t<std::is_unsigned_v<T>, int> = 0>
167 class UnsignedParser : public UnsignedParserBase {
168  public:
UnsignedParser(std::string_view name)169   UnsignedParser(std::string_view name) : UnsignedParserBase(name) {}
UnsignedParser(std::string_view shortopt,std::string_view longopt)170   UnsignedParser(std::string_view shortopt, std::string_view longopt)
171       : UnsignedParserBase(shortopt, longopt) {}
172 
value()173   T value() const { return static_cast<T>(Get<uint64_t>()); }
174 
set_default(T value)175   UnsignedParser& set_default(T value) {
176     set_initial(static_cast<uint64_t>(value));
177     return *this;
178   }
179 
180   ParseStatus Parse(std::string_view arg0,
181                     std::string_view arg1 = std::string_view()) {
182     return UnsignedParserBase::Parse(arg0, arg1, std::numeric_limits<T>::max());
183   }
184 };
185 
186 // Holds argument parsers of different types.
187 using ArgParserVariant =
188     std::variant<BoolParser, UnsignedParser<uint16_t>, UnsignedParser<size_t>>;
189 
190 // Parses the command line arguments and sets the values of the given `parsers`.
191 Status ParseArgs(Vector<ArgParserVariant>& parsers, int argc, char** argv);
192 
193 // Logs a usage message based on the given `parsers` and the program name given
194 // by `argv0`.
195 void PrintUsage(const Vector<ArgParserVariant>& parsers,
196                 std::string_view argv0);
197 
198 // Attempts to find the parser in `parsers` with the given `name`, and returns
199 // its value if found.
200 std::optional<ArgVariant> GetArg(const Vector<ArgParserVariant>& parsers,
201                                  std::string_view name);
202 
GetArgValue(const ArgVariant & arg,bool * out)203 inline void GetArgValue(const ArgVariant& arg, bool* out) {
204   *out = std::get<bool>(arg);
205 }
206 
207 template <typename T, typename std::enable_if_t<std::is_unsigned_v<T>, int> = 0>
GetArgValue(const ArgVariant & arg,T * out)208 void GetArgValue(const ArgVariant& arg, T* out) {
209   *out = static_cast<T>(std::get<uint64_t>(arg));
210 }
211 
212 // Like `GetArgVariant` above, but extracts the typed value from the variant
213 // into `out`. Returns an error if no parser exists in `parsers` with the given
214 // `name`.
215 template <typename T>
GetArg(const Vector<ArgParserVariant> & parsers,std::string_view name,T * out)216 Status GetArg(const Vector<ArgParserVariant>& parsers,
217               std::string_view name,
218               T* out) {
219   const auto& arg = GetArg(parsers, name);
220   if (!arg.has_value()) {
221     return Status::InvalidArgument();
222   }
223   GetArgValue(*arg, out);
224   return OkStatus();
225 }
226 
227 // Resets the parser with the given name. Returns an error if not found.
228 Status ResetArg(Vector<ArgParserVariant>& parsers, std::string_view name);
229 
230 }  // namespace pw::rpc::fuzz
231