diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index ccb357cf2..66b3d0445 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -106,7 +106,6 @@ bool unescape_string_cstyle(const std::string &str, std::string &str_out) bool unescape_strings_cstyle(const std::string &str, std::vector &out) { - out.clear(); if (str.empty()) return true; @@ -500,56 +499,109 @@ DynamicConfig::erase(const t_config_option_key &opt_key) { this->options.erase(opt_key); } +void +DynamicConfig::read_cli(const std::vector &tokens, t_config_option_keys* extra) +{ + std::vector _argv; + + // push a bogus executable name (argv[0]) + _argv.push_back(""); + + for (size_t i = 0; i < tokens.size(); ++i) + _argv.push_back(const_cast(tokens[i].c_str())); + + this->read_cli(_argv.size(), &_argv[0], extra); +} + void DynamicConfig::read_cli(const int argc, const char** argv, t_config_option_keys* extra) { + // cache the CLI option => opt_key mapping + std::map opts; + for (const auto &oit : this->def->options) { + std::string cli = oit.second.cli; + cli = cli.substr(0, cli.find("=")); + boost::trim_right_if(cli, boost::is_any_of("!")); + std::vector tokens; + boost::split(tokens, cli, boost::is_any_of("|")); + for (const std::string &t : tokens) + opts[t] = oit.first; + } + bool parse_options = true; for (int i = 1; i < argc; ++i) { std::string token = argv[i]; + // Store non-option arguments in the provided vector. + if (!parse_options || !boost::starts_with(token, "-")) { + extra->push_back(token); + continue; + } + + + // Stop parsing tokens as options when -- is supplied. if (token == "--") { - // stop parsing tokens as options parse_options = false; - } else if (parse_options && boost::starts_with(token, "-")) { - boost::algorithm::trim_left_if(token, boost::algorithm::is_any_of("-")); - // TODO: handle --key=value - - // look for the option def - t_config_option_key opt_key; - const ConfigOptionDef* optdef; - for (t_optiondef_map::const_iterator oit = this->def->options.begin(); - oit != this->def->options.end(); ++oit) { - optdef = &oit->second; - - if (optdef->cli == token - || optdef->cli == token + '!' - || boost::starts_with(optdef->cli, token + "=") - || boost::starts_with(optdef->cli, token + "|") - || (token.length() == 1 && boost::contains(optdef->cli, "|" + token))) { - opt_key = oit->first; - break; - } + continue; + } + + // Remove leading dashes + boost::trim_left_if(token, boost::is_any_of("-")); + + // Remove the "no-" prefix used to negate boolean options. + bool no = false; + if (boost::starts_with(token, "no-")) { + no = true; + boost::replace_first(token, "no-", ""); + } + + // Read value when supplied in the --key=value form. + std::string value; + { + size_t equals_pos = token.find("="); + if (equals_pos != std::string::npos) { + value = token.substr(equals_pos+1); + token.erase(equals_pos); } - - if (opt_key.empty()) { - printf("Warning: unknown option --%s\n", token.c_str()); + } + + // Look for the cli -> option mapping. + const auto it = opts.find(token); + if (it == opts.end()) { + printf("Warning: unknown option --%s\n", token.c_str()); + continue; + } + const t_config_option_key opt_key = it->second; + const ConfigOptionDef &optdef = this->def->options.at(opt_key); + + // If the option type expects a value and it was not already provided, + // look for it in the next token. + if (optdef.type != coBool && optdef.type != coBools && value.empty()) { + if (i == (argc-1)) { + printf("No value supplied for --%s\n", token.c_str()); continue; } - - if (ConfigOptionBool* opt = this->opt(opt_key, true)) { - opt->value = !boost::starts_with(token, "no-"); - } else if (ConfigOptionBools* opt = this->opt(opt_key, true)) { - opt->values.push_back(!boost::starts_with(token, "no-")); - } else { - // we expect one more token carrying the value - if (i == (argc-1)) { - printf("No value supplied for --%s\n", token.c_str()); - exit(1); - } - this->set_deserialize(opt_key, argv[++i], true); - } + value = argv[++i]; + } + + // Store the option value. + const bool existing = this->has(opt_key); + if (ConfigOptionBool* opt = this->opt(opt_key, true)) { + opt->value = !no; + } else if (ConfigOptionBools* opt = this->opt(opt_key, true)) { + if (!existing) opt->values.clear(); // remove the default values + opt->values.push_back(!no); + } else if (ConfigOptionStrings* opt = this->opt(opt_key, true)) { + if (!existing) opt->values.clear(); // remove the default values + opt->deserialize(value, true); + } else if (ConfigOptionFloats* opt = this->opt(opt_key, true)) { + if (!existing) opt->values.clear(); // remove the default values + opt->deserialize(value, true); + } else if (ConfigOptionPoints* opt = this->opt(opt_key, true)) { + if (!existing) opt->values.clear(); // remove the default values + opt->deserialize(value, true); } else { - extra->push_back(token); + this->set_deserialize(opt_key, value, true); } } } diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 8e375701e..32a80adee 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -12,6 +12,9 @@ #include #include "libslic3r.h" #include "Point.hpp" +#include +#include +#include namespace Slic3r { @@ -248,6 +251,7 @@ class ConfigOptionStrings : public ConfigOptionVector }; bool deserialize(std::string str, bool append = false) { + if (!append) this->values.clear(); return unescape_strings_cstyle(str, this->values); }; }; @@ -392,20 +396,23 @@ class ConfigOptionPoints : public ConfigOptionVector bool deserialize(std::string str, bool append = false) { if (!append) this->values.clear(); - std::istringstream is(str); - std::string point_str; - while (std::getline(is, point_str, ',')) { - Pointf point; - std::istringstream iss(point_str); - std::string coord_str; - if (std::getline(iss, coord_str, 'x')) { - std::istringstream(coord_str) >> point.x; - if (std::getline(iss, coord_str, 'x')) { - std::istringstream(coord_str) >> point.y; - } + + std::vector tokens; + boost::split(tokens, str, boost::is_any_of("x,")); + if (tokens.size() % 2) return false; + + try { + for (size_t i = 0; i < tokens.size(); ++i) { + Pointf point; + point.x = boost::lexical_cast(tokens[i]); + point.y = boost::lexical_cast(tokens[++i]); + this->values.push_back(point); } - this->values.push_back(point); + } catch (boost::bad_lexical_cast &e) { + printf("%s\n", e.what()); + return false; } + return true; }; }; @@ -691,6 +698,7 @@ class DynamicConfig : public virtual ConfigBase virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false); t_config_option_keys keys() const; void erase(const t_config_option_key &opt_key); + void read_cli(const std::vector &tokens, t_config_option_keys* extra); void read_cli(const int argc, const char **argv, t_config_option_keys* extra); private: diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 256fa539f..214baad9f 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 147; +use Test::More tests => 159; use Data::Dumper; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { @@ -251,4 +251,45 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo ok 1, 'did not crash on reading invalid items in config'; } +{ + my $parse = sub { + my @argv = @_; + my $config = Slic3r::Config->new; + $config->read_cli(\@argv); + return $config; + }; + { + my $config = $parse->(qw(--extra-perimeters --perimeters 1 --layer-height 0.45 + --fill-density 70% --detect-bridging-perimeters --notes=foobar)); + is $config->get('extra_perimeters'), 1, 'read_cli(): bool'; + is $config->get('perimeters'), 1, 'read_cli(): int'; + is $config->get('layer_height'), 0.45, 'read_cli(): float'; + is $config->serialize('fill_density'), '70%', 'read_cli(): percent'; + is $config->get('overhangs'), 1, 'read_cli(): alternative'; + is $config->get('notes'), 'foobar', 'read_cli(): key=val'; + } + { + my $config = $parse->(qw(--extra-perimeters --no-extra-perimeters)); + ok $config->has('extra_perimeters'), 'read_cli(): negated bool'; + is_deeply $config->get('extra_perimeters'), 0, 'read_cli(): negated bool'; + } + { + my $config = $parse->(qw(--wipe --no-wipe --wipe)); + is_deeply $config->get('wipe'), [1,0,1], 'read_cli(): bools array'; + } + { + my $config = $parse->(qw(--post-process foo --post-process bar)); + is_deeply $config->get('post_process'), ['foo', 'bar'], 'read_cli(): strings array'; + } + { + my $config = $parse->(qw(--retract-speed 0.4 --retract-speed 0.5)); + is_deeply $config->get('retract_speed'), [0.4, 0.5], 'read_cli(): floats array'; + } + { + my $config = $parse->(qw(--extruder-offset 0,0 --extruder-offset 10x5)); + is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], + [[0,0], [10,5]], 'read_cli(): points array'; + } +} + __END__ diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 96c4830e0..6b4fb1950 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -40,6 +40,8 @@ double min_object_distance(); %name{_load} void load(std::string file); %name{_save} void save(std::string file); + std::vector read_cli(std::vector _argv) + %code{% THIS->read_cli(_argv, &RETVAL); %}; }; %name{Slic3r::Config::Static} class StaticPrintConfig {