diff options
Diffstat (limited to 'configreader.vala')
-rw-r--r-- | configreader.vala | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/configreader.vala b/configreader.vala new file mode 100644 index 0000000..a57a7ab --- /dev/null +++ b/configreader.vala @@ -0,0 +1,188 @@ +// What follows is arguably the least readily understandable part of +// weltschmerz; some pointers and design notes shall follow. +// +// The purpose of this class is to read the key-value configuration file from +// disk and turn it into an in-memory representation of GLib's KeyFile +// structure. We do not stray from this format, yet the error handling and some +// design implications make it a bit harder than *just* parsing a file. +// +// In particular, a core design decision is to provide reasonable defaults to +// the user and to, wherever possible, fall back to a configuration that is at +// least useable. Short of a bug in GLib's KeyFile implementation, this code +// *should* gracefully deal with any sort of input: a missing configuration +// file, a corrupted or wrongly encoded configuration file, etc. +// +// Another decision is to report any noteworthy error to the user, and to make +// sure that issues with the configuration file are reported as accurately as +// possible. This includes particularly the reporting of entries that were +// *not* accessed by the program at all, which can indicate a small oversight +// like a typo, or a deprecated config entry. +// +// Most of the information in this file based on the official documentation, +// but a few parts (the ones there is no documentation for) are based on my +// reading of the GLib C code and live testing. Therefore, I cannot guarantee +// complete accuracy. +// +// Notable GLib resources: +// https://valadoc.org/glib-2.0/GLib.KeyFile.html +// https://valadoc.org/glib-2.0/GLib.KeyFileError.html + +class ConfigReader { + KeyFile keyfile = new KeyFile(); + string[] warnings; + + public ConfigReader (string path) { + try { + keyfile.load_from_file(path, NONE); + } catch (Error e) { + // The GLib documentation does not make this clear, but the only + // KeyFileErrors that will be reported when loading the file from + // disk are KeyFileError.PARSE and KeyFileError.UNKNOWN_ENCODING + // + // KeyFileError.NOT_FOUND will never be returned, FileError.NOENT + // takes its place instead. Since we can gracefully fall back to + // default values, we specifically ignore this error. + if (e is FileError.NOENT) + return; + + // We want to warn the user about any other error, and set keyfile + // to null. At this point the file has already been fully parsed + // by GLib, but we do not trust it to have read anything correctly + // at this point, so we destroy the in-memory representation. + append_warning(e.message); + keyfile = null; + } + } + + public string[] get_warnings() { + return warnings; + } + + void append_warning(string message) { + warning(message); + warnings += "• " + Markup.escape_text(message); + } + + // This method is called from all three read_* methods if they encountered + // any KeyFileErrors whilst accessing keys in keyfile. At this point, the + // following KeyFileErrors are possible: INVALID_VALUE, UNKNOWN_ENCODING, + // KEY_NOT_FOUND, and GROUP_NOT_FOUND. + // + // The latter two are insignificant since we can fall back to default + // values. + // + // INVALID_VALUE is treated specially here to remove a potentially + // confusing warning message about unknown keys (see log_unknown_keys). + // + // Finally, UNKNOWN_ENCODING is treated normally. + void handle_error(KeyFileError e, string group, string key) { + if (e is KeyFileError.INVALID_VALUE) { + try { + // Remove a known key with an invalid value + keyfile.remove_key(group, key); + } catch (KeyFileError e) { + debug("Could not remove existing and valid key entry"); + } + } else if (e is KeyFileError.UNKNOWN_ENCODING) { + append_warning(e.message); + } + } + + // In order to keep track of entries that were not accessed by the program + // at all, we remove all successfully read entries from the KeyFile. Any + // entries that are left over have not been used and can then be reported + // to the user. + public void log_unknown_keys() { + if (keyfile == null) + return; + + string[] keys = {}; + try { + foreach(var group in keyfile.get_groups()) { + foreach(var key in keyfile.get_keys(group)) { + keys += string.join(".", group, key); + } + } + } catch (KeyFileError e) {} + + if (keys.length > 0) { + string k = keys.length > 1 ? "keys" : "key"; + append_warning("Unknown %s in config: %s".printf(k, string.joinv(", ", keys))); + } + } + + public string? read_string(string group, string key, string? default) { + if (keyfile == null) + return default; + + try { + string str = keyfile.get_string(group, key); + keyfile.remove_key(group, key); + return str; + } catch (KeyFileError e) { + handle_error(e, group, key); + return default; + } + } + + public int? read_integer(string group, string key, int? default) { + if (keyfile == null) + return default; + + try { + int integer = keyfile.get_integer(group, key); + keyfile.remove_key(group, key); + return integer; + } catch (KeyFileError e) { + handle_error(e, group, key); + return default; + } + } + + public bool? read_boolean(string group, string key, bool? default) { + if (keyfile == null) + return default; + + try { + bool boolean = keyfile.get_boolean(group, key); + keyfile.remove_key(group, key); + return boolean; + } catch (KeyFileError e) { + handle_error(e, group, key); + return default; + } + } + + public Gdk.RGBA? read_colour(string group, string key, string? default) { + string str = read_string(group, key, default); + + if (str == null) + return null; + + var rgba = Gdk.RGBA(); + if (!rgba.parse(str)) { + append_warning("invalid colour '%s' in %s.%s".printf(str, group, key)); + + rgba.parse(default); + return rgba; + } + + return rgba; + } + + public Vte.CursorShape read_cursor(string group, string key, string default) { + string cursor = read_string(group, key, default); + + switch (cursor) { + case "block": + return BLOCK; + case "beam": + return IBEAM; + case "underline": + return UNDERLINE; + default: + append_warning("invalid cursor shape '%s' in %s.%s".printf(cursor, group, key)); + return BLOCK; + } + } +} |