Skip to content

Commit

Permalink
Revert "No longer depend on getopt_long()."
Browse files Browse the repository at this point in the history
This reverts commit c1b9200.
  • Loading branch information
dillof committed Jan 18, 2024
1 parent c1b9200 commit 27f9f0c
Show file tree
Hide file tree
Showing 10 changed files with 738 additions and 138 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ add_dependencies(distcheck dist)
check_function_exists(MD5Init HAVE_MD5INIT)
check_function_exists(SHA1Init HAVE_SHA1INIT)
check_function_exists(fnmatch HAVE_FNMATCH)
check_function_exists(getopt_long HAVE_GETOPT_LONG)
check_function_exists(getprogname HAVE_GETPROGNAME)
check_symbol_exists(_stricmp string.h HAVE__STRICMP)
check_symbol_exists(strcasecmp strings.h HAVE_STRCASECMP)

Expand Down
2 changes: 2 additions & 0 deletions cmake-config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#cmakedefine HAVE_TOMLPLUSPLUS

#cmakedefine HAVE_FNMATCH
#cmakedefine HAVE_GETOPT_LONG
#cmakedefine HAVE_GETPROGNAME
#cmakedefine HAVE_MD5INIT
#cmakedefine HAVE_SHA1INIT
#cmakedefine HAVE_STRCASECMP
Expand Down
102 changes: 68 additions & 34 deletions regress/dbrestore.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,61 +66,95 @@ static int restore_table(sqlite3 *db, FILE *f);
static void unget_line(const std::string &line);
static std::vector<std::string> split(const std::string &string, const std::string &separaptor, bool strip_whitespace = false);

