aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/configreader.vala
diff options
context:
space:
mode:
Diffstat (limited to 'configreader.vala')
-rw-r--r--configreader.vala188
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;
+ }
+ }
+}