/* Copyright (C) 2016 Eric Wasylishen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See file, 'COPYING', for details. */ #include "common/settings.hh" #include "common/threads.hh" #include "common/fs.hh" #include namespace settings { // parse_exception parse_exception::parse_exception(std::string str) : _what(std::move(str)) { } const char *parse_exception::what() const noexcept { return _what.c_str(); } // nameset nameset::nameset(const char *str) : vector({str}) { } nameset::nameset(const std::string &str) : vector({str}) { } nameset::nameset(const std::initializer_list &strs) : vector(strs.begin(), strs.end()) { } nameset::nameset(const std::initializer_list &strs) : vector(strs) { } // setting_base setting_base::setting_base( setting_container *dictionary, const nameset &names, const setting_group *group, const char *description) : _names(names), _group(group), _description(description) { Q_assert(_names.size() > 0); if (dictionary) { dictionary->registerSetting(this); } } bool setting_base::changeSource(source newSource) { if (newSource >= _source) { _source = newSource; return true; } return false; } // setting_func setting_func::setting_func(setting_container *dictionary, const nameset &names, std::function func, const setting_group *group, const char *description) : setting_base(dictionary, names, group, description), _func(func) { } bool setting_func::copyFrom(const setting_base &other) { return true; } void setting_func::reset() { } bool setting_func::parse(const std::string &settingName, parser_base_t &parser, source source) { _func(source); return true; } std::string setting_func::stringValue() const { return ""; } std::string setting_func::format() const { return ""; } // setting_bool bool setting_bool::parseInternal(parser_base_t &parser, source source, bool truthValue) { // boolean flags can be just flagged themselves if (parser.parse_token(PARSE_PEEK)) { // if the token that follows is 1, 0 or -1, we'll handle it // as a value, otherwise it's probably part of the next option. if (parser.token == "1" || parser.token == "0" || parser.token == "-1") { parser.parse_token(); int intval = std::stoi(parser.token); const bool f = (intval != 0 && intval != -1) ? truthValue : !truthValue; // treat 0 and -1 as false setValue(f, source); return true; } } setValue(truthValue, source); return true; } setting_bool::setting_bool(setting_container *dictionary, const nameset &names, bool v, const setting_group *group, const char *description) : setting_value(dictionary, names, v, group, description) { } bool setting_bool::parse(const std::string &settingName, parser_base_t &parser, source source) { return parseInternal(parser, source, true); } std::string setting_bool::stringValue() const { return _value ? "1" : "0"; } std::string setting_bool::format() const { return _default ? "[0]" : ""; } // setting_invertible_bool static nameset extendNames(const nameset &names) { nameset n = names; for (auto &name : names) { n.push_back("no" + name); } return n; } setting_invertible_bool::setting_invertible_bool(setting_container *dictionary, const nameset &names, bool v, const setting_group *group, const char *description) : setting_bool(dictionary, extendNames(names), v, group, description) { } bool setting_invertible_bool::parse(const std::string &settingName, parser_base_t &parser, source source) { return parseInternal(parser, source, settingName.compare(0, 2, "no") == 0 ? false : true); } // setting_redirect setting_redirect::setting_redirect(setting_container *dictionary, const nameset &names, const std::initializer_list &settings, const setting_group *group, const char *description) : setting_base(dictionary, names, group, description), _settings(settings) { } bool setting_redirect::copyFrom(const setting_base &other) { return true; } void setting_redirect::reset() { } bool setting_redirect::parse(const std::string &settingName, parser_base_t &parser, source source) { // this is a bit ugly, but we run the parse function for // every setting that we redirect from. for every entry // except the last, we'll backup & restore the state. for (size_t i = 0; i < _settings.size(); i++) { if (i != _settings.size() - 1) { parser.push_state(); } if (!_settings[i]->parse(settingName, parser, source)) { return false; } if (i != _settings.size() - 1) { parser.pop_state(); } } return true; } std::string setting_redirect::stringValue() const { return _settings[0]->stringValue(); } std::string setting_redirect::format() const { return _settings[0]->format(); } // setting_group setting_group performance_group{"Performance", 10}; setting_group logging_group{"Logging", 5}; setting_group game_group{"Game", 15}; // setting_container setting_container::~setting_container() = default; void setting_container::reset() { for (auto setting : _settings) { setting->reset(); } } const char *setting_base::sourceString() const { switch (_source) { case source::DEFAULT: return "default"; case source::GAME_TARGET: return "game target"; case source::MAP: return "map"; case source::COMMANDLINE: return "command line"; default: FError("Error: unknown setting source"); } } // setting_string setting_string::setting_string(setting_container *dictionary, const nameset &names, std::string v, const std::string_view &format, const setting_group *group, const char *description) : setting_value(dictionary, names, v, group, description), _format(format) { } bool setting_string::parse(const std::string &settingName, parser_base_t &parser, source source) { if (parser.parse_token()) { setValue(parser.token, source); return true; } return false; } std::string setting_string::stringValue() const { return _value; } std::string setting_string::format() const { return _format; } // setting_path setting_path::setting_path(setting_container *dictionary, const nameset &names, fs::path v, const setting_group *group, const char *description) : setting_value(dictionary, names, v, group, description) { } bool setting_path::parse(const std::string &settingName, parser_base_t &parser, source source) { // make sure we can parse token out if (!parser.parse_token()) { return false; } setValue(parser.token, source); return true; } std::string setting_path::stringValue() const { return _value.string(); } std::string setting_path::format() const { return "\"relative/path\" or \"C:/absolute/path\""; } // setting_set setting_set::setting_set(setting_container *dictionary, const nameset &names, const std::string_view &format, const setting_group *group, const char *description) : setting_base(dictionary, names, group, description), _format(format) { } const std::unordered_set &setting_set::values() const { return _values; } void setting_set::addValue(const std::string &value, source newSource) { if (changeSource(newSource)) { _values.insert(value); } } bool setting_set::parse(const std::string &settingName, parser_base_t &parser, source source) { if (!parser.parse_token(PARSE_PEEK)) return false; parser.parse_token(); addValue(parser.token, source); return true; } bool setting_set::copyFrom(const setting_base &other) { if (auto *casted = dynamic_cast(&other)) { _values = casted->_values; _source = casted->_source; return true; } return false; } void setting_set::reset() { _values.clear(); _source = source::DEFAULT; } std::string setting_set::format() const { return _format; } std::string setting_set::stringValue() const { std::string result; for (auto &v : _values) { if (!result.empty()) { result += ' '; } result += '\"' + v + '\"'; } return result; } // setting_vec3 qvec3d setting_vec3::transformVec3Value(const qvec3d &val) const { return val; } setting_vec3::setting_vec3(setting_container *dictionary, const nameset &names, vec_t a, vec_t b, vec_t c, const setting_group *group, const char *description) : setting_value(dictionary, names, transformVec3Value({a, b, c}), group, description) { } void setting_vec3::setValue(const qvec3d &f, source newsource) { setting_value::setValue(transformVec3Value(f), newsource); } bool setting_vec3::parse(const std::string &settingName, parser_base_t &parser, source source) { qvec3d vec; for (int i = 0; i < 3; i++) { if (!parser.parse_token()) { return false; } try { vec[i] = std::stod(parser.token); } catch (std::exception &) { return false; } } setValue(vec, source); return true; } std::string setting_vec3::stringValue() const { return qv::to_string(_value); } std::string setting_vec3::format() const { return "x y z"; } // setting_mangle qvec3d setting_mangle::transformVec3Value(const qvec3d &val) const { return qv::vec_from_mangle(val); } bool setting_mangle::parse(const std::string &settingName, parser_base_t &parser, source source) { qvec3d vec {}; for (int i = 0; i < 3; i++) { if (!parser.parse_token(PARSE_PEEK)) { break; } try { vec[i] = std::stod(parser.token); } catch (std::exception &) { break; } parser.parse_token(); } setValue(vec, source); return true; } // setting_color qvec3d setting_color::transformVec3Value(const qvec3d &val) const { return qv::normalize_color_format(val); } // setting_container void setting_container::copyFrom(const setting_container &other) { for (auto setting : _settings) { const std::string &pri_name = setting->primaryName(); const auto *other_setting = other.findSetting(pri_name); if (other_setting) { setting->copyFrom(*other_setting); } } } void setting_container::registerSetting(setting_base *setting) { for (const auto &name : setting->names()) { Q_assert(_settingsmap.find(name) == _settingsmap.end()); _settingsmap.emplace(name, setting); } _settings.emplace(setting); _groupedSettings[setting->getGroup()].insert(setting); } void setting_container::registerSettings(const std::initializer_list &settings) { registerSettings(settings.begin(), settings.end()); } setting_base *setting_container::findSetting(const std::string &name) const { // strip off leading underscores if (name.find("_") == 0) { return findSetting(name.substr(1, name.size() - 1)); } if (auto it = _settingsmap.find(name); it != _settingsmap.end()) { return it->second; } return nullptr; } setting_error setting_container::setSetting(const std::string &name, const std::string &value, source source) { setting_base *setting = findSetting(name); if (setting == nullptr) { if (source == source::COMMANDLINE) { throw parse_exception(fmt::format("Unrecognized command-line option '{}'\n", name)); } return setting_error::MISSING; } parser_t p{value, { }}; return setting->parse(name, p, source) ? setting_error::NONE : setting_error::INVALID; } void setting_container::setSettings(const entdict_t &epairs, source source) { for (const auto &epair : epairs) { setSetting(epair.first, epair.second, source); } } void setting_container::printHelp() { fmt::print("{}usage: {} [-help/-h/-?] [-options] {}\n\n", programDescription, programName, remainderName); for (auto grouped : grouped()) { if (grouped.first) { fmt::print("{}:\n", grouped.first->name); } for (auto setting : grouped.second) { size_t numPadding = max(static_cast(0), 28 - (setting->primaryName().size() + 4)); fmt::print(" -{} {:{}} {}\n", setting->primaryName(), setting->format(), numPadding, setting->getDescription()); for (int i = 1; i < setting->names().size(); i++) { fmt::print(" \\{}\n", setting->names()[i]); } } printf("\n"); } throw quit_after_help_exception(); } void setting_container::printSummary() { logging::print("\n--- Options Summary ---\n"); for (auto setting : _settings) { if (setting->isChanged()) { logging::print(" \"{}\" was set to \"{}\" (from {})\n", setting->primaryName(), setting->stringValue(), setting->sourceString()); } } logging::print("\n"); } std::vector setting_container::parse(parser_base_t &parser) { // the settings parser loop will continuously eat tokens as long as // it begins with a -; once we have no more settings to consume, we // break out of this loop and return the remainder. while (true) { // end of cmd line if (!parser.parse_token(PARSE_PEEK)) { break; } // end of options if (parser.token[0] != '-') { break; } // actually eat the token since we peeked above parser.parse_token(); // remove leading hyphens. we support any number of them. while (parser.token.front() == '-') { parser.token.erase(parser.token.begin()); } if (parser.token.empty()) { throw parse_exception("stray \"-\" in command line; please check your parameters"); } if (parser.token == "help" || parser.token == "h" || parser.token == "?") { printHelp(); } auto setting = findSetting(parser.token); if (!setting) { throw parse_exception(fmt::format("unknown option \"{}\"", parser.token)); } // pass off to setting to parse; store // name for error message below std::string token = std::move(parser.token); if (!setting->parse(token, parser, source::COMMANDLINE)) { throw parse_exception( fmt::format("invalid value for option \"{}\"; should be in format {}", token, setting->format())); } } // return remainder std::vector remainder; while (true) { if (parser.at_end() || !parser.parse_token()) { break; } remainder.emplace_back(std::move(parser.token)); } return remainder; } // global settings common_settings::common_settings() : threads{ this, "threads", 0, &performance_group, "number of threads to use, maximum; leave 0 for automatic"}, lowpriority{this, "lowpriority", true, &performance_group, "run in a lower priority, to free up headroom for other processes"}, log{this, "log", true, &logging_group, "whether log files are written or not"}, verbose{this, {"verbose", "v"}, false, &logging_group, "verbose output"}, nopercent{this, "nopercent", false, &logging_group, "don't output percentage messages"}, nostat{this, "nostat", false, &logging_group, "don't output statistic messages"}, noprogress{this, "noprogress", false, &logging_group, "don't output progress messages"}, nocolor{this, "nocolor", false, &logging_group, "don't output color codes (for TB, etc)"}, quiet{this, {"quiet", "noverbose"}, {&nopercent, &nostat, &noprogress}, &logging_group, "suppress non-important messages (equivalent to -nopercent -nostat -noprogress)"}, gamedir{this, "gamedir", "", &game_group, "override the default mod base directory. if this is not set, or if it is relative, it will be derived from the input file or the basedir if specified."}, basedir{this, "basedir", "", &game_group, "override the default game base directory. if this is not set, or if it is relative, it will be derived from the input file or the gamedir if specified."}, filepriority{this, "filepriority", search_priority_t::LOOSE, {{"loose", search_priority_t::LOOSE}, {"archive", search_priority_t::ARCHIVE}}, &game_group, "which types of archives (folders/loose files or packed archives) are higher priority and chosen first for path searching"}, paths{this, "path", "\"/path/to/folder\" ", &game_group, "additional paths or archives to add to the search path, mostly for loose files"}, q2rtx{this, "q2rtx", false, &game_group, "adjust settings to best support Q2RTX"}, defaultpaths{this, "defaultpaths", true, &game_group, "whether the compiler should attempt to automatically derive game/base paths for games that support it"} {} void common_settings::setParameters(int argc, const char **argv) { programName = fs::path(argv[0]).stem().string(); fmt::print("---- {} / ericw-tools {} ----\n", programName, ERICWTOOLS_VERSION); } void common_settings::preinitialize(int argc, const char **argv) { setParameters(argc, argv); } void common_settings::initialize(int argc, const char **argv) { token_parser_t p(argc, argv, { "command line" }); parse(p); } void common_settings::postinitialize(int argc, const char **argv) { printSummary(); configureTBB(threads.value(), lowpriority.value()); if (verbose.value()) { logging::mask |= logging::flag::VERBOSE; } if (nopercent.value()) { logging::mask &= ~(bitflags(logging::flag::PERCENT) | logging::flag::CLOCK_ELAPSED); } if (nostat.value()) { logging::mask &= ~bitflags(logging::flag::STAT); } if (noprogress.value()) { logging::mask &= ~bitflags(logging::flag::PROGRESS); } if (nocolor.value()) { logging::enable_color_codes = false; } } void common_settings::run(int argc, const char **argv) { preinitialize(argc, argv); initialize(argc, argv); postinitialize(argc, argv); } } // namespace settings