From f147b5439347e14bb8affc08ec9adbc3d25998fb Mon Sep 17 00:00:00 2001
From: Přemysl Eric Janouch 
Date: Fri, 15 Oct 2021 13:37:29 +0200
Subject: sdgui: load dictionaries from sdtui configuration
---
 src/sdgui.c |  67 +++++++++++++++++++++++++++------
 src/sdtui.c | 122 +++++-------------------------------------------------------
 src/utils.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/utils.h |   9 ++++-
 4 files changed, 190 insertions(+), 126 deletions(-)
diff --git a/src/sdgui.c b/src/sdgui.c
index c547760..cc2a042 100644
--- a/src/sdgui.c
+++ b/src/sdgui.c
@@ -53,9 +53,8 @@ g;
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
 static gboolean
-dictionary_load (Dictionary *self, gchar *filename, GError **e)
+dictionary_load (Dictionary *self, GError **e)
 {
-	self->filename = filename;
 	if (!(self->dict = stardict_dict_new (self->filename, e)))
 		return FALSE;
 
@@ -69,22 +68,62 @@ dictionary_load (Dictionary *self, gchar *filename, GError **e)
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
-static gboolean
-init (gchar **filenames, GError **e)
+static void
+init (gchar **filenames)
 {
 	while (filenames[g.dictionaries_len])
 		g.dictionaries_len++;
 
 	g.dictionaries = g_malloc0_n (sizeof *g.dictionaries, g.dictionaries_len);
+	for (gsize i = 0; i < g.dictionaries_len; i++)
+		g.dictionaries[i].filename = filenames[i];
+}
+
+// TODO: try to deduplicate, similar to app_load_config_values()
+static gboolean
+init_from_key_file (GKeyFile *kf, GError **error)
+{
+	const gchar *dictionaries = "Dictionaries";
+	gchar **names =
+		g_key_file_get_keys (kf, dictionaries, &g.dictionaries_len, NULL);
+	if (!names)
+		return TRUE;
+
+	g.dictionaries = g_malloc0_n (sizeof *g.dictionaries, g.dictionaries_len);
+	for (gsize i = 0; i < g.dictionaries_len; i++)
+		g.dictionaries[i].name = names[i];
+	g_free (names);
+
 	for (gsize i = 0; i < g.dictionaries_len; i++)
 	{
 		Dictionary *dict = &g.dictionaries[i];
-		if (!dictionary_load (dict, filenames[i], e))
+		gchar *path =
+			g_key_file_get_string (kf, dictionaries, dict->name, error);
+		if (!path)
 			return FALSE;
+
+		// Try to resolve relative paths and expand tildes
+		if (!(dict->filename =
+			resolve_filename (path, resolve_relative_config_filename)))
+			dict->filename = path;
+		else
+			g_free (path);
 	}
 	return TRUE;
 }
 
+static gboolean
+init_from_config (GError **error)
+{
+	GKeyFile *key_file = load_project_config_file (error);
+	if (!key_file)
+		return FALSE;
+
+	gboolean result = init_from_key_file (key_file, error);
+	g_key_file_free (key_file);
+	return result;
+}
+
 static void
 search (Dictionary *dict)
 {
@@ -203,7 +242,7 @@ main (int argc, char *argv[])
 	GOptionEntry option_entries[] =
 	{
 		{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames,
-			NULL, N_("FILE...")},
+			NULL, N_("[FILE]...")},
 		{},
 	};
 
@@ -217,14 +256,18 @@ main (int argc, char *argv[])
 		return 1;
 	}
 
-	if (!filenames)
-	{
-		// TODO: eventually just load all dictionaries from configuration
-		die_with_dialog ("No arguments have been passed.");
-	}
-	if (!init (filenames, &error))
+	if (filenames)
+		init (filenames);
+	else if (!init_from_config (&error) && error)
 		die_with_dialog (error->message);
 
+	for (gsize i = 0; i < g.dictionaries_len; i++)
+		if (!dictionary_load (&g.dictionaries[i], &error))
+			die_with_dialog (error->message);
+	if (!g.dictionaries_len)
+		die_with_dialog (_("No dictionaries found either in "
+			"the configuration or on the command line"));
+
 	// Some Adwaita stupidity
 	const char *style = "notebook header tab { padding: 2px 8px; margin: 0; }";
 
diff --git a/src/sdtui.c b/src/sdtui.c
index 1158d05..caf08e6 100644
--- a/src/sdtui.c
+++ b/src/sdtui.c
@@ -28,13 +28,11 @@
 #include 
 #include 
 #include 
-#include 
 
 #include 
 #include 
 #include 
 #include 
-#include 
 
 #include   // input
 #include   // output
@@ -96,94 +94,6 @@ add_read_watch (int fd, GIOFunc func, gpointer user_data)
 	return res;
 }
 
