module lunch.conf; import toml; import std.conv; import std.traits; private struct Name { string str; string toString() { return str; } } struct AppConfig { bool talkative; bool report_errors; string report_url; string config_name; bool load_config; bool auto_launch; bool auto_update; bool check_update; } struct UIConfig { bool enabled; bool launch; bool update; bool config_dump; bool exit; string message; string launch_label; string update_label; string update_avail_label; string config_dump_label; string exit_label; } struct LaunchConfig { string[] command; string workdir; bool use_script; string entry_file; string source_dir; version (Windows): bool hide_when_running; } struct UpdateConfig { string seedurl; string workdir; size_t parallel_downloads; string[] exclude; } struct Config { @Name("application") AppConfig app; @Name("interface") UIConfig ui; LaunchConfig launcher; UpdateConfig updater; } immutable string default_config_toml = import("config.toml"); immutable Config default_config; immutable Config config; private string dash2minus(string str) { string newStr = ""; foreach (c; str) { if (c == '_') newStr ~= '-'; else newStr ~= c; } return newStr; } void wrapTOML(T)(ref T config, TOMLValue[string] toml, bool ignore_missing = false) @safe { static foreach (name; __traits(allMembers, T)) {{ alias member = __traits(getMember, config, name); alias type = typeof(__traits(getMember, T, name)); type* field = &__traits(getMember, config, name); static if (hasUDA!(member, Name)) enum key = getUDAs!(member, Name)[0].str; else enum key = name.dash2minus; TOMLValue* value = key in toml; if (!value) { if (!ignore_missing) throw new Exception("Missing field \""~key~"\" of type "~type.stringof); } else { static if (isSomeString!type) { *field = value.str.to!type; } else static if (is(type == string[])) { *field = []; foreach (_, entry; value.array) *field ~= entry.str; } else static if (is(type == bool)) { *field = value.boolean; } else static if (isIntegral!type) { *field = value.integer.to!type; } else static if (isFloatingPoint!type) { *field = value.floating.to!type; } else { wrapTOML(*field, value.table, ignore_missing); } } }} } Config parseConfig(string toml) @safe { Config config; TOMLDocument doc = parseTOML(toml); wrapTOML(config, doc); return config; } shared static this() { default_config = cast(immutable)parseConfig(default_config_toml); config = default_config; }