322 lines
11 KiB
C++
322 lines
11 KiB
C++
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
#ifndef TOOLS_CMDLINE_H_
|
|
#define TOOLS_CMDLINE_H_
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "lib/jxl/base/status.h"
|
|
|
|
namespace jpegxl {
|
|
namespace tools {
|
|
|
|
class CommandLineParser {
|
|
public:
|
|
typedef size_t OptionId;
|
|
|
|
// An abstract class for defining command line options.
|
|
class CmdOptionInterface {
|
|
public:
|
|
CmdOptionInterface() = default;
|
|
virtual ~CmdOptionInterface() = default;
|
|
|
|
// Return a string with the option name or available flags.
|
|
virtual std::string help_flags() const = 0;
|
|
|
|
// Return the help string if any, or nullptr if no help string.
|
|
virtual const char* help_text() const = 0;
|
|
|
|
// Return the verbosity level for this option
|
|
virtual int verbosity_level() const = 0;
|
|
|
|
// Return whether the option was passed.
|
|
virtual bool matched() const = 0;
|
|
|
|
// Returns whether this option matches the passed command line argument.
|
|
virtual bool Match(const char* arg, bool parse_options) const = 0;
|
|
|
|
// Parses the option. The passed i points to the argument with the flag
|
|
// that matches either the short or the long name.
|
|
virtual bool Parse(int argc, const char* argv[], int* i) = 0;
|
|
|
|
// Returns whether the option is positional, and therefore will be shown
|
|
// in the first command line representation of the help output.
|
|
virtual bool positional() const = 0;
|
|
|
|
// Returns whether the option should be displayed as required in the help
|
|
// output. No effect on validation.
|
|
virtual bool required() const = 0;
|
|
};
|
|
|
|
// Add a positional argument. Returns the id of the added option or
|
|
// kOptionError on error.
|
|
// The "required" flag indicates whether the parameter is mandatory or
|
|
// optional, but is only used for how it is displayed in the command line
|
|
// help.
|
|
OptionId AddPositionalOption(const char* name, bool required,
|
|
const char* help_text, const char** storage,
|
|
int verbosity_level = 0) {
|
|
options_.emplace_back(new CmdOptionPositional(name, help_text, storage,
|
|
verbosity_level, required));
|
|
return options_.size() - 1;
|
|
}
|
|
|
|
// Add an option with a value of type T. The option can be passed as
|
|
// '-s <value>' or '--long value' or '--long=value'. The CommandLineParser
|
|
// parser will call the function parser with the string pointing to '<value>'
|
|
// in either case. Returns the id of the added option or kOptionError on
|
|
// error.
|
|
template <typename T>
|
|
OptionId AddOptionValue(char short_name, const char* long_name,
|
|
const char* metavar, const char* help_text,
|
|
T* storage, bool(parser)(const char*, T*),
|
|
int verbosity_level = 0) {
|
|
options_.emplace_back(new CmdOptionFlag<T>(short_name, long_name, metavar,
|
|
help_text, storage, parser,
|
|
verbosity_level));
|
|
return options_.size() - 1;
|
|
}
|
|
|
|
// Add a flag without a value. Returns the id of the added option or
|
|
// kOptionError on error.
|
|
template <typename T>
|
|
OptionId AddOptionFlag(char short_name, const char* long_name,
|
|
const char* help_text, T* storage, bool(parser)(T*),
|
|
int verbosity_level = 0) {
|
|
options_.emplace_back(new CmdOptionFlag<T>(
|
|
short_name, long_name, help_text, storage, parser, verbosity_level));
|
|
return options_.size() - 1;
|
|
}
|
|
|
|
const CmdOptionInterface* GetOption(OptionId id) const {
|
|
JXL_ASSERT(id < options_.size());
|
|
return options_[id].get();
|
|
}
|
|
|
|
// Print the help message to stdout.
|
|
void PrintHelp() const;
|
|
|
|
// Whether a help flag was specified
|
|
bool HelpFlagPassed() const { return help_; }
|
|
|
|
int verbosity = 0;
|
|
|
|
// Parse the command line.
|
|
bool Parse(int argc, const char* argv[]);
|
|
|
|
// Return the remaining positional args
|
|
std::vector<const char*> PositionalArgs() const;
|
|
|
|
private:
|
|
// A positional argument.
|
|
class CmdOptionPositional : public CmdOptionInterface {
|
|
public:
|
|
CmdOptionPositional(const char* name, const char* help_text,
|
|
const char** storage, int verbosity_level,
|
|
bool required)
|
|
: name_(name),
|
|
help_text_(help_text),
|
|
storage_(storage),
|
|
verbosity_level_(verbosity_level),
|
|
required_(required) {}
|
|
|
|
std::string help_flags() const override { return name_; }
|
|
const char* help_text() const override { return help_text_; }
|
|
int verbosity_level() const override { return verbosity_level_; }
|
|
bool matched() const override { return matched_; }
|
|
|
|
// Only match non-flag values. This means that you can't pass '-foo' as a
|
|
// positional argument, but it helps with detecting when passed a flag with
|
|
// a typo. After '--', option matching is disabled so positional arguments
|
|
// starting with '-' can be used.
|
|
bool Match(const char* arg, bool parse_options) const override {
|
|
return !matched_ && (!parse_options || arg[0] != '-');
|
|
}
|
|
|
|
bool Parse(const int argc, const char* argv[], int* i) override {
|
|
*storage_ = argv[*i];
|
|
(*i)++;
|
|
matched_ = true;
|
|
return true;
|
|
}
|
|
|
|
bool positional() const override { return true; }
|
|
|
|
bool required() const override { return required_; }
|
|
|
|
private:
|
|
const char* name_;
|
|
const char* help_text_;
|
|
const char** storage_;
|
|
const int verbosity_level_;
|
|
const bool required_;
|
|
|
|
bool matched_{false};
|
|
};
|
|
|
|
// A class for handling an option flag like '-v' or '--foo=bar'.
|
|
template <typename T>
|
|
class CmdOptionFlag : public CmdOptionInterface {
|
|
public:
|
|
// Construct a flag that doesn't take any value, for example '-v' or
|
|
// '--long'. Passing a value to it raises an error.
|
|
CmdOptionFlag(char short_name, const char* long_name, const char* help_text,
|
|
T* storage, bool(parser)(T*), int verbosity_level)
|
|
: short_name_(short_name),
|
|
long_name_(long_name),
|
|
long_name_len_(long_name ? strlen(long_name) : 0),
|
|
metavar_(nullptr),
|
|
help_text_(help_text),
|
|
storage_(storage),
|
|
verbosity_level_(verbosity_level) {
|
|
parser_.parser_no_value_ = parser;
|
|
}
|
|
|
|
// Construct a flag that expects a value to be passed.
|
|
CmdOptionFlag(char short_name, const char* long_name, const char* metavar,
|
|
const char* help_text, T* storage,
|
|
bool(parser)(const char* arg, T*), int verbosity_level)
|
|
: short_name_(short_name),
|
|
long_name_(long_name),
|
|
long_name_len_(long_name ? strlen(long_name) : 0),
|
|
metavar_(metavar ? metavar : ""),
|
|
help_text_(help_text),
|
|
storage_(storage),
|
|
verbosity_level_(verbosity_level) {
|
|
parser_.parser_with_arg_ = parser;
|
|
}
|
|
|
|
std::string help_flags() const override {
|
|
std::string ret;
|
|
if (short_name_) {
|
|
ret += std::string("-") + short_name_;
|
|
if (metavar_) ret += std::string(" ") + metavar_;
|
|
if (long_name_) ret += ", ";
|
|
}
|
|
if (long_name_) {
|
|
ret += std::string("--") + long_name_;
|
|
if (metavar_) ret += std::string("=") + metavar_;
|
|
}
|
|
return ret;
|
|
}
|
|
const char* help_text() const override { return help_text_; }
|
|
int verbosity_level() const override { return verbosity_level_; }
|
|
bool matched() const override { return matched_; }
|
|
|
|
bool Match(const char* arg, bool parse_options) const override {
|
|
return parse_options && (MatchShort(arg) || MatchLong(arg));
|
|
}
|
|
|
|
bool Parse(const int argc, const char* argv[], int* i) override {
|
|
matched_ = true;
|
|
if (MatchLong(argv[*i])) {
|
|
const char* arg = argv[*i] + 2 + long_name_len_;
|
|
if (arg[0] == '=') {
|
|
if (metavar_) {
|
|
// Passed '--long_name=...'.
|
|
(*i)++;
|
|
// Skip over the '=' on the LongMatch.
|
|
arg += 1;
|
|
return (*parser_.parser_with_arg_)(arg, storage_);
|
|
} else {
|
|
fprintf(stderr, "--%s didn't expect any argument passed to it.\n",
|
|
argv[*i]);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// In any other case, it passed a -s or --long_name
|
|
(*i)++;
|
|
if (metavar_) {
|
|
if (argc <= *i) {
|
|
fprintf(stderr, "--%s expected an argument but none passed.\n",
|
|
argv[*i - 1]);
|
|
return false;
|
|
}
|
|
return (*parser_.parser_with_arg_)(argv[(*i)++], storage_);
|
|
} else {
|
|
return (*parser_.parser_no_value_)(storage_);
|
|
}
|
|
}
|
|
|
|
bool positional() const override { return false; }
|
|
|
|
bool required() const override {
|
|
// Only used for help display of positional arguments.
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
// Returns whether arg matches the short_name flag of this option.
|
|
bool MatchShort(const char* arg) const {
|
|
if (!short_name_ || arg[0] != '-') return false;
|
|
return arg[1] == short_name_ && arg[2] == 0;
|
|
}
|
|
|
|
// Returns whether arg matches the long_name flag of this option,
|
|
// potentially with an argument passed to it.
|
|
bool MatchLong(const char* arg) const {
|
|
if (!long_name_ || arg[0] != '-' || arg[1] != '-') return false;
|
|
arg += 2; // Skips the '--'
|
|
if (strncmp(long_name_, arg, long_name_len_) != 0) return false;
|
|
arg += long_name_len_;
|
|
// Allow "--long_name=foo" and "--long_name" as long matches.
|
|
return arg[0] == 0 || arg[0] == '=';
|
|
}
|
|
|
|
// A short option passed as '-X' where X is the char. A value of 0 means
|
|
// no short option.
|
|
const char short_name_;
|
|
|
|
// A long option name passed as '--long' where 'long' is the name of the
|
|
// option.
|
|
const char* long_name_;
|
|
size_t long_name_len_;
|
|
|
|
// The text to display when referring to the value passed to this flag, for
|
|
// example "N" in the flag '--value N'. If null, this flag accepts no value
|
|
// and therefore no value must be passed.
|
|
const char* metavar_;
|
|
|
|
// The help string for this flag.
|
|
const char* help_text_;
|
|
|
|
// The pointer to the storage of this flag used when parsing.
|
|
T* storage_;
|
|
|
|
// At which verbosity level do we show this option?
|
|
int verbosity_level_;
|
|
|
|
// The function to use to parse the value when matched. The function used is
|
|
// parser_with_arg_ when metavar_ is not null (and the value string will be
|
|
// used) or parser_no_value_ when metavar_ is null.
|
|
union {
|
|
bool (*parser_with_arg_)(const char*, T*);
|
|
bool (*parser_no_value_)(T*);
|
|
} parser_;
|
|
|
|
// Whether this flag was matched.
|
|
bool matched_{false};
|
|
};
|
|
|
|
const char* program_name_{nullptr};
|
|
|
|
std::vector<std::unique_ptr<CmdOptionInterface>> options_;
|
|
|
|
// If true, help argument was given, so print help to stdout rather than
|
|
// stderr.
|
|
bool help_ = false;
|
|
};
|
|
|
|
} // namespace tools
|
|
} // namespace jpegxl
|
|
|
|
#endif // TOOLS_CMDLINE_H_
|