-// At times, GLib even with its sheer size is surprisingly useless,
-// and I need to port some code over from "liberty".
-
-static gchar **
-get_xdg_config_dirs (void)
-{
-	GPtrArray *paths = g_ptr_array_new ();
-	g_ptr_array_add (paths, (gpointer) g_get_user_config_dir ());
-	for (const gchar *const *system = g_get_system_config_dirs ();
-		*system; system++)
-		g_ptr_array_add (paths, (gpointer) *system);
-	g_ptr_array_add (paths, NULL);
-	return (gchar **) g_ptr_array_free (paths, FALSE);
-}
-
-static gchar *
-resolve_relative_filename_generic
-	(gchar **paths, const gchar *tail, const gchar *filename)
-{
-	for (; *paths; paths++)
-	{
-		// As per XDG spec, relative paths are ignored
-		if (**paths != '/')
-			continue;
-
-		gchar *file = g_build_filename (*paths, tail, filename, NULL);
-		GStatBuf st;
-		if (!g_stat (file, &st))
-			return file;
-		g_free (file);
-	}
-	return NULL;
-}
-
-static gchar *
-resolve_relative_config_filename (const gchar *filename)
-{
-	gchar **paths = get_xdg_config_dirs ();
-	gchar *result = resolve_relative_filename_generic
-		(paths, PROJECT_NAME, filename);
-	g_strfreev (paths);
-	return result;
-}
-
-static gchar *
-try_expand_tilde (const gchar *filename)
-{
-	size_t until_slash = strcspn (filename, "/");
-	if (!until_slash)
-		return g_build_filename (g_get_home_dir () ?: "", filename, NULL);
-
-	long buf_len = sysconf (_SC_GETPW_R_SIZE_MAX);
-	if (buf_len < 0)
-		buf_len = 1024;
-	struct passwd pwd, *success = NULL;
-
-	gchar *user = g_strndup (filename, until_slash);
-	gchar *buf = g_malloc (buf_len);
-	while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE)
-		buf = g_realloc (buf, buf_len <<= 1);
-	g_free (user);
-
-	gchar *result = NULL;
-	if (success)
-		result = g_strdup_printf ("%s%s", pwd.pw_dir, filename + until_slash);
-	g_free (buf);
-	return result;
-}
-
-static gchar *
-resolve_filename (const gchar *filename, gchar *(*relative_cb) (const char *))
-{
-	// Absolute path is absolute
-	if (*filename == '/')
-		return g_strdup (filename);
-
-	// We don't want to use wordexp() for this as it may execute /bin/sh
-	if (*filename == '~')
-	{
-		// Paths to home directories ought to be absolute
-		char *expanded = try_expand_tilde (filename + 1);
-		if (expanded)
-			return expanded;
-		g_debug ("failed to expand the home directory in `%s'", filename);
-	}
-	return relative_cb (filename);
-}
-
 // --- Application -------------------------------------------------------------
 
 #define ATTRIBUTE_TABLE(XX)                               \
@@ -574,8 +484,8 @@ app_load_config_values (Application *self, GKeyFile *kf)
 			continue;
 
 		// Try to resolve relative paths and expand tildes
-		gchar *resolved = resolve_filename
-			(path, resolve_relative_config_filename);
+		gchar *resolved =
+			resolve_filename (path, resolve_relative_config_filename);
 		if (resolved)
 			g_free (path);
 		else
@@ -588,30 +498,16 @@ app_load_config_values (Application *self, GKeyFile *kf)
 }
 
 static void
-app_load_config (Application *self, GError **e)
+app_load_config (Application *self, GError **error)
 {
-	GKeyFile *kf = g_key_file_new ();
-	gchar **paths = get_xdg_config_dirs ();
-
-	// XXX: if there are dashes in the final path component,
-	//   the function tries to replace them with directory separators,
-	//   which is completely undocumented
-	GError *error = NULL;
-	g_key_file_load_from_dirs (kf,
-		PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf",
-		(const gchar **) paths, NULL, 0, &error);
-	g_strfreev (paths);
-
 	// TODO: proper error handling showing all relevant information;
 	//   we can afford that here since the terminal hasn't been initialized yet
-	if (!error)
-		app_load_config_values (self, kf);
-	else if (error->code == G_KEY_FILE_ERROR_NOT_FOUND)
-		g_error_free (error);
-	else
-		g_propagate_error (e, error);
-
-	g_key_file_free (kf);
+	GKeyFile *key_file = load_project_config_file (error);
+	if (key_file)
+	{
+		app_load_config_values (self, key_file);
+		g_key_file_free (key_file);
+	}
 }
 
 static void
diff --git a/src/utils.c b/src/utils.c
index 3bba022..612eaaa 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -19,10 +19,14 @@
 #include 
 #include 
 #include 
+#include 
+
 #include 
 #include 
 #include 
 
+#include 
+
 #include "config.h"
 #include "utils.h"
 
