//----------------------------------------------------------------------------// // // // 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 #include #include #include #include #include #include #include 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 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 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>; template class OZZ_OPTIONS_DLL Registrer>; template class OZZ_OPTIONS_DLL Registrer>; template class OZZ_OPTIONS_DLL Registrer>; } // 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(std::tolower(*(_left++))); r = static_cast(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(std::tolower(*(_left++))); r = static_cast(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(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(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 const char* FormatOptionType(); // Specialization of FormatOptionType for all supported types. template <> const char* FormatOptionType() { return "bool"; } template <> const char* FormatOptionType() { return "float"; } template <> const char* FormatOptionType() { return "int"; } template <> const char* FormatOptionType() { return "string"; } // Validate exclusive options. bool ValidateExclusiveOption(const Option& _option, int _argc) { if (static_cast(_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 bool TypedOption<_Type>::ParseImpl(const char* _argv) { return ozz::options::Parse(_argv, name(), &value_); } template ozz::string TypedOption<_Type>::FormatDefault() const { std::stringstream str; str << "\"" << std::boolalpha << default_ << "\""; return str.str().c_str(); } template const char* TypedOption<_Type>::FormatType() const { return ozz::options::FormatOptionType<_Type>(); } // Explicit instantiation of all supported types of TypedOption. template class TypedOption; template class TypedOption; template class TypedOption; template class TypedOption; 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