std::vector<Commandline::Option> dbrestore_options = {
Commandline::Option("db-version", "version", "specify DB schema version"),
Commandline::Option("sql","file", "take SQL schema from FILE"),
Commandline::Option("type", 't', "type", "specify type of database: mamedb (default) or ckmamedb)")

#define QUERY_COLS_FMT "pragma table_info(%s)"

const char *usage = "usage: %s [-hV] [--db-version VERSION] [--sql SQL_INIT_FILE] [-t db-type] dump-file db-file\n";

char help_head[] = PACKAGE " by Dieter Baron and Thomas Klausner\n\n";

char help[] = "\n"
" --db-version VERSION specify version of database schema\n"
" -h, --help display this help message\n"
" --sql SQL_INIT_FILE use table definitions from this SQL init file\n"
" -t, --type TYPE restore database of type TYPE (ckmamedb, mamedb, memdb)\n"
" -V, --version display version number\n"
"\nReport bugs to " PACKAGE_BUGREPORT ".\n";

char version_string[] = PACKAGE " " VERSION "\n"
"Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner\n" PACKAGE " comes with ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n";


#define OPTIONS "ht:V"

enum {
OPT_DB_VERSION = 256,
OPT_SQL
};

struct option options[] = {
{"db-version", 1, 0, OPT_DB_VERSION },
{"help", 0, 0, 'h'},
{"sql", 1, 0, OPT_SQL },
{"type", 1, 0, 't'},
{"version", 0, 0, 'V'}
};

#define PROGRAM_NAME "dbrestore"

int main(int argc, char *argv[]) {
setprogname(argv[0]);

DBType type = DBTYPE_ROMDB;
std::string sql_file;
int db_version = -1;

const char *header = PROGRAM_NAME " by Dieter Baron and Thomas Klausner";
const char *footer = "Report bugs to " PACKAGE_BUGREPORT ".";
const char *version = "dumpgame (" PACKAGE " " VERSION ")\nCopyright (C) 2021-2024 Dieter Baron and Thomas Klausner\n"
PACKAGE " " VERSION "\n"
PACKAGE " comes with ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n";

auto commandline = Commandline(dbrestore_options, "dump-file db-file", header, footer, version);

auto arguments = commandline.parse(argc, argv);

for (const auto& option: arguments.options) {
if (option.name == "db-version") {
opterr = 0;
int c;
while ((c = getopt_long(argc, argv, OPTIONS, options, 0)) != EOF) {
switch (c) {
case 'h':
fputs(help_head, stdout);
printf(usage, getprogname());
fputs(help, stdout);
exit(0);
case 'V':
fputs(version_string, stdout);
exit(0);

case 't':
if ((type = db_type(optarg)) == DBTYPE_INVALID) {
fprintf(stderr, "%s: unknown db type '%s'\n", getprogname(), optarg);
exit(1);
}
break;

case OPT_DB_VERSION:
try {
size_t idx;
db_version = std::stoi(option.argument, &idx);
if (option.argument[idx] != '\0') {
db_version = std::stoi(optarg, &idx);
if (optarg[idx] != '\0') {
throw std::invalid_argument("");
}
}
catch (...) {
fprintf(stderr, "%s: invalid DB schema version '%s'\n", getprogname(), option.argument.c_str());
fprintf(stderr, "%s: invalid DB schema version '%s'\n", getprogname(), optarg);
exit(1);
}
}
else if (option.name == "sql") {
sql_file = option.argument;
}
else if (option.name == "type") {
if ((type = db_type(option.argument)) == DBTYPE_INVALID) {
fprintf(stderr, "%s: unknown db type '%s'\n", getprogname(), option.argument.c_str());
exit(1);
}
}
break;

case OPT_SQL:
sql_file = optarg;
break;
}
}

if (arguments.arguments.size() != 2) {
commandline.usage(false, stderr);
if (optind != argc - 2) {
fprintf(stderr, usage, getprogname());
exit(1);
}

std::string dump_fname = arguments.arguments[0];
std::string db_fname = arguments.arguments[1];
std::string dump_fname = argv[optind];
std::string db_fname = argv[optind + 1];

output.set_error_file(dump_fname);

Expand Down
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
if(NOT HAVE_GETPROGRAME)
list(APPEND COMPATIBILITY getprogname.cc)
endif()
if(NOT HAVE_MD5INIT)
list(APPEND COMPATIBILITY md5.cc)
endif()
Expand Down
182 changes: 82 additions & 100 deletions src/Commandline.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Commandline.cc -- parse command line options and arguments
Commandline.h -- parse command line options and arguments
Copyright (C) 2021 Dieter Baron and Thomas Klausner
This file is part of ckmame, a program to check rom sets for MAME.
Expand Down Expand Up @@ -36,119 +36,101 @@
#include <algorithm>
#include <cctype>
#include <sstream>
#include <iostream>
#include <unordered_map>
#include <utility>

#include "compat.h"

#include "Exception.h"

extern int optind;

Commandline::Commandline(std::vector<Option> options_, std::string arguments_, std::string header_, std::string footer_, std::string version_) : options(std::move(options_)), arguments(std::move(arguments_)), header(std::move(header_)), footer(std::move(footer_)), version(std::move(version_)), options_sorted(false) {
add_option(Option("help", 'h', "display this help message"));
add_option(Option("version", 'V', "display version integer"));
add_option(Option("version", 'V', "display version number"));
}


ParsedCommandline Commandline::parse(int argc, char *const *argv) {
program_name = argv[0];

std::unordered_map<char, const Option*> short_options;
std::unordered_map<std::string, const Option*> long_options;

for (const auto& option: options) {
if (option.short_name) {
short_options[*option.short_name] = &option;
std::string short_options;
std::vector<struct option> long_options;
std::unordered_map<int, size_t> option_indices;
int next_index = 256;

for (const auto &option : options) {
//#define DEBUG_OPTIONS
#ifdef DEBUG_OPTIONS
printf("option '%s'", option.name.c_str());
if (option.short_name.has_value()) {
printf("/'%c'", option.short_name.value());
}
if (option.has_argument()) {
printf(", argument '%s'", option.argument_name.c_str());
}
long_options[option.name] = &option;
printf(", description '%s'\n", option.description.c_str());
#endif
int index;
if (option.short_name.has_value()) {
index = option.short_name.value();
short_options += option.short_name.value();
if (option.has_argument()) {
short_options += ":";
}
}
else {
index = next_index++;
}

option_indices[index] = long_options.size();
struct option long_option = {
option.name.c_str(), option.has_argument() ? 1 : 0, nullptr, index
};
long_options.push_back(long_option);
}


#ifdef DEBUG_OPTIONS
printf("short options: '%s'\n", short_options.c_str());
#endif
struct option terminator = { nullptr, 0, nullptr, 0 };
long_options.push_back(terminator);

auto parsed_commandline = ParsedCommandline();

auto in_options = true;

for (int index = 1; index < argc; index += 1) {
auto argument = std::string(argv[index]);

if (in_options) {
if (argument.size() >=2 && argument[0] == '-' && argument[1] == '-' /* argument.starts_with("--") */) {
auto equals = argument.find('=');
auto option_name = argument.substr(2, equals == std::string::npos ? equals : equals - 2);
auto it = long_options.find(option_name);
if (it == long_options.end()) {
usage(false, stderr);
std::cerr << "unknown option '--" << option_name << "'\n";
exit(1);
}
auto option_argument = std::string{};
if (equals != std::string::npos) {
if (it->second->has_argument()) {
parsed_commandline.add_option(option_name, argument.substr(equals + 1));
}
else {
usage(false, stderr);
std::cerr << "option '--" << option_name << "' doesn't take an argument\n";
exit(1);
}
}
else {
if (it->second->has_argument()) {
if (index == argc - 1) {
usage(false, stderr);
std::cerr << "missing argument for option '--" << option_name << "'\n";
exit(1);
}
index += 1;
parsed_commandline.add_option(option_name, argv[index]);
}
else {
parsed_commandline.add_option(option_name, "");
}
}
}
else if (argument.size() >= 1 && argument[0] == '-' /* argument.starts_with('-') */) {
for (size_t position = 1; position < argument.size(); position += 1) {
auto option_name = argument[position];
auto it = short_options.find(option_name);
if (it == short_options.end()) {
usage(false, stderr);
std::cerr << "unknown option '-" << option_name << "'\n";
exit(1);
}
if (it->second->has_argument()) {
position += 1;
if (position < argument.size()) {
parsed_commandline.add_option(it->second->name, argument.substr(position));
}
else {
if (index == argc - 1) {
usage(false, stderr);
std::cerr << "missing argument for option '-" << option_name << "'\n";
exit(1);
}
index += 1;
parsed_commandline.add_option(it->second->name, argv[index]);
}
break;
}
else {
parsed_commandline.add_option(it->second->name, "");
}
}
}
else {
in_options = false;
}
opterr = 0;
int c;
while ((c = getopt_long(argc, argv, short_options.c_str(), long_options.data(), nullptr)) != EOF) {
if (c == '?') {
usage(false, stderr);
fprintf(stderr, "unknown option\n"); // TODO: include unknown option, how to get that information?
exit(1);
}

if (!in_options) {
parsed_commandline.arguments.emplace_back(argument);
if (c == ':') {
usage(false, stderr);
fprintf(stderr, "option missing argument\n"); // TODO: include unknown option, how to get that information?
exit(1);
}

auto it = option_indices.find(c);
if (it == option_indices.end()) {
usage(false, stderr);
fprintf(stderr, "unknown option '%c'\n", c);
exit(1);
}
const auto &option = long_options[it->second];

parsed_commandline.options.emplace_back(option.name, option.has_arg ? optarg : "");
}

for (auto i = optind; i < argc; i++) {
parsed_commandline.arguments.emplace_back(argv[i]);
}

if (parsed_commandline.find_first("help").has_value()) {
usage(true);
exit(0);
usage(true);
exit(0);
}
if (parsed_commandline.find_first("version").has_value()) {
std::cout << version << std::endl;
exit(0);
fputs(version.c_str(), stdout);
exit(0);
}

return parsed_commandline;
Expand All @@ -168,7 +150,7 @@ std::optional<std::string> ParsedCommandline::find_first(const std::string &name
[[maybe_unused]] std::optional<std::string> ParsedCommandline::find_last(const std::string &name) const {
for (auto it = options.rbegin(); it != options.rend(); it++) {
const auto &option = *it;

if (option.name == name) {
return option.argument;
}
Expand Down Expand Up @@ -199,7 +181,7 @@ void Commandline::usage(bool full, FILE *fout) {
fprintf(fout, "%s\n\n", header.c_str());
}

fprintf(fout, "Usage: %s", program_name.c_str());
fprintf(fout, "Usage: %s", getprogname());
if (!short_options_without_argument.str().empty()) {
fprintf(fout, " [-%s]", short_options_without_argument.str().c_str());
}
Expand Down Expand Up @@ -256,8 +238,8 @@ void Commandline::add_option(Commandline::Option option) {

void Commandline::sort_options() {
if (!options_sorted) {
std::sort(options.begin(), options.end());
options_sorted = true;
std::sort(options.begin(), options.end());
options_sorted = true;
}
}

Expand Down Expand Up @@ -288,7 +270,7 @@ bool Commandline::Option::operator<(const Option &other) const {
return compare_char(name[0], other.short_name.value()) < 0;
}
else {
return std::lexicographical_compare(name.begin(), name.end(), other.name.begin(), other.name.end());
return strcasecmp(name.c_str(), other.name.c_str()) < 0;
}
}
}
Loading

0 comments on commit 27f9f0c

Please sign in to comment.