@@ -105,3 +109,117 @@ fatal (const gchar *format, ...)
 	exit (EXIT_FAILURE);
 	va_end (ap);
 }
+
+// At times, GLib even with its sheer size is surprisingly useless,
+// and I need to port some code over from "liberty".
+
+static gchar **
+get_xdg_config_dirs (void)
+{
+	GPtrArray *paths = g_ptr_array_new ();
+	g_ptr_array_add (paths, (gpointer) g_get_user_config_dir ());
+	for (const gchar *const *system = g_get_system_config_dirs ();
+		*system; system++)
+		g_ptr_array_add (paths, (gpointer) *system);
+	g_ptr_array_add (paths, NULL);
+	return (gchar **) g_ptr_array_free (paths, FALSE);
+}
+
+gchar *
+resolve_relative_filename_generic
+	(gchar **paths, const gchar *tail, const gchar *filename)
+{
+	for (; *paths; paths++)
+	{
+		// As per XDG spec, relative paths are ignored
+		if (**paths != '/')
+			continue;
+
+		gchar *file = g_build_filename (*paths, tail, filename, NULL);
+		GStatBuf st;
+		if (!g_stat (file, &st))
+			return file;
+		g_free (file);
+	}
+	return NULL;
+}
+
+gchar *
+resolve_relative_config_filename (const gchar *filename)
+{
+	gchar **paths = get_xdg_config_dirs ();
+	gchar *result = resolve_relative_filename_generic
+		(paths, PROJECT_NAME, filename);
+	g_strfreev (paths);
+	return result;
+}
+
+static gchar *
+try_expand_tilde (const gchar *filename)
+{
+	size_t until_slash = strcspn (filename, "/");
+	if (!until_slash)
+		return g_build_filename (g_get_home_dir () ?: "", filename, NULL);
+
+	long buf_len = sysconf (_SC_GETPW_R_SIZE_MAX);
+	if (buf_len < 0)
+		buf_len = 1024;
+	struct passwd pwd, *success = NULL;
+
+	gchar *user = g_strndup (filename, until_slash);
+	gchar *buf = g_malloc (buf_len);
+	while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE)
+		buf = g_realloc (buf, buf_len <<= 1);
+	g_free (user);
+
+	gchar *result = NULL;
+	if (success)
+		result = g_strdup_printf ("%s%s", pwd.pw_dir, filename + until_slash);
+	g_free (buf);
+	return result;
+}
+
+gchar *
+resolve_filename (const gchar *filename, gchar *(*relative_cb) (const char *))
+{
+	// Absolute path is absolute
+	if (*filename == '/')
+		return g_strdup (filename);
+
+	// We don't want to use wordexp() for this as it may execute /bin/sh
+	if (*filename == '~')
+	{
+		// Paths to home directories ought to be absolute
+		char *expanded = try_expand_tilde (filename + 1);
+		if (expanded)
+			return expanded;
+		g_debug ("failed to expand the home directory in `%s'", filename);
+	}
+	return relative_cb (filename);
+}
+
+GKeyFile *
+load_project_config_file (GError **error)
+{
+	GKeyFile *key_file = g_key_file_new ();
+	gchar **paths = get_xdg_config_dirs ();
+	GError *e = NULL;
+
+	// XXX: if there are dashes in the final path component,
+	//   the function tries to replace them with directory separators,
+	//   which is completely undocumented
+	g_key_file_load_from_dirs (key_file,
+		PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf",
+		(const gchar **) paths, NULL, 0, &e);
+	g_strfreev (paths);
+	if (!e)
+		return key_file;
+
+	if (e->code == G_KEY_FILE_ERROR_NOT_FOUND)
+		g_error_free (e);
+	else
+		g_propagate_error (error, e);
+
+	g_key_file_free (key_file);
+	return NULL;
+}
diff --git a/src/utils.h b/src/utils.h
index b47daa9..1394f08 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -1,7 +1,7 @@
 /*
  * utils.h: miscellaneous utilities
  *
- * Copyright (c) 2013 - 2020, Přemysl Eric Janouch 
+ * Copyright (c) 2013 - 2021, Přemysl Eric Janouch 
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted.
@@ -43,4 +43,11 @@ gchar *stream_read_string (GDataInputStream *dis, GError **error);
 gboolean xstrtoul (unsigned long *out, const char *s, int base);
 void fatal (const gchar *format, ...) G_GNUC_PRINTF (1, 2) G_GNUC_NORETURN;
 
+gchar *resolve_relative_filename_generic
+	(gchar **paths, const gchar *tail, const gchar *filename);
+gchar *resolve_relative_config_filename (const gchar *filename);
+gchar *resolve_filename
+	(const gchar *filename, gchar *(*relative_cb) (const char *));
+GKeyFile *load_project_config_file (GError **error);
+
 #endif  // ! UTILS_H
-- 
cgit v1.2.3-70-g09d2