Complete implementation of read_cli()

This commit is contained in:
Alessandro Ranellucci 2017-03-18 00:48:24 +01:00
parent c9f0a4c934
commit 90395f9394
4 changed files with 154 additions and 51 deletions

View File

@ -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<std::string> &out) bool unescape_strings_cstyle(const std::string &str, std::vector<std::string> &out)
{ {
out.clear();
if (str.empty()) if (str.empty())
return true; return true;
@ -500,56 +499,109 @@ DynamicConfig::erase(const t_config_option_key &opt_key) {
this->options.erase(opt_key); this->options.erase(opt_key);
} }
void
DynamicConfig::read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra)
{
std::vector<const char*> _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<const char*>(tokens[i].c_str()));
this->read_cli(_argv.size(), &_argv[0], extra);
}
void void
DynamicConfig::read_cli(const int argc, const char** argv, t_config_option_keys* extra) DynamicConfig::read_cli(const int argc, const char** argv, t_config_option_keys* extra)
{ {
// cache the CLI option => opt_key mapping
std::map<std::string,std::string> 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<std::string> tokens;
boost::split(tokens, cli, boost::is_any_of("|"));
for (const std::string &t : tokens)
opts[t] = oit.first;
}
bool parse_options = true; bool parse_options = true;
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
std::string token = argv[i]; std::string token = argv[i];
if (token == "--") { // Store non-option arguments in the provided vector.
// stop parsing tokens as options if (!parse_options || !boost::starts_with(token, "-")) {
parse_options = false; extra->push_back(token);
} 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;
}
}
if (opt_key.empty()) {
printf("Warning: unknown option --%s\n", token.c_str());
continue; continue;
} }
if (ConfigOptionBool* opt = this->opt<ConfigOptionBool>(opt_key, true)) {
opt->value = !boost::starts_with(token, "no-"); // Stop parsing tokens as options when -- is supplied.
} else if (ConfigOptionBools* opt = this->opt<ConfigOptionBools>(opt_key, true)) { if (token == "--") {
opt->values.push_back(!boost::starts_with(token, "no-")); parse_options = false;
} else { continue;
// we expect one more token carrying the value }
// 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);
}
}
// 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)) { if (i == (argc-1)) {
printf("No value supplied for --%s\n", token.c_str()); printf("No value supplied for --%s\n", token.c_str());
exit(1); continue;
} }
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<ConfigOptionBool>(opt_key, true)) {
opt->value = !no;
} else if (ConfigOptionBools* opt = this->opt<ConfigOptionBools>(opt_key, true)) {
if (!existing) opt->values.clear(); // remove the default values
opt->values.push_back(!no);
} else if (ConfigOptionStrings* opt = this->opt<ConfigOptionStrings>(opt_key, true)) {
if (!existing) opt->values.clear(); // remove the default values
opt->deserialize(value, true);
} else if (ConfigOptionFloats* opt = this->opt<ConfigOptionFloats>(opt_key, true)) {
if (!existing) opt->values.clear(); // remove the default values
opt->deserialize(value, true);
} else if (ConfigOptionPoints* opt = this->opt<ConfigOptionPoints>(opt_key, true)) {
if (!existing) opt->values.clear(); // remove the default values
opt->deserialize(value, true);
} else { } else {
extra->push_back(token); this->set_deserialize(opt_key, value, true);
} }
} }
} }

View File

@ -12,6 +12,9 @@
#include <vector> #include <vector>
#include "libslic3r.h" #include "libslic3r.h"
#include "Point.hpp" #include "Point.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/lexical_cast.hpp>
namespace Slic3r { namespace Slic3r {
@ -248,6 +251,7 @@ class ConfigOptionStrings : public ConfigOptionVector<std::string>
}; };
bool deserialize(std::string str, bool append = false) { bool deserialize(std::string str, bool append = false) {
if (!append) this->values.clear();
return unescape_strings_cstyle(str, this->values); return unescape_strings_cstyle(str, this->values);
}; };
}; };
@ -392,20 +396,23 @@ class ConfigOptionPoints : public ConfigOptionVector<Pointf>
bool deserialize(std::string str, bool append = false) { bool deserialize(std::string str, bool append = false) {
if (!append) this->values.clear(); if (!append) this->values.clear();
std::istringstream is(str);
std::string point_str; std::vector<std::string> tokens;
while (std::getline(is, point_str, ',')) { 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; Pointf point;
std::istringstream iss(point_str); point.x = boost::lexical_cast<coordf_t>(tokens[i]);
std::string coord_str; point.y = boost::lexical_cast<coordf_t>(tokens[++i]);
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;
}
}
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; return true;
}; };
}; };
@ -691,6 +698,7 @@ class DynamicConfig : public virtual ConfigBase
virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false); virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false);
t_config_option_keys keys() const; t_config_option_keys keys() const;
void erase(const t_config_option_key &opt_key); void erase(const t_config_option_key &opt_key);
void read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra);
void read_cli(const int argc, const char **argv, t_config_option_keys* extra); void read_cli(const int argc, const char **argv, t_config_option_keys* extra);
private: private:

View File

@ -4,7 +4,7 @@ use strict;
use warnings; use warnings;
use Slic3r::XS; use Slic3r::XS;
use Test::More tests => 147; use Test::More tests => 159;
use Data::Dumper; use Data::Dumper;
foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { 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'; 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__ __END__

View File

@ -40,6 +40,8 @@
double min_object_distance(); double min_object_distance();
%name{_load} void load(std::string file); %name{_load} void load(std::string file);
%name{_save} void save(std::string file); %name{_save} void save(std::string file);
std::vector<std::string> read_cli(std::vector<std::string> _argv)
%code{% THIS->read_cli(_argv, &RETVAL); %};
}; };
%name{Slic3r::Config::Static} class StaticPrintConfig { %name{Slic3r::Config::Static} class StaticPrintConfig {