diff options
Diffstat (limited to 'src/lib/util/options.cpp')
-rw-r--r-- | src/lib/util/options.cpp | 980 |
1 files changed, 532 insertions, 448 deletions
diff --git a/src/lib/util/options.cpp b/src/lib/util/options.cpp index 2c56843947a..e989d6e4204 100644 --- a/src/lib/util/options.cpp +++ b/src/lib/util/options.cpp @@ -64,244 +64,479 @@ namespace } }; +//************************************************************************** +// OPTIONS EXCEPTION CLASS +//************************************************************************** + +//------------------------------------------------- +// options_exception - constructor +//------------------------------------------------- + +options_exception::options_exception(std::string &&message) + : m_message(std::move(message)) +{ +} + + +//------------------------------------------------- +// options_warning_exception - constructor +//------------------------------------------------- + +options_warning_exception::options_warning_exception(std::string &&message) + : options_exception(std::move(message)) +{ +} + + +//------------------------------------------------- +// options_error_exception - constructor +//------------------------------------------------- + +options_error_exception::options_error_exception(std::string &&message) + : options_exception(std::move(message)) +{ +} + //************************************************************************** -// CORE OPTIONS ENTRY +// CORE OPTIONS ENTRY BASE CLASS //************************************************************************** //------------------------------------------------- // entry - constructor //------------------------------------------------- -core_options::entry::entry(const char *name, const char *description, uint32_t flags, const char *defvalue) - : m_next(nullptr), - m_flags(flags), - m_error_reported(false), - m_priority(OPTION_PRIORITY_DEFAULT), - m_description(description), - m_changed(false) +core_options::entry::entry(std::vector<std::string> &&names, core_options::option_type type, const char *description) + : m_names(std::move(names)) + , m_priority(OPTION_PRIORITY_DEFAULT) + , m_type(type) + , m_description(description) { - // copy in the name(s) as appropriate - if (name != nullptr) + assert(m_names.empty() == (m_type == option_type::HEADER)); +} + +core_options::entry::entry(std::string &&name, core_options::option_type type, const char *description) + : entry(std::vector<std::string>({ std::move(name) }), type, description) +{ +} + + +//------------------------------------------------- +// entry - destructor +//------------------------------------------------- + +core_options::entry::~entry() +{ +} + + +//------------------------------------------------- +// entry::value +//------------------------------------------------- + +const char *core_options::entry::value() const +{ + // returning 'nullptr' from here signifies a value entry that is essentially "write only" + // and cannot be meaningfully persisted (e.g. - a command or the software name) + return nullptr; +} + + +//------------------------------------------------- +// entry::set_value +//------------------------------------------------- + +void core_options::entry::set_value(std::string &&newvalue, int priority_value, bool always_override) +{ + // it is invalid to set the value on a header + assert(type() != option_type::HEADER); + + // only set the value if we have priority + if (always_override || priority_value >= priority()) { - // first extract any range - std::string namestr(name); - int lparen = namestr.find_first_of('(',0); - int dash = namestr.find_first_of('-',lparen + 1); - int rparen = namestr.find_first_of(')',dash + 1); - if (lparen != -1 && dash != -1 && rparen != -1) + internal_set_value(std::move(newvalue)); + m_priority = priority_value; + + // invoke the value changed handler, if appropriate + if (m_value_changed_handler) + m_value_changed_handler(value()); + } +} + + +//------------------------------------------------- +// entry::set_default_value +//------------------------------------------------- + +void core_options::entry::set_default_value(std::string &&newvalue) +{ + // set_default_value() is not necessarily supported for all entry types + throw false; +} + + +//------------------------------------------------- +// entry::validate +//------------------------------------------------- + +void core_options::entry::validate(const std::string &data) +{ + float fval; + int ival; + + switch (type()) + { + case option_type::BOOLEAN: + // booleans must be 0 or 1 + if (sscanf(data.c_str(), "%d", &ival) != 1 || ival < 0 || ival > 1) + throw options_warning_exception("Illegal boolean value for %s: \"%s\"; reverting to %s\n", name(), data, value()); + break; + + case option_type::INTEGER: + // integers must be integral + if (sscanf(data.c_str(), "%d", &ival) != 1) + throw options_warning_exception("Illegal integer value for %s: \"%s\"; reverting to %s\n", name(), data, value()); + + // range checking + if (has_range()) { - strtrimspace(m_minimum.assign(namestr.substr(lparen + 1, dash - (lparen + 1)))); - strtrimspace(m_maximum.assign(namestr.substr(dash + 1, rparen - (dash + 1)))); - namestr.erase(lparen, rparen + 1 - lparen); + int minimum_integer = atoi(minimum()); + int maximum_integer = atoi(maximum()); + if (ival < minimum_integer || ival > maximum_integer) + throw options_warning_exception("Out-of-range integer value for %s: \"%s\" (must be between %d and %d); reverting to %s\n", name(), data, minimum_integer, maximum_integer, value()); } + break; - // then chop up any semicolon-separated names - int semi; - int nameindex = 0; - while ((semi = namestr.find_first_of(';')) != -1 && nameindex < ARRAY_LENGTH(m_name)) + case option_type::FLOAT: + if (sscanf(data.c_str(), "%f", &fval) != 1) + throw options_warning_exception("Illegal float value for %s: \"%s\"; reverting to %s\n", name(), data, value()); + + // range checking + if (has_range()) { - m_name[nameindex++].assign(namestr.substr(0, semi)); - namestr.erase(0, semi + 1); + float minimum_float = atof(minimum()); + float maximum_float = atof(maximum()); + if (fval < minimum_float || fval > maximum_float) + throw options_warning_exception("Out-of-range float value for %s: \"%s\" (must be between %f and %f); reverting to %s\n", name(), data, minimum_float, maximum_float, value()); } + break; - // finally add the last item - if (nameindex < ARRAY_LENGTH(m_name)) - m_name[nameindex++] = namestr; + case OPTION_STRING: + // strings can be anything + break; + + case OPTION_INVALID: + case OPTION_HEADER: + default: + // anything else is invalid + throw options_error_exception("Attempted to set invalid option %s\n", name()); } +} - // set the default value - if (defvalue != nullptr) - m_defdata = defvalue; - m_data = m_defdata; + +//------------------------------------------------- +// entry::minimum +//------------------------------------------------- + +const char *core_options::entry::minimum() const +{ + return nullptr; } //------------------------------------------------- -// set_value - update our data value +// entry::maximum //------------------------------------------------- -void core_options::entry::set_value(const char *newdata, int priority) +const char *core_options::entry::maximum() const { - // ignore if we don't have priority - if (priority < m_priority) - return; + return nullptr; +} + - // set the data and priority, then bump the sequence - m_data = newdata; - m_priority = priority; +//------------------------------------------------- +// entry::has_range +//------------------------------------------------- + +bool core_options::entry::has_range() const +{ + return minimum() && maximum(); } //------------------------------------------------- -// set_default_value - set the default value of -// an option, and reset the current value to it +// entry::default_value //------------------------------------------------- -void core_options::entry::set_default_value(const char *defvalue) +const std::string &core_options::entry::default_value() const { - m_data = defvalue; - m_defdata = defvalue; - m_priority = OPTION_PRIORITY_DEFAULT; + // I don't really want this generally available, but MewUI seems to need it. Please + // do not use + throw false; } +//************************************************************************** +// CORE OPTIONS SIMPLE ENTRYCLASS +//************************************************************************** + //------------------------------------------------- -// set_description - set the description of -// an option +// simple_entry - constructor //------------------------------------------------- -void core_options::entry::set_description(const char *description) +core_options::simple_entry::simple_entry(std::vector<std::string> &&names, const char *description, core_options::option_type type, std::string &&defdata, std::string &&minimum, std::string &&maximum) + : entry(std::move(names), type, description) + , m_defdata(std::move(defdata)) + , m_minimum(std::move(minimum)) + , m_maximum(std::move(maximum)) { - m_description = description; + m_data = m_defdata; } -void core_options::entry::set_flag(uint32_t mask, uint32_t flag) +//------------------------------------------------- +// simple_entry - destructor +//------------------------------------------------- + +core_options::simple_entry::~simple_entry() { - m_flags = ( m_flags & mask ) | flag; } //------------------------------------------------- -// revert - revert back to our default if we are -// within the given priority range +// simple_entry::value //------------------------------------------------- -void core_options::entry::revert(int priority_hi, int priority_lo) +const char *core_options::simple_entry::value() const { - // if our priority is within the range, revert to the default - if (m_priority <= priority_hi && m_priority >= priority_lo) + const char *result; + switch (type()) { - m_data = m_defdata; - m_priority = OPTION_PRIORITY_DEFAULT; + case core_options::option_type::BOOLEAN: + case core_options::option_type::INTEGER: + case core_options::option_type::FLOAT: + case core_options::option_type::STRING: + result = m_data.c_str(); + break; + + default: + // this is an option type for which returning a value is + // a meaningless operation (e.g. - core_options::option_type::COMMAND) + result = nullptr; + break; } + return result; } - -//************************************************************************** -// CORE OPTIONS -//************************************************************************** - //------------------------------------------------- -// core_options - constructor +// simple_entry::default_value //------------------------------------------------- -core_options::core_options() +const std::string &core_options::simple_entry::default_value() const { + // only MewUI seems to need this; please don't use + return m_defdata; } -core_options::core_options(const options_entry *entrylist) + +//------------------------------------------------- +// internal_set_value +//------------------------------------------------- + +void core_options::simple_entry::internal_set_value(std::string &&newvalue) { - add_entries(entrylist); + m_data = std::move(newvalue); } -core_options::core_options(const options_entry *entrylist1, const options_entry *entrylist2) + +//------------------------------------------------- +// set_default_value +//------------------------------------------------- + +void core_options::simple_entry::set_default_value(std::string &&newvalue) { - add_entries(entrylist1); - add_entries(entrylist2); + m_defdata = std::move(newvalue); } -core_options::core_options(const options_entry *entrylist1, const options_entry *entrylist2, const options_entry *entrylist3) + +//------------------------------------------------- +// minimum +//------------------------------------------------- + +const char *core_options::simple_entry::minimum() const { - add_entries(entrylist1); - add_entries(entrylist2); - add_entries(entrylist3); + return m_minimum.c_str(); } -core_options::core_options(const core_options &src) + +//------------------------------------------------- +// maximum +//------------------------------------------------- + +const char *core_options::simple_entry::maximum() const { - copyfrom(src); + return m_maximum.c_str(); } +//************************************************************************** +// CORE OPTIONS +//************************************************************************** + //------------------------------------------------- -// ~core_options - destructor +// core_options - constructor //------------------------------------------------- -core_options::~core_options() +core_options::core_options() { } //------------------------------------------------- -// operator= - assignment operator +// ~core_options - destructor //------------------------------------------------- -core_options &core_options::operator=(const core_options &rhs) +core_options::~core_options() { - // ignore self-assignment - if (this != &rhs) - copyfrom(rhs); - return *this; } //------------------------------------------------- -// operator== - compare two sets of options +// add_entry - adds an entry //------------------------------------------------- -bool core_options::operator==(const core_options &rhs) +void core_options::add_entry(entry::shared_ptr &&entry, const char *after_header) { - // iterate over options in the first list - for (entry &curentry : m_entrylist) - if (!curentry.is_header()) - { - // if the values differ, return false - if (strcmp(curentry.value(), rhs.value(curentry.name())) != 0) - return false; - } + // update the entry map + for (const std::string &name : entry->names()) + { + // append the entry + add_to_entry_map(std::string(name), entry); + + // for booleans, add the "-noXYZ" option as well + if (entry->type() == option_type::BOOLEAN) + add_to_entry_map(std::string("no") + name, entry); + } - return true; + // and add the entry to the vector + m_entries.emplace_back(std::move(entry)); } //------------------------------------------------- -// operator!= - compare two sets of options +// add_to_entry_map - adds an entry to the entry +// map //------------------------------------------------- -bool core_options::operator!=(const core_options &rhs) +void core_options::add_to_entry_map(std::string &&name, entry::shared_ptr &entry) { - return !operator==(rhs); + // it is illegal to call this method for something that already ex0ists + assert(m_entrymap.find(name) == m_entrymap.end()); + + // append the entry + m_entrymap.emplace(std::make_pair(name, entry::weak_ptr(entry))); } //------------------------------------------------- -// add_entry - add an entry to the current -// options set +// add_entry - adds an entry based on an +// options_entry //------------------------------------------------- -void core_options::add_entry(const char *name, const char *description, uint32_t flags, const char *defvalue, bool override_existing) +void core_options::add_entry(const options_entry &opt, bool override_existing) { - // allocate a new entry - auto newentry = global_alloc(entry(name, description, flags, defvalue)); - if (newentry->name() != nullptr) + std::vector<std::string> names; + std::string minimum, maximum; + + // copy in the name(s) as appropriate + if (opt.name) { - // see if we match an existing entry - auto checkentry = m_entrymap.find(newentry->name()); - if (checkentry != m_entrymap.end()) + // first extract any range + std::string namestr(opt.name); + int lparen = namestr.find_first_of('(', 0); + int dash = namestr.find_first_of('-', lparen + 1); + int rparen = namestr.find_first_of(')', dash + 1); + if (lparen != -1 && dash != -1 && rparen != -1) { - entry *existing = checkentry->second; - // if we're overriding existing entries, then remove the old one - if (override_existing) - m_entrylist.remove(*existing); + strtrimspace(minimum.assign(namestr.substr(lparen + 1, dash - (lparen + 1)))); + strtrimspace(maximum.assign(namestr.substr(dash + 1, rparen - (dash + 1)))); + namestr.erase(lparen, rparen + 1 - lparen); + } + + // then chop up any semicolon-separated names + size_t semi; + while ((semi = namestr.find_first_of(';')) != std::string::npos) + { + names.push_back(namestr.substr(0, semi)); + namestr.erase(0, semi + 1); + } + + // finally add the last item + names.push_back(std::move(namestr)); + } - // otherwise, just override the default and current values and throw out the new entry + // we might be called with an existing entry + entry::shared_ptr existing_entry; + do + { + for (const std::string &name : names) + { + existing_entry = get_entry(name.c_str()); + if (existing_entry) + break; + } + + if (existing_entry) + { + if (override_existing) + remove_entry(*existing_entry); else - { - existing->set_default_value(newentry->value()); - global_free(newentry); return; - } } + } while (existing_entry); + + // set the default value + std::string defdata = opt.defvalue ? opt.defvalue : ""; + + // create and add the entry + add_entry( + std::move(names), + opt.description, + opt.type, + std::move(defdata), + std::move(minimum), + std::move(maximum)); +} + + +//------------------------------------------------- +// add_entry +//------------------------------------------------- + +void core_options::add_entry(std::vector<std::string> &&names, const char *description, option_type type, std::string &&default_value, std::string &&minimum, std::string &&maximum) +{ + // create the entry + entry::shared_ptr new_entry = std::make_shared<simple_entry>( + std::move(names), + description, + type, + std::move(default_value), + std::move(minimum), + std::move(maximum)); + + // and add it + add_entry(std::move(new_entry)); +} - // need to call value_changed() with initial value - value_changed(newentry->name(), newentry->value()); - } - // add us to the list and maps - append_entry(*newentry); +//------------------------------------------------- +// add_header +//------------------------------------------------- + +void core_options::add_header(const char *description) +{ + add_entry(std::vector<std::string>(), description, option_type::HEADER); } @@ -313,8 +548,8 @@ void core_options::add_entry(const char *name, const char *description, uint32_t void core_options::add_entries(const options_entry *entrylist, bool override_existing) { // loop over entries until we hit a nullptr name - for ( ; entrylist->name != nullptr || (entrylist->flags & OPTION_HEADER) != 0; entrylist++) - add_entry(*entrylist, override_existing); + for (int i = 0; entrylist[i].name || entrylist[i].type == option_type::HEADER; i++) + add_entry(entrylist[i], override_existing); } @@ -325,13 +560,8 @@ void core_options::add_entries(const options_entry *entrylist, bool override_exi void core_options::set_default_value(const char *name, const char *defvalue) { - // find the entry and bail if we can't - auto curentry = m_entrymap.find(name); - if (curentry == m_entrymap.end()) - return; - // update the data and default data - curentry->second->set_default_value(defvalue); + get_entry(name)->set_default_value(defvalue); } @@ -342,13 +572,8 @@ void core_options::set_default_value(const char *name, const char *defvalue) void core_options::set_description(const char *name, const char *description) { - // find the entry and bail if we can't - auto curentry = m_entrymap.find(name); - if (curentry == m_entrymap.end()) - return; - // update the data and default data - curentry->second->set_description(description); + get_entry(name)->set_description(description); } @@ -357,10 +582,12 @@ void core_options::set_description(const char *name, const char *description) // command line arguments //------------------------------------------------- -bool core_options::parse_command_line(std::vector<std::string> &args, int priority, std::string &error_string) +void core_options::parse_command_line(const std::vector<std::string> &args, int priority, bool ignore_unknown_options) { + std::ostringstream error_stream; + condition_type condition = condition_type::NONE; + // reset the errors and the command - error_string.clear(); m_command.clear(); // we want to identify commands first @@ -368,23 +595,20 @@ bool core_options::parse_command_line(std::vector<std::string> &args, int priori { if (!args[arg].empty() && args[arg][0] == '-') { - auto curentry = m_entrymap.find(&args[arg][1]); - if (curentry != m_entrymap.end() && curentry->second->type() == OPTION_COMMAND) + auto curentry = get_entry(&args[arg][1]); + if (curentry && curentry->type() == OPTION_COMMAND) { // can only have one command if (!m_command.empty()) - { - error_string.append(string_format("Error: multiple commands specified -%s and %s\n", m_command, args[arg])); - return false; - } - m_command = curentry->second->name(); + throw options_error_exception("Error: multiple commands specified -%s and %s\n", m_command, args[arg]); + + m_command = curentry->name(); } } } // iterate through arguments int unadorned_index = 0; - size_t new_argc = 1; for (size_t arg = 1; arg < args.size(); arg++) { // determine the entry name to search for @@ -396,39 +620,26 @@ bool core_options::parse_command_line(std::vector<std::string> &args, int priori if (is_unadorned && !m_command.empty()) { m_command_arguments.push_back(std::move(args[arg])); - args[arg].clear(); + command_argument_processed(); continue; } // find our entry; if not found, continue - auto curentry = m_entrymap.find(optionname); - if (curentry == m_entrymap.end()) + auto curentry = get_entry(optionname); + if (!curentry) { - // we need to relocate this option - if (new_argc != arg) - args[new_argc] = std::move(args[arg]); - new_argc++; - - if (!is_unadorned) - { - arg++; - if (arg < args.size()) - { - if (new_argc != arg) - args[new_argc] = std::move(args[arg]); - new_argc++; - } - } + if (!ignore_unknown_options) + throw options_error_exception("Error: unknown option: -%s\n", optionname); continue; } // at this point, we've already processed commands - if (curentry->second->type() == OPTION_COMMAND) + if (curentry->type() == OPTION_COMMAND) continue; // get the data for this argument, special casing booleans std::string newdata; - if (curentry->second->type() == OPTION_BOOLEAN) + if (curentry->type() == option_type::BOOLEAN) { newdata = (strncmp(&curarg[1], "no", 2) == 0) ? "0" : "1"; } @@ -438,22 +649,19 @@ bool core_options::parse_command_line(std::vector<std::string> &args, int priori } else if (arg + 1 < args.size()) { - args[arg++].clear(); - newdata = std::move(args[arg]); + newdata = args[++arg]; } else { - error_string.append(string_format("Error: option %s expected a parameter\n", curarg)); - return false; + throw options_error_exception("Error: option %s expected a parameter\n", curarg); } - args[arg].clear(); // set the new data - validate_and_set_data(*curentry->second, std::move(newdata), priority, error_string); + do_set_value(*curentry, std::move(newdata), priority, error_stream, condition); } - args.resize(new_argc); - return true; + // did we have any errors that may need to be aggregated? + throw_options_exception_if_appropriate(condition, error_stream); } @@ -462,8 +670,11 @@ bool core_options::parse_command_line(std::vector<std::string> &args, int priori // an INI file //------------------------------------------------- -bool core_options::parse_ini_file(util::core_file &inifile, int priority, bool ignore_unknown_options, std::string &error_string) +void core_options::parse_ini_file(util::core_file &inifile, int priority, bool ignore_unknown_options, bool always_override) { + std::ostringstream error_stream; + condition_type condition = condition_type::NONE; + // loop over lines in the file char buffer[4096]; while (inifile.gets(buffer, ARRAY_LENGTH(buffer)) != nullptr) @@ -487,7 +698,8 @@ bool core_options::parse_ini_file(util::core_file &inifile, int priority, bool i // if we hit the end early, print a warning and continue if (*temp == 0) { - error_string.append(string_format("Warning: invalid line in INI: %s", buffer)); + condition = std::max(condition, condition_type::WARN); + util::stream_format(error_stream, "Warning: invalid line in INI: %s", buffer); continue; } @@ -507,79 +719,73 @@ bool core_options::parse_ini_file(util::core_file &inifile, int priority, bool i *temp = 0; // find our entry - auto curentry = m_entrymap.find(optionname); - if (curentry == m_entrymap.end()) + entry::shared_ptr curentry = get_entry(optionname); + if (!curentry) { if (!ignore_unknown_options) - error_string.append(string_format("Warning: unknown option in INI: %s\n", optionname)); + { + condition = std::max(condition, condition_type::WARN); + util::stream_format(error_stream, "Warning: unknown option in INI: %s\n", optionname); + } continue; } // set the new data std::string data = optiondata; trim_spaces_and_quotes(data); - validate_and_set_data(*curentry->second, std::move(data), priority, error_string); + do_set_value(*curentry, std::move(data), priority, error_stream, condition); } - return true; + + // did we have any errors that may need to be aggregated? + throw_options_exception_if_appropriate(condition, error_stream); } //------------------------------------------------- -// pluck_from_command_line - finds a specific -// value from within a command line +// throw_options_exception_if_appropriate //------------------------------------------------- -bool core_options::pluck_from_command_line(std::vector<std::string> &args, const std::string &optionname, std::string &result) +void core_options::throw_options_exception_if_appropriate(core_options::condition_type condition, std::ostringstream &error_stream) { - // find this entry within the options (it is illegal to call this with a non-existant option - // so we assert if not present) - auto curentry = m_entrymap.find(optionname); - assert(curentry != m_entrymap.end()); - - // build a vector with potential targets - std::vector<std::string> targets; - const char *potential_target; - int index = 0; - while ((potential_target = curentry->second->name(index++)) != nullptr) + switch(condition) { - // not supporting unadorned options for now - targets.push_back(std::string("-") + potential_target); - } + case condition_type::NONE: + // do nothing + break; - // find each of the targets in the argv array - for (int i = 1; i < args.size() - 1; i++) - { - auto const iter = std::find_if( - targets.begin(), - targets.end(), - [&args, i](const std::string &targ) { return targ == args[i]; }); - if (iter != targets.end()) - { - // get the result - result = std::move(args[i + 1]); + case condition_type::WARN: + throw options_warning_exception(error_stream.str()); - // remove this arguments from the list - auto const pos = std::next(args.begin(), i); - args.erase(pos, std::next(pos, 2)); - return true; - } - } + case condition_type::ERR: + throw options_error_exception(error_stream.str()); - result.clear(); - return false; + default: + // should not get here + throw false; + } } //------------------------------------------------- -// revert - revert options at or below a certain -// priority back to their defaults +// copy_from //------------------------------------------------- -void core_options::revert(int priority_hi, int priority_lo) +void core_options::copy_from(const core_options &that) { - // iterate over options and revert to defaults if below the given priority - for (entry &curentry : m_entrylist) - curentry.revert(priority_hi, priority_lo); + for (auto &dest_entry : m_entries) + { + if (dest_entry->names().size() > 0) + { + // identify the source entry + const entry::shared_ptr source_entry = that.get_entry(dest_entry->name()); + if (source_entry) + { + const char *value = source_entry->value(); + if (value) + dest_entry->set_value(value, source_entry->priority(), true); + } + } + } } @@ -600,47 +806,34 @@ std::string core_options::output_ini(const core_options *diff) const std::string overridden_value; // loop over all items - for (entry &curentry : m_entrylist) + for (auto &curentry : m_entries) { - const char *name = curentry.name(); - const char *value; - switch (override_get_value(name, overridden_value)) + if (curentry->type() == option_type::HEADER) { - case override_get_value_result::NONE: - default: - value = curentry.value(); - break; - - case override_get_value_result::SKIP: - continue; - - case override_get_value_result::OVERRIDE: - value = overridden_value.c_str(); - break; + // header: record description + last_header = curentry->description(); } - bool is_unadorned = false; - - // check if it's unadorned - if (name && strlen(name) && !strcmp(name, core_options::unadorned(unadorned_index))) + else { - unadorned_index++; - is_unadorned = true; - } + const std::string &name(curentry->name()); + const char *value(curentry->value()); - // header: record description - if (curentry.is_header()) - last_header = curentry.description(); + // check if it's unadorned + bool is_unadorned = false; + if (name == core_options::unadorned(unadorned_index)) + { + unadorned_index++; + is_unadorned = true; + } - // otherwise, output entries for all non-command items - else if (!curentry.is_command()) - { - if (!curentry.is_internal()) + // output entries for all non-command items (items with value) + if (value) { // look up counterpart in diff, if diff is specified - if (diff == nullptr || strcmp(value, diff->value(name)) != 0) + if (!diff || strcmp(value, diff->value(name.c_str()))) { // output header, if we have one - if (last_header != nullptr) + if (last_header) { if (num_valid_headers++) buffer << '\n'; @@ -651,7 +844,7 @@ std::string core_options::output_ini(const core_options *diff) const // and finally output the data, skip if unadorned if (!is_unadorned) { - if (strchr(value, ' ') != nullptr) + if (strchr(value, ' ')) util::stream_format(buffer, "%-25s \"%s\"\n", name, value); else util::stream_format(buffer, "%-25s %s\n", name, value); @@ -674,15 +867,15 @@ std::string core_options::output_help() const std::ostringstream buffer; // loop over all items - for (entry &curentry : m_entrylist) + for (auto &curentry : m_entries) { // header: just print - if (curentry.is_header()) - util::stream_format(buffer, "\n#\n# %s\n#\n", curentry.description()); + if (curentry->type() == option_type::HEADER) + util::stream_format(buffer, "\n#\n# %s\n#\n", curentry->description()); // otherwise, output entries for all non-deprecated items - else if (curentry.description() != nullptr) - util::stream_format(buffer, "-%-20s%s\n", curentry.name(), curentry.description()); + else if (curentry->description() != nullptr) + util::stream_format(buffer, "-%-20s%s\n", curentry->name(), curentry->description()); } return buffer.str(); } @@ -692,10 +885,9 @@ std::string core_options::output_help() const // value - return the raw option value //------------------------------------------------- -const char *core_options::value(const char *name) const +const char *core_options::value(const char *option) const { - auto curentry = m_entrymap.find(name); - return (curentry != m_entrymap.end()) ? curentry->second->value() : ""; + return get_entry(option)->value(); } @@ -703,241 +895,133 @@ const char *core_options::value(const char *name) const // description - return description of option //------------------------------------------------- -const char *core_options::description(const char *name) const +const char *core_options::description(const char *option) const { - auto curentry = m_entrymap.find(name); - return (curentry != m_entrymap.end()) ? curentry->second->description() : ""; + return get_entry(option)->description(); } +//************************************************************************** +// LEGACY +//************************************************************************** + //------------------------------------------------- -// priority - return the priority of option +// set_value - set the raw option value //------------------------------------------------- -int core_options::priority(const char *name) const +void core_options::set_value(const std::string &name, const std::string &value, int priority) { - auto curentry = m_entrymap.find(name); - return (curentry != m_entrymap.end()) ? curentry->second->priority() : 0; + set_value(name, std::string(value), priority); } +void core_options::set_value(const std::string &name, std::string &&value, int priority) +{ + get_entry(name)->set_value(std::move(value), priority); +} -//------------------------------------------------- -// exists - return if option exists in list -//------------------------------------------------- +void core_options::set_value(const std::string &name, int value, int priority) +{ + set_value(name, string_format("%d", value), priority); +} -bool core_options::exists(const char *name) const +void core_options::set_value(const std::string &name, float value, int priority) { - return (m_entrymap.find(name) != m_entrymap.end()); + set_value(name, string_format("%f", value), priority); } //------------------------------------------------- -// set_value - set the raw option value +// remove_entry - remove an entry from our list +// and map //------------------------------------------------- -bool core_options::set_value(const char *name, const char *value, int priority, std::string &error_string) -{ - // find the entry first - auto curentry = m_entrymap.find(name); - if (curentry == m_entrymap.end()) - { - error_string.append(string_format("Attempted to set unknown option %s\n", name)); - return false; - } - - // validate and set the item normally - return validate_and_set_data(*curentry->second, value, priority, error_string); -} - -bool core_options::set_value(const char *name, int value, int priority, std::string &error_string) +void core_options::remove_entry(core_options::entry &delentry) { - return set_value(name, string_format("%d", value).c_str(), priority, error_string); + // find this in m_entries + auto iter = std::find_if( + m_entries.begin(), + m_entries.end(), + [&delentry](const auto &x) { return &*x == &delentry; }); + assert(iter != m_entries.end()); + + // erase each of the items out of the entry map + for (const std::string &name : delentry.names()) + m_entrymap.erase(name); + + // finally erase it + m_entries.erase(iter); } -bool core_options::set_value(const char *name, float value, int priority, std::string &error_string) -{ - return set_value(name, string_format("%f", value).c_str(), priority, error_string); -} +//------------------------------------------------- +// do_set_value +//------------------------------------------------- -void core_options::set_flag(const char *name, uint32_t mask, uint32_t flag) +void core_options::do_set_value(entry &curentry, std::string &&data, int priority, std::ostream &error_stream, condition_type &condition) { - // find the entry first - auto curentry = m_entrymap.find(name); - if ( curentry == m_entrymap.end()) + // this is called when parsing a command line or an INI - we want to catch the option_exception and write + // any exception messages to the error stream + try { - return; + curentry.set_value(std::move(data), priority); } - curentry->second->set_flag(mask, flag); -} - -void core_options::mark_changed(const char* name) -{ - // find the entry first - auto curentry = m_entrymap.find(name); - if (curentry == m_entrymap.end()) + catch (options_warning_exception &ex) { - return; + // we want to aggregate option exceptions + error_stream << ex.message(); + condition = std::max(condition, condition_type::WARN); + } + catch (options_error_exception &ex) + { + // we want to aggregate option exceptions + error_stream << ex.message(); + condition = std::max(condition, condition_type::ERR); } - curentry->second->mark_changed(); } + //------------------------------------------------- -// reset - reset the options state, removing -// everything +// get_entry //------------------------------------------------- -void core_options::reset() +const core_options::entry::shared_ptr core_options::get_entry(const std::string &name) const +{ + auto curentry = m_entrymap.find(name); + return (curentry != m_entrymap.end()) ? curentry->second.lock() : nullptr; +} + +core_options::entry::shared_ptr core_options::get_entry(const std::string &name) { - m_entrylist.reset(); - m_entrymap.clear(); + auto curentry = m_entrymap.find(name); + return (curentry != m_entrymap.end()) ? curentry->second.lock() : nullptr; } //------------------------------------------------- -// append_entry - append an entry to our list -// and index it in the map +// set_value_changed_handler //------------------------------------------------- -void core_options::append_entry(core_options::entry &newentry) +void core_options::set_value_changed_handler(const std::string &name, std::function<void(const char *)> &&handler) { - m_entrylist.append(newentry); - - // if we have names, add them to the map - for (int name = 0; name < ARRAY_LENGTH(newentry.m_name); name++) - if (newentry.name(name) != nullptr) - { - m_entrymap.insert(std::make_pair(newentry.name(name), &newentry)); - // for boolean options add a "no" variant as well - if (newentry.type() == OPTION_BOOLEAN) - m_entrymap.insert(std::make_pair(std::string("no").append(newentry.name(name)), &newentry)); - } + get_entry(name)->set_value_changed_handler(std::move(handler)); } //------------------------------------------------- -// remove_entry - remove an entry from our list -// and map +// header_exists //------------------------------------------------- -void core_options::remove_entry(core_options::entry &delentry) +bool core_options::header_exists(const char *description) const { - // remove all names from the map - for (int name = 0; name < ARRAY_LENGTH(delentry.m_name); name++) - if (!delentry.m_name[name].empty()) - { - auto entry = m_entrymap.find(delentry.m_name[name]); - if (entry!= m_entrymap.end()) m_entrymap.erase(entry); - } - - // remove the entry from the list - m_entrylist.remove(delentry); -} - -/** - * @fn void core_options::copyfrom(const core_options &src) - * - * @brief ------------------------------------------------- - * copyfrom - copy options from another set - * -------------------------------------------------. - * - * @param src Source for the. - */ - -void core_options::copyfrom(const core_options &src) -{ - // reset ourselves first - reset(); - - // iterate through the src options and make our own - for (entry &curentry : src.m_entrylist) - append_entry(*global_alloc(entry(curentry.name(), curentry.description(), curentry.flags(), curentry.default_value()))); -} - -/** - * @fn bool core_options::validate_and_set_data(core_options::entry &curentry, const char *newdata, int priority, std::string &error_string) - * - * @brief ------------------------------------------------- - * validate_and_set_data - make sure the data is of the appropriate type and within - * range, then set it - * -------------------------------------------------. - * - * @param [in,out] curentry The curentry. - * @param newdata The newdata. - * @param priority The priority. - * @param [in,out] error_string The error string. - * - * @return true if it succeeds, false if it fails. - */ - -bool core_options::validate_and_set_data(core_options::entry &curentry, std::string &&data, int priority, std::string &error_string) -{ - // let derived classes override how we set this data - if (override_set_value(curentry.name(), data)) - return true; - - // validate the type of data and optionally the range - float fval; - int ival; - switch (curentry.type()) - { - // booleans must be 0 or 1 - case OPTION_BOOLEAN: - if (sscanf(data.c_str(), "%d", &ival) != 1 || ival < 0 || ival > 1) - { - error_string.append(string_format("Illegal boolean value for %s: \"%s\"; reverting to %s\n", curentry.name(), data.c_str(), curentry.value())); - return false; - } - break; - - // integers must be integral - case OPTION_INTEGER: - if (sscanf(data.c_str(), "%d", &ival) != 1) - { - error_string.append(string_format("Illegal integer value for %s: \"%s\"; reverting to %s\n", curentry.name(), data.c_str(), curentry.value())); - return false; - } - if (curentry.has_range() && (ival < atoi(curentry.minimum()) || ival > atoi(curentry.maximum()))) + auto iter = std::find_if( + m_entries.begin(), + m_entries.end(), + [description](const auto &entry) { - error_string.append(string_format("Out-of-range integer value for %s: \"%s\" (must be between %s and %s); reverting to %s\n", curentry.name(), data.c_str(), curentry.minimum(), curentry.maximum(), curentry.value())); - return false; - } - break; + return entry->type() == option_type::HEADER + && entry->description() + && !strcmp(entry->description(), description); + }); - // floating-point values must be numeric - case OPTION_FLOAT: - if (sscanf(data.c_str(), "%f", &fval) != 1) - { - error_string.append(string_format("Illegal float value for %s: \"%s\"; reverting to %s\n", curentry.name(), data.c_str(), curentry.value())); - return false; - } - if (curentry.has_range() && ((double) fval < atof(curentry.minimum()) || (double) fval > atof(curentry.maximum()))) - { - error_string.append(string_format("Out-of-range float value for %s: \"%s\" (must be between %s and %s); reverting to %s\n", curentry.name(), data.c_str(), curentry.minimum(), curentry.maximum(), curentry.value())); - return false; - } - break; - - // strings can be anything - case OPTION_STRING: - break; - - // anything else is invalid - case OPTION_INVALID: - case OPTION_HEADER: - default: - error_string.append(string_format("Attempted to set invalid option %s\n", curentry.name())); - return false; - } - - // set the data - curentry.set_value(data.c_str(), priority); - value_changed(curentry.name(), data); - return true; -} - -core_options::entry *core_options::get_entry(const char *name) const -{ - auto curentry = m_entrymap.find(name); - return (curentry != m_entrymap.end()) ? curentry->second : nullptr; + return iter != m_entries.end(); } |