2023-03-26 13:28:12 +02:00

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