diff --git a/.gitignore b/.gitignore
index bf501f67e9..b6155e14ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,7 @@ compile_commands.json
# Ignore generated executables
dingusppc
dingusppc.exe
+dingusppc.ini
# Ignore system files
.DS_Store
diff --git a/main.cpp b/main.cpp
index cb7da1eed1..fe931db5e8 100644
--- a/main.cpp
+++ b/main.cpp
@@ -37,6 +37,7 @@ along with this program. If not, see .
#include
#include
#include
+#include
#include
#include
#include
@@ -106,6 +107,10 @@ int main(int argc, char** argv) {
CLI::App app(appDescription);
app.allow_windows_style_options(); /* we want Windows-style options */
app.allow_extras();
+ app.allow_config_extras(CLI::config_extras_mode::capture);
+ app.config_formatter(std::make_shared());
+ app.set_config("-c,--config", "dingusppc.ini",
+ "Read an INI config file (default: dingusppc.ini in working directory)", false);
bool realtime_enabled = false;
bool debugger_enabled = false;
@@ -140,7 +145,8 @@ int main(int argc, char** argv) {
"Send internal logging to stderr (instead of dingusppc.log)");
app.add_flag("--log-verbosity", log_verbosity,
"Adjust logging verbosity (default is 0 a.k.a. INFO)")
- ->check(CLI::Number);
+ ->check(CLI::Number)
+ ->configurable(false);
app.add_flag("--log-no-uptime", log_no_uptime,
"Disable the uptime preamble of logged messages");
@@ -166,6 +172,13 @@ int main(int argc, char** argv) {
list_cmd->add_option("machines", sub_arg, "List supported machines");
list_cmd->add_option("properties", sub_arg, "List available properties");
+ string write_config_path;
+ app.add_option("--write-config", write_config_path,
+ "Write current configuration to a file and exit")
+ ->expected(0, 1)
+ ->default_val("dingusppc.ini")
+ ->configurable(false);
+
CLI11_PARSE(app, argc, argv);
if (*list_cmd) {
@@ -179,6 +192,68 @@ int main(int argc, char** argv) {
return 0;
}
+ if (app.count("--write-config")) {
+ std::ofstream out(write_config_path);
+ if (!out) {
+ cerr << "Cannot open " << write_config_path << " for writing" << endl;
+ return 1;
+ }
+ out << app.config_to_str(false, true);
+
+ auto escape_ini_string = [](const std::string& input) {
+ std::string escaped;
+ escaped.reserve(input.size());
+ for (char ch : input) {
+ switch (ch) {
+ case '\\': escaped += "\\\\"; break;
+ case '"': escaped += "\\\""; break;
+ case '\n': escaped += "\\n"; break;
+ case '\r': escaped += "\\r"; break;
+ case '\t': escaped += "\\t"; break;
+ default: escaped.push_back(ch); break;
+ }
+ }
+ return escaped;
+ };
+
+ // Append passthrough machine properties from CLI (--name=value)
+ // or config file (bare name, value pairs)
+ auto extras = app.remaining(false);
+ for (size_t i = 0; i < extras.size(); i++) {
+ std::string key, val;
+ auto &arg = extras[i];
+ if (arg.substr(0, 2) == "--") {
+ auto eq = arg.find('=');
+ if (eq != std::string::npos) {
+ key = arg.substr(2, eq - 2);
+ val = arg.substr(eq + 1);
+ } else if (i + 1 < extras.size() &&
+ extras[i + 1].rfind("--", 0) != 0) {
+ key = arg.substr(2);
+ val = extras[++i];
+ } else {
+ key = arg.substr(2);
+ val = "true";
+ }
+ } else if (i + 1 < extras.size()) {
+ key = arg;
+ val = extras[++i];
+ } else {
+ continue;
+ }
+ // Quote values that contain spaces or special characters
+ bool needs_quote = val.find_first_of(" \t\n\r#;\"\\") != std::string::npos
+ || val.empty();
+ if (needs_quote)
+ out << key << "=\"" << escape_ini_string(val) << "\"\n";
+ else
+ out << key << "=" << val << "\n";
+ }
+
+ cout << "Configuration written to " << write_config_path << endl;
+ return 0;
+ }
+
if (debugger_enabled) {
execution_mode = debugger;
}
@@ -221,25 +296,29 @@ int main(int argc, char** argv) {
return 1;
}
- // Hook to allow properties to be read from the command-line, regardless
- // of when they are registered.
+ // Hook to allow properties to be read from the command-line or config
+ // file, regardless of when they are registered.
MachineFactory::get_setting_value = [&](const std::string& name) -> std::optional {
- CLI::App sa;
- sa.allow_extras();
-
- std::string value;
- sa.add_option("--" + name, value)->expected(0,1);
- try {
- sa.parse(app.remaining_for_passthrough());
- } catch (const CLI::Error& e) {
- ABORT_F("Cannot parse CLI: %s", e.get_name().c_str());
- }
-
- if (sa.count("--" + name) > 0) {
- return value;
- } else {
- return std::nullopt;
+ auto extras = app.remaining(false);
+ std::string dashed = "--" + name;
+ for (size_t i = 0; i < extras.size(); i++) {
+ auto &arg = extras[i];
+ // --name=value (from CLI)
+ if (arg.rfind(dashed + "=", 0) == 0) {
+ return arg.substr(dashed.size() + 1);
+ }
+ // --name value (from CLI, next element is the value)
+ if (arg == dashed && i + 1 < extras.size() &&
+ extras[i + 1].rfind("--", 0) != 0) {
+ return extras[i + 1];
+ }
+ // bare name from config file (next element is the value)
+ if (arg == name && arg.rfind("--", 0) != 0 &&
+ i + 1 < extras.size()) {
+ return extras[i + 1];
+ }
}
+ return std::nullopt;
};
if (MachineFactory::register_machine_settings(machine_str) < 0) {