mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-12 11:37:42 +00:00
[CLI]: CLI Argument Parsing Fixes (#15478)
* Reject invalid CLI argument values instead of silently accepting them * Add read_cli accept/reject tests * Update Option Type for LogFile argument * Add read_cli vector option tests * Accept common bool spellings on the CLI, cover --logfile in tests * Add unit tests for truthy bool parsing
This commit is contained in:
@@ -7384,7 +7384,7 @@ void CLI::print_help(bool include_print_options, PrinterTechnology printer_techn
|
||||
<< std::endl
|
||||
<< "Print setting priorities:" << std::endl
|
||||
<< "\t1) setting values from the command line (highest priority)"<< std::endl
|
||||
<< "\t2) setting values loaded with --load_settings and --load_filaments" << std::endl
|
||||
<< "\t2) setting values loaded with --load-settings and --load-filaments" << std::endl
|
||||
<< "\t3) setting values loaded from 3mf(lowest priority)" << std::endl;
|
||||
|
||||
/*if (include_print_options) {
|
||||
|
||||
@@ -1715,6 +1715,36 @@ const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key) co
|
||||
return (it == options.end()) ? nullptr : it->second.get();
|
||||
}
|
||||
|
||||
// ConfigOptionBool(s)::deserialize only understands "1" and "0", but scripts commonly spell CLI
|
||||
// flags as --opt=true or --opt=no. Map the usual spellings onto what deserialize() accepts, per
|
||||
// comma-separated item so vector options keep working, and pass anything else through unchanged
|
||||
// so a genuine typo is still reported as invalid.
|
||||
static std::string normalize_cli_bool_value(const std::string &value)
|
||||
{
|
||||
static const char* true_values[] = { "1", "true", "yes", "on", "enabled" };
|
||||
static const char* false_values[] = { "0", "false", "no", "off", "disabled" };
|
||||
|
||||
auto matches = [](const std::string &item, const char* const* candidates, size_t count) {
|
||||
return std::any_of(candidates, candidates + count, [&item](const char* candidate) { return boost::iequals(item, candidate); });
|
||||
};
|
||||
|
||||
std::string normalized;
|
||||
std::istringstream is(value);
|
||||
std::string item;
|
||||
while (std::getline(is, item, ',')) {
|
||||
boost::trim(item);
|
||||
if (! normalized.empty())
|
||||
normalized += ",";
|
||||
if (matches(item, true_values, std::size(true_values)))
|
||||
normalized += "1";
|
||||
else if (matches(item, false_values, std::size(false_values)))
|
||||
normalized += "0";
|
||||
else
|
||||
normalized += item;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys)
|
||||
{
|
||||
// cache the CLI option => opt_key mapping
|
||||
@@ -1812,17 +1842,32 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
||||
// to the end of the value.
|
||||
if (opt_base->type() == coBools && value.empty())
|
||||
static_cast<ConfigOptionBools*>(opt_base)->values.push_back(!no);
|
||||
else
|
||||
else {
|
||||
// Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way
|
||||
// they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape
|
||||
// will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be
|
||||
// unescaped by the calling shell.
|
||||
opt_vector->deserialize(value, true);
|
||||
const std::string vector_value = opt_base->type() == coBools ? normalize_cli_bool_value(value) : value;
|
||||
bool deserialized = false;
|
||||
try {
|
||||
deserialized = opt_vector->deserialize(vector_value, true);
|
||||
} catch (const std::exception &ex) {
|
||||
// e.g. "nil" deserialized into a non-nullable vector option throws instead of
|
||||
// returning false - treat that the same as any other invalid value here.
|
||||
deserialized = false;
|
||||
}
|
||||
if (! deserialized) {
|
||||
boost::nowide::cerr << "Invalid value for option --" << token.c_str() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (opt_base->type() == coBool) {
|
||||
if (value.empty())
|
||||
static_cast<ConfigOptionBool*>(opt_base)->value = !no;
|
||||
else
|
||||
opt_base->deserialize(value);
|
||||
else if (! opt_base->deserialize(normalize_cli_bool_value(value))) {
|
||||
boost::nowide::cerr << "Invalid value for option --" << token.c_str() << std::endl;
|
||||
return false;
|
||||
}
|
||||
} else if (opt_base->type() == coString) {
|
||||
// Do not unescape single string values, the unescaping is left to the calling shell.
|
||||
static_cast<ConfigOptionString*>(opt_base)->value = value;
|
||||
|
||||
@@ -11990,13 +11990,11 @@ CLIActionsConfigDef::CLIActionsConfigDef()
|
||||
def = this->add("load_defaultfila", coBool);
|
||||
def->label = L("Load default filaments");
|
||||
def->tooltip = L("Load first filament as default for those not loaded.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("min_save", coBool);
|
||||
def->label = L("Minimum save");
|
||||
def->tooltip = L("Export 3MF with minimum size.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("mtcpp", coInt);
|
||||
@@ -12022,7 +12020,6 @@ CLIActionsConfigDef::CLIActionsConfigDef()
|
||||
def = this->add("normative_check", coBool);
|
||||
def->label = L("Normative check");
|
||||
def->tooltip = L("Check the normative items.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
/*def = this->add("help_fff", coBool);
|
||||
@@ -12289,7 +12286,7 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
||||
def->cli_params = "level";
|
||||
def->set_default_value(new ConfigOptionInt(1));
|
||||
|
||||
def = this->add("logfile", coInt);
|
||||
def = this->add("logfile", coString);
|
||||
def->label = L("Log file");
|
||||
def->tooltip = L("Redirects debug logging to file.\n");
|
||||
def->cli_params = "file";
|
||||
@@ -12337,7 +12334,6 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
||||
def = this->add("skip_modified_gcodes", coBool);
|
||||
def->label = L("Skip modified G-code in 3MF");
|
||||
def->tooltip = L("Skip the modified G-code in 3MF from printer or filament presets.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("makerlab_name", coString);
|
||||
@@ -12367,14 +12363,12 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
||||
def = this->add("allow_newer_file", coBool);
|
||||
def->label = L("Allow 3MF with newer version to be sliced");
|
||||
def->tooltip = L("Allow 3MF with newer version to be sliced.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("allow_mix_temp", coBool);
|
||||
// internal use only, don't need translation
|
||||
def->label = "Allow filaments with high/low temperature to be printed together";
|
||||
def->tooltip = "Allow filaments with high/low temperature to be printed together.";
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user