658 lines
20 KiB
C++
658 lines
20 KiB
C++
//----------------------------------------------------------------------------//
|
|
// //
|
|
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
|
|
// and distributed under the MIT License (MIT). //
|
|
// //
|
|
// Copyright (c) Guillaume Blanc //
|
|
// //
|
|
// Permission is hereby granted, free of charge, to any person obtaining a //
|
|
// copy of this software and associated documentation files (the "Software"), //
|
|
// to deal in the Software without restriction, including without limitation //
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
|
|
// and/or sell copies of the Software, and to permit persons to whom the //
|
|
// Software is furnished to do so, subject to the following conditions: //
|
|
// //
|
|
// The above copyright notice and this permission notice shall be included in //
|
|
// all copies or substantial portions of the Software. //
|
|
// //
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
|
|
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
|
|
// DEALINGS IN THE SOFTWARE. //
|
|
// //
|
|
//----------------------------------------------------------------------------//
|
|
|
|
#include "ozz/options/options.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cctype>
|
|
#include <cstring>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <new>
|
|
#include <sstream>
|
|
|
|
namespace ozz {
|
|
namespace options {
|
|
|
|
namespace internal {
|
|
|
|
namespace {
|
|
|
|
// Declares and instantiates a global object that aims to delete the global
|
|
// BaseRegistrer parser. This global BaseRegistrer parser is allocated when
|
|
// options are being registered or when ParseCommandLine is called.
|
|
static class GlobalRegistrer {
|
|
public:
|
|
~GlobalRegistrer() {
|
|
if (parser_) {
|
|
parser_->~Parser();
|
|
parser_ = nullptr;
|
|
}
|
|
}
|
|
|
|
static Parser* Construct() {
|
|
if (!parser_) {
|
|
parser_ = new (placeholder_) Parser;
|
|
}
|
|
return parser_;
|
|
}
|
|
|
|
static Parser* parser() { return parser_; }
|
|
|
|
private:
|
|
// Take advantage of the default initialization (to 0) of file-scope level
|
|
// variables to ensure that registration does not depend on the initialization
|
|
// order of any other variable.
|
|
static Parser* parser_;
|
|
|
|
// The global parser is allocated from the static section in order to avoid
|
|
// any heap allocation before entering the main (and after exiting it also).
|
|
// Uses an array of pointers (void*) in order to ensure a native minimum
|
|
// alignment.
|
|
static void* placeholder_[];
|
|
} g_global_registrer;
|
|
|
|
// Default initialization to 0 only. See parser_ member document above.
|
|
Parser* GlobalRegistrer::parser_;
|
|
|
|
// Compute placeholder_ array size and performs default initialization only.
|
|
// See placeholder_ member document above.
|
|
static const size_t kParserPlaceholerSize =
|
|
(sizeof(Parser) + sizeof(void*) - 1) / sizeof(void*);
|
|
void* GlobalRegistrer::placeholder_[kParserPlaceholerSize];
|
|
} // namespace
|
|
|
|
// Implements Registrer constructor and destructor.
|
|
template <typename _Option>
|
|
Registrer<_Option>::Registrer(const char* _name, const char* _help,
|
|
typename _Option::Type _default, bool _required,
|
|
typename _Option::ValidateFn _fn)
|
|
: _Option(_name, _help, _default, _required, _fn) {
|
|
if (!internal::g_global_registrer.Construct()->RegisterOption(this)) {
|
|
std::cerr << "Failed to register option " << _name << std::endl;
|
|
}
|
|
}
|
|
|
|
template <typename _Option>
|
|
Registrer<_Option>::~Registrer() {
|
|
Parser* parser = internal::g_global_registrer.parser();
|
|
if (parser) {
|
|
parser->UnregisterOption(this);
|
|
}
|
|
}
|
|
|
|
// Explicit instantiation of all supported types of Registrer.
|
|
template class OZZ_OPTIONS_DLL Registrer<TypedOption<bool>>;
|
|
template class OZZ_OPTIONS_DLL Registrer<TypedOption<int>>;
|
|
template class OZZ_OPTIONS_DLL Registrer<TypedOption<float>>;
|
|
template class OZZ_OPTIONS_DLL Registrer<TypedOption<const char*>>;
|
|
} // namespace internal
|
|
|
|
// Construct the parser if no option is registered.
|
|
// This local parser will be deleted automatically. This allows to query the
|
|
// executable path and name, as well as the usage and version strings even if
|
|
// no option is registered.
|
|
ParseResult ParseCommandLine(int _argc, const char* const* _argv,
|
|
const char* _version, const char* _usage) {
|
|
// Need to instantiate a local parser if no option is registered.
|
|
Parser* parser = internal::g_global_registrer.Construct();
|
|
|
|
// Set usage and version strings and parse command line.
|
|
parser->set_usage(_usage);
|
|
parser->set_version(_version);
|
|
return parser->Parse(_argc, _argv);
|
|
}
|
|
|
|
// A nullptr parser means that no option is registered and that ParseCommandLine
|
|
// has not been called.
|
|
ozz::string ParsedExecutablePath() {
|
|
Parser* parser = internal::g_global_registrer.parser();
|
|
if (!parser) {
|
|
return {};
|
|
}
|
|
return parser->executable_path();
|
|
}
|
|
|
|
// See Parser::ExecutableName().
|
|
const char* ParsedExecutableName() {
|
|
Parser* parser = internal::g_global_registrer.parser();
|
|
if (!parser) {
|
|
return "";
|
|
}
|
|
return parser->executable_name();
|
|
}
|
|
|
|
// See Parser::Usage().
|
|
const char* ParsedExecutableUsage() {
|
|
Parser* parser = internal::g_global_registrer.parser();
|
|
if (!parser) {
|
|
return "";
|
|
}
|
|
return parser->usage();
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Portable case insensitive string comparison functions.
|
|
int StrICmp(const char* _left, const char* _right) {
|
|
int l, r;
|
|
do {
|
|
l = static_cast<unsigned char>(std::tolower(*(_left++)));
|
|
r = static_cast<unsigned char>(std::tolower(*(_right++)));
|
|
} while (l && (l == r));
|
|
return l - r;
|
|
}
|
|
|
|
int StrNICmp(const char* _left, const char* _right, size_t _count) {
|
|
if (_count) {
|
|
int l, r;
|
|
do {
|
|
l = static_cast<unsigned char>(std::tolower(*(_left++)));
|
|
r = static_cast<unsigned char>(std::tolower(*(_right++)));
|
|
} while (--_count && l && (l == r));
|
|
return l - r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Returns the first character after _option, or nullptr if option has not been
|
|
// found.
|
|
const char* ParseOption(const char* _argv, const char* _prefix,
|
|
const char* _option) {
|
|
const size_t prefix_len = std::strlen(_prefix);
|
|
const size_t option_len = std::strlen(_option);
|
|
|
|
// All options start with --.
|
|
if (StrNICmp(_argv, _prefix, prefix_len) != 0) {
|
|
return nullptr;
|
|
}
|
|
_argv += prefix_len;
|
|
if (!StrNICmp(_argv, _option, option_len)) {
|
|
return _argv + option_len;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool Parse(const char* _argv, const char* _option, bool* _value) {
|
|
assert(_value && _option && _argv);
|
|
|
|
const char* option_end = ParseOption(_argv, "--", _option);
|
|
if (!option_end) {
|
|
// Test for the explicit option of form --no*.
|
|
option_end = ParseOption(_argv, "--no", _option);
|
|
if (option_end && *option_end == '\0') {
|
|
*_value = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Option _option was found, now checks for a valid value.
|
|
if (*option_end == '\0') {
|
|
// Implicit boolean options have no trailing characters.
|
|
*_value = true;
|
|
return true;
|
|
} else if (*option_end == '=') {
|
|
// Explicit options values are set after the '=' character.
|
|
// Using StrICmp function ensures an exact match, ie no trailing characters.
|
|
for (++option_end; std::isspace(*option_end);
|
|
++option_end) { // Trims spaces.
|
|
}
|
|
const char* true_options[] = {"yes", "true", "1", "t", "y"};
|
|
for (size_t i = 0; i < sizeof(true_options) / sizeof(true_options[0]);
|
|
i++) {
|
|
if (!StrICmp(option_end, true_options[i])) {
|
|
*_value = true;
|
|
return true;
|
|
}
|
|
}
|
|
const char* false_options[] = {"no", "false", "0", "f", "n"};
|
|
for (size_t i = 0; i < sizeof(false_options) / sizeof(false_options[0]);
|
|
++i) {
|
|
if (!StrICmp(option_end, false_options[i])) {
|
|
*_value = false;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false; // Option was not found or is invalid.
|
|
}
|
|
|
|
bool Parse(const char* _argv, const char* _option, float* _value) {
|
|
assert(_value && _option && _argv);
|
|
|
|
const char* option_end = ParseOption(_argv, "--", _option);
|
|
if (option_end && *option_end == '=') {
|
|
for (++option_end; std::isspace(*option_end);
|
|
++option_end) { // Trims spaces.
|
|
}
|
|
char* found;
|
|
double double_value = std::strtod(option_end, &found);
|
|
// No trailing characters are allowed.
|
|
if (found != option_end && *found == '\0') {
|
|
*_value = static_cast<float>(double_value);
|
|
return true;
|
|
}
|
|
}
|
|
return false; // Option was not found or is invalid.
|
|
}
|
|
|
|
bool Parse(const char* _argv, const char* _option, int* _value) {
|
|
assert(_value && _option && _argv);
|
|
|
|
const char* option_end = ParseOption(_argv, "--", _option);
|
|
if (option_end && *option_end == '=') {
|
|
for (++option_end; std::isspace(*option_end);
|
|
++option_end) { // Trims spaces.
|
|
}
|
|
char* found;
|
|
long long_value = std::strtol(option_end, &found, 10);
|
|
// No trailing characters are allowed. If base is 0,
|
|
if (found != option_end && *found == '\0') {
|
|
*_value = static_cast<int>(long_value);
|
|
return true;
|
|
}
|
|
}
|
|
return false; // Option was not found or is invalid.
|
|
}
|
|
|
|
bool Parse(const char* _argv, const char* _option, const char** _value) {
|
|
assert(_value && _option && _argv);
|
|
|
|
const char* option_end = ParseOption(_argv, "--", _option);
|
|
if (option_end && *option_end == '=') {
|
|
for (++option_end; std::isspace(*option_end);
|
|
++option_end) { // Trims spaces.
|
|
}
|
|
*_value = option_end;
|
|
return true;
|
|
}
|
|
return false; // Option was not found or is invalid.
|
|
}
|
|
|
|
// Format option type using template specialization.
|
|
template <typename _Type>
|
|
const char* FormatOptionType();
|
|
|
|
// Specialization of FormatOptionType for all supported types.
|
|
template <>
|
|
const char* FormatOptionType<bool>() {
|
|
return "bool";
|
|
}
|
|
template <>
|
|
const char* FormatOptionType<float>() {
|
|
return "float";
|
|
}
|
|
template <>
|
|
const char* FormatOptionType<int>() {
|
|
return "int";
|
|
}
|
|
template <>
|
|
const char* FormatOptionType<const char*>() {
|
|
return "string";
|
|
}
|
|
|
|
// Validate exclusive options.
|
|
bool ValidateExclusiveOption(const Option& _option, int _argc) {
|
|
if (static_cast<const BoolOption&>(_option).value() && _argc != 1) {
|
|
std::cout << "Option \"" << _option.name()
|
|
<< "\" is an exclusive option. "
|
|
"It must not be used with any other option."
|
|
<< std::endl;
|
|
// This is an exclusive flag.
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
Option::Option(const char* _name, const char* _help, bool _required,
|
|
ValidateFn _validate)
|
|
: name_(_name ? _name : ""),
|
|
help_(_help ? _help : "..."),
|
|
required_(_required),
|
|
parsed_(false),
|
|
validate_(_validate) {}
|
|
|
|
Option::~Option() {}
|
|
|
|
bool Option::Validate(int _argc) {
|
|
if (validate_) {
|
|
return (*validate_)(*this, _argc);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Option::Parse(const char* _argv) {
|
|
if (ParseImpl(_argv) && !parsed_) { // Fails if argument's already specified.
|
|
parsed_ = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Option::RestoreDefault() {
|
|
parsed_ = false;
|
|
RestoreDefaultImpl(); // Restores actual value.
|
|
}
|
|
|
|
template <typename _Type>
|
|
bool TypedOption<_Type>::ParseImpl(const char* _argv) {
|
|
return ozz::options::Parse(_argv, name(), &value_);
|
|
}
|
|
|
|
template <typename _Type>
|
|
ozz::string TypedOption<_Type>::FormatDefault() const {
|
|
std::stringstream str;
|
|
str << "\"" << std::boolalpha << default_ << "\"";
|
|
return str.str().c_str();
|
|
}
|
|
|
|
template <typename _Type>
|
|
const char* TypedOption<_Type>::FormatType() const {
|
|
return ozz::options::FormatOptionType<_Type>();
|
|
}
|
|
|
|
// Explicit instantiation of all supported types of TypedOption.
|
|
template class TypedOption<bool>;
|
|
template class TypedOption<int>;
|
|
template class TypedOption<float>;
|
|
template class TypedOption<const char*>;
|
|
|
|
Parser::Parser()
|
|
: options_count_(0),
|
|
builtin_options_count_(0),
|
|
executable_path_begin_(""),
|
|
executable_path_end_(executable_path_begin_ + 1),
|
|
executable_name_(""),
|
|
version_(nullptr),
|
|
usage_(nullptr),
|
|
builtin_version_("version", "Displays application version", false, false,
|
|
&ValidateExclusiveOption),
|
|
builtin_help_("help", "Displays help", false, false,
|
|
&ValidateExclusiveOption) {
|
|
// Set default values.
|
|
set_version(nullptr);
|
|
set_usage(nullptr);
|
|
// Registers built-in options.
|
|
RegisterOption(&builtin_version_);
|
|
RegisterOption(&builtin_help_);
|
|
builtin_options_count_ = options_count_;
|
|
}
|
|
|
|
Parser::~Parser() {
|
|
UnregisterOption(&builtin_version_);
|
|
UnregisterOption(&builtin_help_);
|
|
}
|
|
|
|
ParseResult Parser::Parse(int _argc, const char* const* _argv) {
|
|
if (_argc < 1 || !_argv) {
|
|
return kExitFailure;
|
|
}
|
|
|
|
// Select the left most '/' or '\\' separator.
|
|
const char* path_end =
|
|
std::max(std::strrchr(_argv[0], '/'), strrchr(_argv[0], '\\'));
|
|
if (path_end) {
|
|
++path_end; // Includes the last separator.
|
|
executable_path_begin_ = _argv[0];
|
|
executable_path_end_ = path_end;
|
|
executable_name_ = path_end;
|
|
} else {
|
|
executable_path_begin_ = "";
|
|
executable_path_end_ = executable_path_begin_ + 1;
|
|
executable_name_ = _argv[0];
|
|
}
|
|
|
|
// The first argument is skipped because it is the program path.
|
|
++_argv;
|
|
--_argc;
|
|
|
|
// Hides all arguments after a "--" argument, substitutes argc to argc_trunc.
|
|
int argc_trunc = 0;
|
|
for (; argc_trunc < _argc && std::strcmp(_argv[argc_trunc], "--") != 0;
|
|
++argc_trunc) {
|
|
}
|
|
|
|
// Restores built-in options to their default value in case parsing in done
|
|
// multiple times.
|
|
for (int i = 0; i < options_count_; ++i) {
|
|
options_[i]->RestoreDefault();
|
|
}
|
|
|
|
// Iterates all arguments and all options.
|
|
ParseResult result = kSuccess;
|
|
for (int i = 0; i < argc_trunc; ++i) {
|
|
const char* argv = _argv[i];
|
|
|
|
// Empty arguments aren't consider invalid.
|
|
if (*argv == 0) {
|
|
continue;
|
|
}
|
|
|
|
int j = 0;
|
|
for (; j < options_count_; ++j) {
|
|
if (options_[j]->Parse(argv)) {
|
|
break; // Also breaks if argument is duplicated.
|
|
}
|
|
}
|
|
// An invalid (or duplicated) command line argument is a fatal failure.
|
|
if (j == options_count_) {
|
|
std::cout << "Invalid command line argument:\"" << argv << "\"."
|
|
<< std::endl;
|
|
result = kExitFailure;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Validate build-in options first.
|
|
// They need to be validated and tested first, as they have priority over
|
|
// others, even required once.
|
|
if (!builtin_help_.Validate(argc_trunc) ||
|
|
!builtin_version_.Validate(argc_trunc)) {
|
|
result = kExitFailure;
|
|
}
|
|
|
|
// Display built-in help.
|
|
if (result == kSuccess && builtin_help_) {
|
|
Help();
|
|
result = kExitSuccess;
|
|
}
|
|
|
|
// Display built-in version.
|
|
if (result == kSuccess && builtin_version_) {
|
|
std::cout << "version " << version() << std::endl;
|
|
result = kExitSuccess;
|
|
}
|
|
|
|
// Ensures all required options were specified in the command line.
|
|
if (result == kSuccess) {
|
|
for (int i = 0; i < options_count_; ++i) {
|
|
if (!options_[i]->statisfied()) {
|
|
std::cout << "Required option \"" << options_[i]->name()
|
|
<< "\" is not specified." << std::endl;
|
|
result = kExitFailure;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validates all options.
|
|
if (result == kSuccess) {
|
|
for (int i = 0; i < options_count_; ++i) {
|
|
if (!options_[i]->Validate(argc_trunc)) {
|
|
result = kExitFailure;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Also displays help if an error occurred.
|
|
if (result == kExitFailure) {
|
|
Help();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Parser::Help() {
|
|
std::cout << std::endl;
|
|
std::cout << executable_name() << " version " << version() << std::endl;
|
|
std::cout << usage() << std::endl;
|
|
std::cout << std::endl;
|
|
|
|
// Displays usage line.
|
|
std::cout << "Usage:" << std::endl;
|
|
std::cout << executable_name();
|
|
for (int i = 0; i < options_count_; ++i) {
|
|
const Option& option = *options_[i];
|
|
std::cout << ' ';
|
|
if (option.required()) {
|
|
std::cout << '[';
|
|
}
|
|
std::cout << "--" << option.name();
|
|
if (option.required()) {
|
|
std::cout << ']';
|
|
}
|
|
}
|
|
std::cout << std::endl;
|
|
|
|
// Displays option details.
|
|
std::cout << "\nWhere:" << std::endl;
|
|
for (int i = 0; i < options_count_; ++i) {
|
|
const Option& option = *options_[i];
|
|
const std::string option_str =
|
|
std::string(" --") + option.name() + "=<" + option.FormatType() + ">";
|
|
std::cout << std::setiosflags(std::ios::left) << std::setw(28) << option_str
|
|
<< std::resetiosflags(std::ios::left) << option.help()
|
|
<< "(default is " << option.FormatDefault() << ")" << std::endl;
|
|
}
|
|
|
|
const char* how_to =
|
|
"\n"
|
|
"Syntax:\n"
|
|
"To set an option from the command line use the form --option=value for\n"
|
|
"non-boolean options, and --option/--nooption for booleans.\n"
|
|
"For example, \"foo --var=46\" will set \"var\" variable to 46.\n"
|
|
"If \"var\" type is not compatible with the specified argument (in this\n"
|
|
"case not an integer, a float or a string), then this help message\n"
|
|
"is displayed and application exits.\n"
|
|
"\n"
|
|
"Boolean options can be set using different syntax:\n"
|
|
"- to set a boolean option to true: \"--var\", \"--var=true\", "
|
|
"\"--var=t\","
|
|
" \"--var=yes\", \"--var=y\", \"--var=1\".\n"
|
|
"- to set a boolean option to false: \"--novar\", \"--var=false\","
|
|
" \"--var=f\", \"--var=no\", \"--var=n\", \"--var=0\".\n"
|
|
"Consistently using single-form --option/--nooption is recommended "
|
|
"though.";
|
|
std::cout << how_to << std::endl;
|
|
}
|
|
|
|
namespace {
|
|
// Sort required options first, and then based on their names.
|
|
bool sort_options(Option* _left, Option* _right) {
|
|
return (_left->required() && !_right->required()) ||
|
|
(_left->required() == _right->required() &&
|
|
std::strcmp(_left->name(), _right->name()) < 0);
|
|
}
|
|
} // namespace
|
|
|
|
bool Parser::RegisterOption(Option* _option) {
|
|
if (!_option) {
|
|
return false;
|
|
}
|
|
if (options_count_ == sizeof(options_) / sizeof(options_[0])) {
|
|
return false;
|
|
}
|
|
|
|
// Tests for duplicate options.
|
|
if (std::count(options_, options_end(), _option) != 0) {
|
|
return false;
|
|
}
|
|
|
|
// Empty (or nullptr) names aren't allowed.
|
|
if (_option->name()[0] == '\0') {
|
|
std::cerr << "Empty (or nullptr) names aren't allowed." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Test for duplicate options' name.
|
|
for (int i = 0; i < options_count_; ++i) {
|
|
if (StrICmp(options_[i]->name(), _option->name()) == 0) {
|
|
std::cerr << "Option name:\"" << _option->name()
|
|
<< "\" already registered." << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Adds the option and maintains lexical order.
|
|
options_[options_count_++] = _option;
|
|
std::inplace_merge(options_, options_end() - 1, options_end(), &sort_options);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Parser::UnregisterOption(Option* _option) {
|
|
if (!_option) {
|
|
return false;
|
|
}
|
|
|
|
// Finds and removes _option from the collection.
|
|
Option** it = std::remove(options_, options_end(), _option);
|
|
if (it != options_end()) {
|
|
return --options_count_ == builtin_options_count_;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int Parser::max_options() const {
|
|
return sizeof(options_) / sizeof(options_[0]) - builtin_options_count_;
|
|
}
|
|
|
|
void Parser::set_usage(const char* _usage) {
|
|
usage_ = _usage ? _usage : "unspecified usage";
|
|
}
|
|
|
|
void Parser::set_version(const char* _version) {
|
|
version_ = _version ? _version : "unspecified version";
|
|
}
|
|
|
|
const char* Parser::usage() const { return usage_; }
|
|
|
|
const char* Parser::version() const { return version_; }
|
|
|
|
ozz::string Parser::executable_path() const {
|
|
return ozz::string(executable_path_begin_, executable_path_end_);
|
|
}
|
|
|
|
const char* Parser::executable_name() const { return executable_name_; }
|
|
} // namespace options
|
|
} // namespace ozz
|