cmdline.h (14402B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #ifndef TOOLS_CMDLINE_H_ 7 #define TOOLS_CMDLINE_H_ 8 9 #include <stdarg.h> 10 #include <stdio.h> 11 #include <string.h> 12 13 #include <cstdint> 14 #include <memory> 15 #include <string> 16 #include <vector> 17 18 namespace jpegxl { 19 namespace tools { 20 21 class CommandLineParser { 22 public: 23 typedef int OptionId; 24 25 // An abstract class for defining command line options. 26 class CmdOptionInterface { 27 public: 28 CmdOptionInterface() = default; 29 virtual ~CmdOptionInterface() = default; 30 31 // Return a string with the option name or available flags. 32 virtual std::string help_flags() const = 0; 33 34 // Return the help string if any, or nullptr if no help string. 35 virtual const char* help_text() const = 0; 36 37 // Return the verbosity level for this option 38 virtual int verbosity_level() const = 0; 39 40 // Return whether the option was passed. 41 virtual bool matched() const = 0; 42 43 // Returns whether this option matches the passed command line argument. 44 virtual bool Match(const char* arg, bool parse_options) const = 0; 45 46 // Parses the option. The passed i points to the argument with the flag 47 // that matches either the short or the long name. 48 virtual bool Parse(int argc, const char* argv[], int* i) = 0; 49 50 // Returns whether the option is positional, and therefore will be shown 51 // in the first command line representation of the help output. 52 virtual bool positional() const = 0; 53 54 // Returns whether the option should be displayed as required in the help 55 // output. No effect on validation. 56 virtual bool required() const = 0; 57 58 // Returns whether the option is not really an option but just help text 59 virtual bool help_only() const = 0; 60 }; 61 62 // Add help text 63 void AddHelpText(const char* help_text, int verbosity_level = 0) { 64 options_.emplace_back(new CmdHelpText(help_text, verbosity_level)); 65 } 66 67 // Add a positional argument. Returns the id of the added option or 68 // kOptionError on error. 69 // The "required" flag indicates whether the parameter is mandatory or 70 // optional, but is only used for how it is displayed in the command line 71 // help. 72 OptionId AddPositionalOption(const char* name, bool required, 73 const std::string& help_text, 74 const char** storage, int verbosity_level = 0) { 75 options_.emplace_back(new CmdOptionPositional(name, help_text, storage, 76 verbosity_level, required)); 77 return options_.size() - 1; 78 } 79 80 // Add an option with a value of type T. The option can be passed as 81 // '-s <value>' or '--long value' or '--long=value'. The CommandLineParser 82 // parser will call the function parser with the string pointing to '<value>' 83 // in either case. Returns the id of the added option or kOptionError on 84 // error. 85 template <typename T> 86 OptionId AddOptionValue(char short_name, const char* long_name, 87 const char* metavar, const char* help_text, 88 T* storage, bool(parser)(const char*, T*), 89 int verbosity_level = 0) { 90 options_.emplace_back(new CmdOptionFlag<T>(short_name, long_name, metavar, 91 help_text, storage, parser, 92 verbosity_level)); 93 return options_.size() - 1; 94 } 95 96 // Add a flag without a value. Returns the id of the added option or 97 // kOptionError on error. 98 template <typename T> 99 OptionId AddOptionFlag(char short_name, const char* long_name, 100 const char* help_text, T* storage, bool(parser)(T*), 101 int verbosity_level = 0) { 102 options_.emplace_back(new CmdOptionFlag<T>( 103 short_name, long_name, help_text, storage, parser, verbosity_level)); 104 return options_.size() - 1; 105 } 106 107 const CmdOptionInterface* GetOption(OptionId id) const { 108 return options_[id].get(); 109 } 110 111 // Print the help message to stdout. 112 void PrintHelp() const; 113 114 // Whether a help flag was specified 115 bool HelpFlagPassed() const { return help_; } 116 117 int verbosity = 0; 118 119 // Parse the command line. 120 bool Parse(int argc, const char* argv[]); 121 122 // Return the remaining positional args 123 std::vector<const char*> PositionalArgs() const; 124 125 // Conditionally print a message to stderr 126 void VerbosePrintf(int min_verbosity, const char* format, ...) const; 127 128 private: 129 // Help text only. 130 class CmdHelpText : public CmdOptionInterface { 131 public: 132 CmdHelpText(const char* help_text, int verbosity_level) 133 : help_text_(help_text), verbosity_level_(verbosity_level) {} 134 135 std::string help_flags() const override { return ""; } 136 const char* help_text() const override { return help_text_; } 137 int verbosity_level() const override { return verbosity_level_; } 138 bool matched() const override { return false; } 139 140 bool Match(const char* arg, bool parse_options) const override { 141 return false; 142 } 143 144 bool Parse(const int argc, const char* argv[], int* i) override { 145 return true; 146 } 147 148 bool positional() const override { return false; } 149 150 bool required() const override { return false; } 151 152 bool help_only() const override { return true; } 153 154 private: 155 const char* help_text_; 156 const int verbosity_level_; 157 }; 158 159 // A positional argument. 160 class CmdOptionPositional : public CmdOptionInterface { 161 public: 162 CmdOptionPositional(const char* name, const std::string& help_text, 163 const char** storage, int verbosity_level, 164 bool required) 165 : name_(name), 166 help_text_(help_text), 167 storage_(storage), 168 verbosity_level_(verbosity_level), 169 required_(required) {} 170 171 std::string help_flags() const override { return name_; } 172 const char* help_text() const override { return help_text_.c_str(); } 173 int verbosity_level() const override { return verbosity_level_; } 174 bool matched() const override { return matched_; } 175 176 // Only match non-flag values. This means that you can't pass '-foo' as a 177 // positional argument, but it helps with detecting when passed a flag with 178 // a typo. After '--', option matching is disabled so positional arguments 179 // starting with '-' can be used. 180 bool Match(const char* arg, bool parse_options) const override { 181 return !matched_ && (!parse_options || arg[0] != '-'); 182 } 183 184 bool Parse(const int argc, const char* argv[], int* i) override { 185 *storage_ = argv[*i]; 186 (*i)++; 187 matched_ = true; 188 return true; 189 } 190 191 bool positional() const override { return true; } 192 193 bool required() const override { return required_; } 194 195 bool help_only() const override { return false; } 196 197 private: 198 const char* name_; 199 const std::string help_text_; 200 const char** storage_; 201 const int verbosity_level_; 202 const bool required_; 203 204 bool matched_{false}; 205 }; 206 207 // A class for handling an option flag like '-v' or '--foo=bar'. 208 template <typename T> 209 class CmdOptionFlag : public CmdOptionInterface { 210 public: 211 // Construct a flag that doesn't take any value, for example '-v' or 212 // '--long'. Passing a value to it raises an error. 213 CmdOptionFlag(char short_name, const char* long_name, const char* help_text, 214 T* storage, bool(parser)(T*), int verbosity_level) 215 : short_name_(short_name), 216 long_name_(long_name), 217 long_name_len_(long_name ? strlen(long_name) : 0), 218 metavar_(nullptr), 219 help_text_(help_text), 220 storage_(storage), 221 verbosity_level_(verbosity_level) { 222 parser_.parser_no_value_ = parser; 223 } 224 225 // Construct a flag that expects a value to be passed. 226 CmdOptionFlag(char short_name, const char* long_name, const char* metavar, 227 const char* help_text, T* storage, 228 bool(parser)(const char* arg, T*), int verbosity_level) 229 : short_name_(short_name), 230 long_name_(long_name), 231 long_name_len_(long_name ? strlen(long_name) : 0), 232 metavar_(metavar ? metavar : ""), 233 help_text_(help_text), 234 storage_(storage), 235 verbosity_level_(verbosity_level) { 236 parser_.parser_with_arg_ = parser; 237 } 238 239 std::string help_flags() const override { 240 std::string ret; 241 if (short_name_) { 242 ret += std::string("-") + short_name_; 243 if (metavar_) ret += std::string(" ") + metavar_; 244 if (long_name_) ret += ", "; 245 } 246 if (long_name_) { 247 ret += std::string("--") + long_name_; 248 if (metavar_) ret += std::string("=") + metavar_; 249 } 250 return ret; 251 } 252 const char* help_text() const override { return help_text_; } 253 int verbosity_level() const override { return verbosity_level_; } 254 bool matched() const override { return matched_; } 255 256 bool Match(const char* arg, bool parse_options) const override { 257 return parse_options && (MatchShort(arg) || MatchLong(arg)); 258 } 259 260 bool Parse(const int argc, const char* argv[], int* i) override { 261 matched_ = true; 262 if (MatchLong(argv[*i])) { 263 const char* arg = argv[*i] + 2 + long_name_len_; 264 if (arg[0] == '=') { 265 if (metavar_) { 266 // Passed '--long_name=...'. 267 (*i)++; 268 // Skip over the '=' on the LongMatch. 269 arg += 1; 270 return (*parser_.parser_with_arg_)(arg, storage_); 271 } else { 272 fprintf(stderr, "--%s didn't expect any argument passed to it.\n", 273 argv[*i]); 274 return false; 275 } 276 } 277 } 278 // In any other case, it passed a -s or --long_name 279 (*i)++; 280 if (metavar_) { 281 if (argc <= *i) { 282 fprintf(stderr, "--%s expected an argument but none passed.\n", 283 argv[*i - 1]); 284 return false; 285 } 286 return (*parser_.parser_with_arg_)(argv[(*i)++], storage_); 287 } else { 288 return (*parser_.parser_no_value_)(storage_); 289 } 290 } 291 292 bool positional() const override { return false; } 293 294 bool required() const override { 295 // Only used for help display of positional arguments. 296 return false; 297 } 298 299 bool help_only() const override { return false; } 300 301 private: 302 // Returns whether arg matches the short_name flag of this option. 303 bool MatchShort(const char* arg) const { 304 if (!short_name_ || arg[0] != '-') return false; 305 return arg[1] == short_name_ && arg[2] == 0; 306 } 307 308 // Returns whether arg matches the long_name flag of this option, 309 // potentially with an argument passed to it. 310 bool MatchLong(const char* arg) const { 311 if (!long_name_ || arg[0] != '-' || arg[1] != '-') return false; 312 arg += 2; // Skips the '--' 313 if (strncmp(long_name_, arg, long_name_len_) != 0) return false; 314 arg += long_name_len_; 315 // Allow "--long_name=foo" and "--long_name" as long matches. 316 return arg[0] == 0 || arg[0] == '='; 317 } 318 319 // A short option passed as '-X' where X is the char. A value of 0 means 320 // no short option. 321 const char short_name_; 322 323 // A long option name passed as '--long' where 'long' is the name of the 324 // option. 325 const char* long_name_; 326 size_t long_name_len_; 327 328 // The text to display when referring to the value passed to this flag, for 329 // example "N" in the flag '--value N'. If null, this flag accepts no value 330 // and therefore no value must be passed. 331 const char* metavar_; 332 333 // The help string for this flag. 334 const char* help_text_; 335 336 // The pointer to the storage of this flag used when parsing. 337 T* storage_; 338 339 // At which verbosity level do we show this option? 340 int verbosity_level_; 341 342 // The function to use to parse the value when matched. The function used is 343 // parser_with_arg_ when metavar_ is not null (and the value string will be 344 // used) or parser_no_value_ when metavar_ is null. 345 union { 346 bool (*parser_with_arg_)(const char*, T*); 347 bool (*parser_no_value_)(T*); 348 } parser_; 349 350 // Whether this flag was matched. 351 bool matched_{false}; 352 }; 353 354 const char* program_name_{nullptr}; 355 356 std::vector<std::unique_ptr<CmdOptionInterface>> options_; 357 358 // If true, help argument was given, so print help to stdout rather than 359 // stderr. 360 bool help_ = false; 361 }; 362 363 // 364 // Common parsers for AddOptionValue and AddOptionFlag 365 // 366 367 static inline bool ParseSigned(const char* arg, int* out) { 368 char* end; 369 *out = static_cast<int>(strtol(arg, &end, 0)); 370 if (end[0] != '\0') { 371 fprintf(stderr, "Unable to interpret as signed integer: %s.\n", arg); 372 return false; 373 } 374 return true; 375 } 376 377 static inline bool ParseUnsigned(const char* arg, size_t* out) { 378 char* end; 379 *out = static_cast<size_t>(strtoull(arg, &end, 0)); 380 if (end[0] != '\0') { 381 fprintf(stderr, "Unable to interpret as unsigned integer: %s.\n", arg); 382 return false; 383 } 384 return true; 385 } 386 387 static inline bool ParseInt64(const char* arg, int64_t* out) { 388 char* end; 389 *out = strtol(arg, &end, 0); 390 if (end[0] != '\0') { 391 fprintf(stderr, "Unable to interpret as signed integer: %s.\n", arg); 392 return false; 393 } 394 return true; 395 } 396 397 static inline bool ParseUint32(const char* arg, uint32_t* out) { 398 size_t value = 0; 399 bool ret = ParseUnsigned(arg, &value); 400 if (ret) *out = value; 401 return ret; 402 } 403 404 static inline bool ParseFloat(const char* arg, float* out) { 405 char* end; 406 *out = static_cast<float>(strtod(arg, &end)); 407 if (end[0] != '\0') { 408 fprintf(stderr, "Unable to interpret as float: %s.\n", arg); 409 return false; 410 } 411 return true; 412 } 413 414 static inline bool ParseDouble(const char* arg, double* out) { 415 char* end; 416 *out = static_cast<double>(strtod(arg, &end)); 417 if (end[0] != '\0') { 418 fprintf(stderr, "Unable to interpret as double: %s.\n", arg); 419 return false; 420 } 421 return true; 422 } 423 424 static inline bool ParseString(const char* arg, std::string* out) { 425 out->assign(arg); 426 return true; 427 } 428 429 static inline bool SetBooleanTrue(bool* out) { 430 *out = true; 431 return true; 432 } 433 434 static inline bool SetBooleanFalse(bool* out) { 435 *out = false; 436 return true; 437 } 438 439 } // namespace tools 440 } // namespace jpegxl 441 442 #endif // TOOLS_CMDLINE_H_