libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

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_