Use parse_*_param() funcs to parse command line option parameters

Use consistently named functions parse_*_param() for all parameters of
command line options that are more complex to parse. And test them!

Also add print_version() function.
This commit is contained in:
Jochen Topf
2022-12-21 20:27:09 +01:00
parent a910b866c7
commit 1d7ac93a83
2 changed files with 256 additions and 137 deletions

View File

@ -294,14 +294,14 @@ options_t::options_t()
}
}
static osmium::Box parse_bbox(char const *bbox)
static osmium::Box parse_bbox_param(char const *arg)
{
double minx = NAN;
double maxx = NAN;
double miny = NAN;
double maxy = NAN;
int const n = sscanf(bbox, "%lf,%lf,%lf,%lf", &minx, &miny, &maxx, &maxy);
int const n = sscanf(arg, "%lf,%lf,%lf,%lf", &minx, &miny, &maxx, &maxy);
if (n != 4) {
throw std::runtime_error{"Bounding box must be specified like: "
"minlon,minlat,maxlon,maxlat."};
@ -322,7 +322,7 @@ static osmium::Box parse_bbox(char const *bbox)
return osmium::Box{minx, miny, maxx, maxy};
}
static unsigned int number_of_threads(char const *arg)
static unsigned int parse_number_processes_param(char const *arg)
{
int num = atoi(arg);
if (num < 1) {
@ -340,6 +340,118 @@ static unsigned int number_of_threads(char const *arg)
return static_cast<unsigned int>(num);
}
static void parse_expire_tiles_param(char const *arg,
uint32_t *expire_tiles_zoom_min,
uint32_t *expire_tiles_zoom)
{
if (!arg || arg[0] == '-') {
throw std::runtime_error{"Missing argument for option --expire-tiles."
" Zoom levels must be positive."};
}
char *next_char = nullptr;
*expire_tiles_zoom_min =
static_cast<uint32_t>(std::strtoul(arg, &next_char, 10));
if (*expire_tiles_zoom_min == 0) {
throw std::runtime_error{"Bad argument for option --expire-tiles."
" Minimum zoom level must be larger than 0."};
}
// The first character after the number is ignored because that is the
// separating hyphen.
if (*next_char == '-') {
++next_char;
// Second number must not be negative because zoom levels must be
// positive.
if (next_char && *next_char != '-' && isdigit(*next_char)) {
char *after_maxzoom = nullptr;
*expire_tiles_zoom = static_cast<uint32_t>(
std::strtoul(next_char, &after_maxzoom, 10));
if (*expire_tiles_zoom == 0 || *after_maxzoom != '\0') {
throw std::runtime_error{"Invalid maximum zoom level"
" given for tile expiry."};
}
} else {
throw std::runtime_error{
"Invalid maximum zoom level given for tile expiry."};
}
return;
}
if (*next_char == '\0') {
// end of string, no second zoom level given
*expire_tiles_zoom = *expire_tiles_zoom_min;
return;
}
throw std::runtime_error{"Minimum and maximum zoom level for"
" tile expiry must be separated by '-'."};
}
static void parse_log_level_param(char const *arg)
{
if (std::strcmp(arg, "debug") == 0) {
get_logger().set_level(log_level::debug);
} else if (std::strcmp(arg, "info") == 0) {
get_logger().set_level(log_level::info);
} else if ((std::strcmp(arg, "warn") == 0) ||
(std::strcmp(arg, "warning") == 0)) {
get_logger().set_level(log_level::warn);
} else if (std::strcmp(arg, "error") == 0) {
get_logger().set_level(log_level::error);
} else {
throw std::runtime_error{
"Unknown value for --log-level option: {}"_format(arg)};
}
}
static void parse_log_progress_param(char const *arg)
{
if (std::strcmp(arg, "true") == 0) {
get_logger().enable_progress();
} else if (std::strcmp(arg, "false") == 0) {
get_logger().disable_progress();
} else if (std::strcmp(arg, "auto") == 0) {
get_logger().auto_progress();
} else {
throw std::runtime_error{
"Unknown value for --log-progress option: {}"_format(arg)};
}
}
static bool parse_with_forward_dependencies_param(char const *arg)
{
if (std::strcmp(arg, "false") == 0) {
return false;
}
if (std::strcmp(arg, "true") == 0) {
return true;
}
throw std::runtime_error{
"Unknown value for --with-forward-dependencies option: {}\n"_format(
arg)};
}
static void print_version()
{
fmt::print(stderr, "Build: {}\n", get_build_type());
fmt::print(stderr, "Compiled using the following library versions:\n");
fmt::print(stderr, "Libosmium {}\n", LIBOSMIUM_VERSION_STRING);
fmt::print(stderr, "Proj {}\n", get_proj_version());
#ifdef HAVE_LUA
#ifdef HAVE_LUAJIT
fmt::print(stderr, "{} ({})\n", LUA_RELEASE, LUAJIT_VERSION);
#else
fmt::print(stderr, "{}\n", LUA_RELEASE);
#endif
#else
fmt::print(stderr, "Lua support not included\n");
#endif
}
options_t::options_t(int argc, char *argv[]) : options_t()
{
// If there are no command line arguments at all, show help.
@ -364,251 +476,174 @@ options_t::options_t(int argc, char *argv[]) : options_t()
//handle the current arg
switch (c) {
case 'a':
case 'a': // --append
append = true;
break;
case 'b':
bbox = parse_bbox(optarg);
case 'b': // --bbox
bbox = parse_bbox_param(optarg);
break;
case 'c':
case 'c': // --create
create = true;
break;
case 'v':
case 'v': // --verbose
help_verbose = true;
get_logger().set_level(log_level::debug);
break;
case 's':
case 's': // --slim
slim = true;
break;
case 'K':
case 'K': // --keep-coastlines
keep_coastlines = true;
break;
case 'l':
case 'l': // --latlong
projection = reprojection::create_projection(PROJ_LATLONG);
break;
case 'm':
case 'm': // --merc
projection = reprojection::create_projection(PROJ_SPHERE_MERC);
break;
case 'E':
case 'E': // --proj
#ifdef HAVE_GENERIC_PROJ
projection = reprojection::create_projection(atoi(optarg));
#else
throw std::runtime_error{"Generic projections not available."};
#endif
break;
case 'p':
case 'p': // --prefix
prefix = optarg;
check_identifier(prefix, "--prefix parameter");
break;
case 'd':
case 'd': // --database
database_options.db = optarg;
break;
case 'C':
case 'C': // --cache
cache = atoi(optarg);
break;
case 'U':
case 'U': // --username
database_options.username = optarg;
break;
case 'W':
case 'W': // --password
pass_prompt = true;
break;
case 'H':
case 'H': // --host
database_options.host = optarg;
break;
case 'P':
case 'P': // --port
database_options.port = optarg;
break;
case 'S':
case 'S': // --style
style = optarg;
break;
case 'i':
case 'i': // --tablespace-index
tblsmain_index = optarg;
tblsslim_index = tblsmain_index;
break;
case 200:
case 200: // --tablespace-slim-data
tblsslim_data = optarg;
break;
case 201:
case 201: // --tablespace-slim-index
tblsslim_index = optarg;
break;
case 202:
case 202: // --tablespace-main-data
tblsmain_data = optarg;
break;
case 203:
case 203: // --tablespace-main-index
tblsmain_index = optarg;
break;
case 'e':
if (!optarg || optarg[0] == '-') {
throw std::runtime_error{
"Missing argument for option --expire-tiles. Zoom "
"levels must be positive."};
}
char *next_char;
expire_tiles_zoom_min =
static_cast<uint32_t>(std::strtoul(optarg, &next_char, 10));
if (expire_tiles_zoom_min == 0) {
throw std::runtime_error{
"Bad argument for option --expire-tiles. "
"Minimum zoom level must be larger "
"than 0."};
}
// The first character after the number is ignored because that is the separating hyphen.
if (*next_char == '-') {
++next_char;
// Second number must not be negative because zoom levels must be positive.
if (next_char && *next_char != '-' && isdigit(*next_char)) {
char *after_maxzoom = nullptr;
expire_tiles_zoom = static_cast<uint32_t>(
std::strtoul(next_char, &after_maxzoom, 10));
if (expire_tiles_zoom == 0 || *after_maxzoom != '\0') {
throw std::runtime_error{"Invalid maximum zoom level "
"given for tile expiry."};
}
} else {
throw std::runtime_error{
"Invalid maximum zoom level given for tile expiry."};
}
} else if (*next_char == '\0') {
// end of string, no second zoom level given
expire_tiles_zoom = expire_tiles_zoom_min;
} else {
throw std::runtime_error{"Minimum and maximum zoom level for "
"tile expiry must be separated by "
"'-'."};
}
case 'e': // --expire-tiles
parse_expire_tiles_param(optarg, &expire_tiles_zoom_min,
&expire_tiles_zoom);
break;
case 'o':
case 'o': // --expire-output
expire_tiles_filename = optarg;
break;
case 214:
case 214: // --expire-bbox-size
expire_tiles_max_bbox = atof(optarg);
break;
case 'O':
case 'O': // --output
output_backend = optarg;
break;
case 'x':
case 'x': // --extra-attributes
extra_attributes = true;
break;
case 'k':
case 'k': // --hstore
if (hstore_mode != hstore_column::none) {
throw std::runtime_error{"You can not specify both --hstore "
"(-k) and --hstore-all (-j)."};
}
hstore_mode = hstore_column::norm;
break;
case 208:
case 208: // --hstore-match-only
hstore_match_only = true;
break;
case 'j':
case 'j': // --hstore-all
if (hstore_mode != hstore_column::none) {
throw std::runtime_error{"You can not specify both --hstore "
"(-k) and --hstore-all (-j)."};
}
hstore_mode = hstore_column::all;
break;
case 'z':
case 'z': // --hstore-column
hstore_columns.emplace_back(optarg);
break;
case 'G':
case 'G': // --multi-geometry
enable_multi = true;
break;
case 'r':
case 'r': // --input-reader
if (std::strcmp(optarg, "auto") != 0) {
input_format = optarg;
}
break;
case 'h':
case 'h': // --help
m_print_help = true;
break;
case 'I':
case 'I': // --disable-parallel-indexing
parallel_indexing = false;
break;
case 204:
case 204: // -cache-strategy
log_warn("Deprecated option --cache-strategy ignored");
break;
case 205:
num_procs = number_of_threads(optarg);
case 205: // --number-processes
num_procs = parse_number_processes_param(optarg);
break;
case 206:
case 206: // --drop
droptemp = true;
break;
case 'F':
case 'F': // --flat-nodes
flat_node_file = optarg;
break;
case 211:
case 211: // --hstore-add-index
enable_hstore_index = true;
break;
case 212:
case 212: // --tag-transform-script
tag_transform_script = optarg;
break;
case 213:
case 213: // --reproject-area
reproject_area = true;
break;
case 'V':
fmt::print(stderr, "Build: {}\n", get_build_type());
fmt::print(stderr, "Compiled using the following library versions:\n");
fmt::print(stderr, "Libosmium {}\n", LIBOSMIUM_VERSION_STRING);
fmt::print(stderr, "Proj {}\n", get_proj_version());
#ifdef HAVE_LUA
#ifdef HAVE_LUAJIT
fmt::print(stderr, "{} ({})\n", LUA_RELEASE, LUAJIT_VERSION);
#else
fmt::print(stderr, "{}\n", LUA_RELEASE);
#endif
#else
fmt::print(stderr, "Lua support not included\n");
#endif
case 'V': // --version
print_version();
exit(EXIT_SUCCESS);
break;
case 215:
case 215: // --middle-schema
middle_dbschema = optarg;
check_identifier(middle_dbschema, "--middle-schema parameter");
break;
case 216:
case 216: // --output-pgsql-schema
output_dbschema = optarg;
check_identifier(output_dbschema, "--output-pgsql-schema parameter");
break;
case 217:
if (std::strcmp(optarg, "false") == 0) {
with_forward_dependencies = false;
} else if (std::strcmp(optarg, "true") == 0) {
with_forward_dependencies = true;
} else {
throw std::runtime_error{
"Unknown value for --with-forward-dependencies option: {}\n"_format(
optarg)};
}
case 217: // --with-forward-dependencies=BOOL
with_forward_dependencies =
parse_with_forward_dependencies_param(optarg);
break;
case 300:
case 300: // --middle-way-node-index-id-shift
way_node_index_id_shift = atoi(optarg);
break;
case 400: // --log-level=LEVEL
if (std::strcmp(optarg, "debug") == 0) {
get_logger().set_level(log_level::debug);
} else if (std::strcmp(optarg, "info") == 0) {
get_logger().set_level(log_level::info);
} else if ((std::strcmp(optarg, "warn") == 0) ||
(std::strcmp(optarg, "warning") == 0)) {
get_logger().set_level(log_level::warn);
} else if (std::strcmp(optarg, "error") == 0) {
get_logger().set_level(log_level::error);
} else {
throw std::runtime_error{
"Unknown value for --log-level option: {}"_format(optarg)};
}
parse_log_level_param(optarg);
break;
case 401: // --log-progress=VALUE
if (std::strcmp(optarg, "true") == 0) {
get_logger().enable_progress();
} else if (std::strcmp(optarg, "false") == 0) {
get_logger().disable_progress();
} else if (std::strcmp(optarg, "auto") == 0) {
get_logger().auto_progress();
} else {
throw std::runtime_error{
"Unknown value for --log-progress option: {}"_format(
optarg)};
}
parse_log_progress_param(optarg);
break;
case 402: // --log-sql
get_logger().enable_sql();

View File

@ -15,7 +15,7 @@
#include "taginfo-impl.hpp"
#include "tagtransform.hpp"
char const *const TEST_PBF = "foo.pbf";
static char const *const TEST_PBF = "foo.pbf";
static void bad_opt(std::vector<char const *> opts, char const *msg)
{
@ -69,6 +69,47 @@ TEST_CASE("Lua styles", "[NoDB]")
#endif
}
TEST_CASE("Parsing bbox", "[NoDB]")
{
auto const opt1 = opt({"-b", "1.2,3.4,5.6,7.8"});
CHECK(opt1.bbox == osmium::Box{1.2, 3.4, 5.6, 7.8});
auto const opt2 = opt({"--bbox", "1.2,3.4,5.6,7.8"});
CHECK(opt2.bbox == osmium::Box{1.2, 3.4, 5.6, 7.8});
auto const opt3 = opt({"--bbox", "1.2, 3.4, 5.6, 7.8"});
CHECK(opt3.bbox == osmium::Box{1.2, 3.4, 5.6, 7.8});
}
TEST_CASE("Parsing bbox fails if coordinates in wrong order", "[NoDB]")
{
bad_opt({"--bbox", "1.0,2.0,0.0,0.0"}, "Bounding box failed due to");
}
TEST_CASE("Parsing bbox fails if wrong format", "[NoDB]")
{
bad_opt({"-b", "123"}, "Bounding box must be specified like:"
" minlon,minlat,maxlon,maxlat.");
}
TEST_CASE("Parsing number-processes", "[NoDB]")
{
auto const opt1 = opt({"--number-processes", "0"});
CHECK(opt1.num_procs == 1);
auto const opt2 = opt({"--number-processes", "1"});
CHECK(opt2.num_procs == 1);
auto const opt3 = opt({"--number-processes", "2"});
CHECK(opt3.num_procs == 2);
auto const opt4 = opt({"--number-processes", "32"});
CHECK(opt4.num_procs == 32);
auto const opt5 = opt({"--number-processes", "33"});
CHECK(opt5.num_procs == 32);
}
TEST_CASE("Parsing tile expiry zoom levels", "[NoDB]")
{
auto options = opt({"-e", "8-12"});
@ -122,3 +163,46 @@ TEST_CASE("Parsing tile expiry zoom levels fails", "[NoDB]")
"Bad argument for option --expire-tiles. Minimum zoom level "
"must be larger than 0.");
}
TEST_CASE("Parsing log-level", "[NoDB]")
{
opt({"--log-level", "debug"});
opt({"--log-level", "info"});
opt({"--log-level", "warn"});
opt({"--log-level", "warning"});
opt({"--log-level", "error"});
}
TEST_CASE("Parsing log-level fails for unknown level", "[NoDB]")
{
bad_opt({"--log-level", "foo"}, "Unknown value for --log-level option: ");
}
TEST_CASE("Parsing log-progress", "[NoDB]")
{
opt({"--log-progress", "true"});
opt({"--log-progress", "false"});
opt({"--log-progress", "auto"});
}
TEST_CASE("Parsing log-progress fails for unknown value", "[NoDB]")
{
bad_opt({"--log-progress", "foo"},
"Unknown value for --log-progress option: ");
}
TEST_CASE("Parsing with-forward-dependencies", "[NoDB]")
{
auto const opt1 = opt({"--with-forward-dependencies", "true"});
CHECK(opt1.with_forward_dependencies);
auto const opt2 = opt({"--with-forward-dependencies", "false"});
CHECK_FALSE(opt2.with_forward_dependencies);
}
TEST_CASE("Parsing with-forward-dependencies fails for unknown value", "[NoDB]")
{
bad_opt({"--with-forward-dependencies", "foo"},
"Unknown value for"
" --with-forward-dependencies option: ");
}