From 8287b9802e843a39723f03c76b385f39835efff7 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Mon, 11 Sep 2023 23:52:38 +0300 Subject: [PATCH] lib: replace mu-bookmarks with mu-query-macros And add some unit tests. --- lib/meson.build | 9 ++- lib/mu-bookmarks.cc | 136 ----------------------------------- lib/mu-bookmarks.hh | 76 -------------------- lib/mu-query-macros.cc | 160 +++++++++++++++++++++++++++++++++++++++++ lib/mu-query-macros.hh | 75 +++++++++++++++++++ mu/mu-cmd-find.cc | 34 ++++----- 6 files changed, 256 insertions(+), 234 deletions(-) delete mode 100644 lib/mu-bookmarks.cc delete mode 100644 lib/mu-bookmarks.hh create mode 100644 lib/mu-query-macros.cc create mode 100644 lib/mu-query-macros.hh diff --git a/lib/meson.build b/lib/meson.build index 3592b3a9..66fd0f56 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -27,7 +27,7 @@ lib_mu=static_library( 'mu-store.cc', 'mu-xapian-db.cc', # querying - 'mu-bookmarks.cc', + 'mu-query-macros.cc', 'mu-query-match-deciders.cc', 'mu-query-parser.cc', 'mu-query-processor.cc', @@ -114,6 +114,13 @@ test('test-config', cpp_args: ['-DBUILD_TESTS'], dependencies: [glib_dep, lib_mu_dep])) +test('test-query-macros', + executable('test-query-macros', + 'mu-query-macros.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [lib_mu_dep])) + test('test-query-processor', executable('test-query-processor', 'mu-query-processor.cc', diff --git a/lib/mu-bookmarks.cc b/lib/mu-bookmarks.cc deleted file mode 100644 index 1865b643..00000000 --- a/lib/mu-bookmarks.cc +++ /dev/null @@ -1,136 +0,0 @@ -/* -** Copyright (C) 2010-2020 Dirk-Jan C. Binnema -** -** This program is free software; you can redistribute it and/or modify it -** under the terms of the GNU General Public License as published by the -** Free Software Foundation; either version 3, or (at your option) any -** later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software Foundation, -** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -** -*/ - -#include -#include "mu-bookmarks.hh" - -#define MU_BOOKMARK_GROUP "mu" - -struct MuBookmarks { - char* _bmpath; - GHashTable* _hash; -}; - -static void -fill_hash(GHashTable* hash, GKeyFile* kfile) -{ - gchar **keys, **cur; - - keys = g_key_file_get_keys(kfile, MU_BOOKMARK_GROUP, NULL, NULL); - if (!keys) - return; - - for (cur = keys; *cur; ++cur) { - gchar* val; - val = g_key_file_get_string(kfile, MU_BOOKMARK_GROUP, *cur, NULL); - if (val) - g_hash_table_insert(hash, *cur, val); - } - - /* don't use g_strfreev, because we put them in the hash table; - * only free the gchar** itself */ - g_free(keys); -} - -static GHashTable* -create_hash_from_key_file(const gchar* bmpath) -{ - GKeyFile* kfile; - GHashTable* hash; - - kfile = g_key_file_new(); - - if (!g_key_file_load_from_file(kfile, bmpath, G_KEY_FILE_NONE, NULL)) { - g_key_file_free(kfile); - return NULL; - } - - hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); - fill_hash(hash, kfile); - - g_key_file_free(kfile); - - return hash; -} - -MuBookmarks* -mu_bookmarks_new(const gchar* bmpath) -{ - MuBookmarks* bookmarks; - GHashTable* hash; - - g_return_val_if_fail(bmpath, NULL); - - hash = create_hash_from_key_file(bmpath); - if (!hash) - return NULL; - - bookmarks = g_new(MuBookmarks, 1); - - bookmarks->_bmpath = g_strdup(bmpath); - bookmarks->_hash = hash; - - return bookmarks; -} - -void -mu_bookmarks_destroy(MuBookmarks* bm) -{ - if (!bm) - return; - - g_free(bm->_bmpath); - g_hash_table_destroy(bm->_hash); - g_free(bm); -} - -const gchar* -mu_bookmarks_lookup(MuBookmarks* bm, const gchar* name) -{ - g_return_val_if_fail(bm, NULL); - g_return_val_if_fail(name, NULL); - - return (const char*)g_hash_table_lookup(bm->_hash, name); -} - -struct _BMData { - MuBookmarksForeachFunc _func; - gpointer _user_data; -}; -typedef struct _BMData BMData; - -static void -each_bookmark(const gchar* key, const gchar* val, BMData* bmdata) -{ - bmdata->_func(key, val, bmdata->_user_data); -} - -void -mu_bookmarks_foreach(MuBookmarks* bm, MuBookmarksForeachFunc func, gpointer user_data) -{ - BMData bmdata; - - g_return_if_fail(bm); - g_return_if_fail(func); - - bmdata._func = func; - bmdata._user_data = user_data; - - g_hash_table_foreach(bm->_hash, (GHFunc)each_bookmark, &bmdata); -} diff --git a/lib/mu-bookmarks.hh b/lib/mu-bookmarks.hh deleted file mode 100644 index a68d23a2..00000000 --- a/lib/mu-bookmarks.hh +++ /dev/null @@ -1,76 +0,0 @@ -/* -** Copyright (C) 2008-2020 Dirk-Jan C. Binnema -** -** This program is free software; you can redistribute it and/or modify it -** under the terms of the GNU General Public License as published by the -** Free Software Foundation; either version 3, or (at your option) any -** later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software Foundation, -** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -** -*/ - -#ifndef MU_BOOKMARKS_HH__ -#define MU_BOOKMARKS_HH__ - -#include -/** - * @addtogroup MuBookmarks - * Functions for dealing with bookmarks - * @{ - */ - -/*! \struct MuBookmarks - * \brief Opaque structure representing a sequence of bookmarks - */ -struct MuBookmarks; - -/** - * create a new bookmarks object. when it's no longer needed, use - * mu_bookmarks_destroy - * - * @param bmpath path to the bookmarks file - * - * @return a new BookMarks object, or NULL in case of error - */ -MuBookmarks* mu_bookmarks_new(const gchar* bmpath) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; - -/** - * destroy a bookmarks object - * - * @param bm a bookmarks object, or NULL - */ -void mu_bookmarks_destroy(MuBookmarks* bm); - -/** - * get the value for some bookmark - * - * @param bm a valid bookmarks object - * @param name name of the bookmark to retrieve - * - * @return the value of the bookmark or NULL in case in error, e.g. if - * the bookmark was not found - */ -const gchar* mu_bookmarks_lookup(MuBookmarks* bm, const gchar* name); - -typedef void (*MuBookmarksForeachFunc)(const gchar* key, const gchar* val, gpointer user_data); - -/** - * call a function for each bookmark - * - * @param bm a valid bookmarks object - * @param func a callback function to be called for each bookmarks - * @param user_data a user pointer passed to the callback - */ -void mu_bookmarks_foreach(MuBookmarks* bm, MuBookmarksForeachFunc func, gpointer user_data); - -/** @} */ - -#endif /*__MU_BOOKMARKS_H__*/ diff --git a/lib/mu-query-macros.cc b/lib/mu-query-macros.cc new file mode 100644 index 00000000..1fb682b1 --- /dev/null +++ b/lib/mu-query-macros.cc @@ -0,0 +1,160 @@ +/* +** Copyright (C) 2023 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include "mu-query-macros.hh" + +#include +#include + +#include "utils/mu-utils.hh" + +using namespace Mu; + +constexpr auto MU_BOOKMARK_GROUP = "mu"; + +struct QueryMacros::Private { + Private(const Config& conf): conf_{conf} {} + + + Result import_key_file(GKeyFile *kfile); + + const Config& conf_; + std::unordered_map macros_{}; +}; + +Result +QueryMacros::Private::import_key_file(GKeyFile *kfile) +{ + if (!kfile) + return Err(Error::Code::InvalidArgument, "invalid key-file"); + + GError *err{}; + size_t num{}; + gchar **keys{g_key_file_get_keys(kfile, MU_BOOKMARK_GROUP, &num, &err)}; + if (!keys) + return Err(Error::Code::File, &err/*cons*/,"failed to read keys"); + + for (auto key = keys; key && *key; ++key) { + + auto rawval{g_key_file_get_string(kfile, MU_BOOKMARK_GROUP, *key, &err)}; + if (!rawval) { + g_strfreev(keys); + return Err(Error::Code::File, &err/*cons*/,"failed to read key '{}'", *key); + } + + auto val{to_string_gchar(std::move(rawval))}; + macros_.erase(val); // we want to replace + macros_.emplace(std::string(*key), std::move(val)); + ++num; + } + + g_strfreev(keys); + mu_debug("imported {} query macro(s); total {}", num, macros_.size()); + return Ok(); +} + +QueryMacros::QueryMacros(const Config& conf): + priv_{std::make_unique(conf)} {} + +QueryMacros::~QueryMacros() = default; + +Result +QueryMacros::load_bookmarks(const std::string& path) +{ + GError *err{}; + GKeyFile *kfile{g_key_file_new()}; + if (!g_key_file_load_from_file(kfile, path.c_str(), G_KEY_FILE_NONE, &err)) { + g_key_file_unref(kfile); + return Err(Error::Code::File, &err/*cons*/, + "failed to read bookmarks from {}", path); + } + + auto&& res = priv_->import_key_file(kfile); + g_key_file_unref(kfile); + + return res; +} + +Option +QueryMacros::find_macro(const std::string& name) const +{ + if (const auto it{priv_->macros_.find(name)}; it != priv_->macros_.end()) + return it->second; + else + return Nothing; +} + + +#ifdef BUILD_TESTS +/* + * Tests. + * + */ + +#include "utils/mu-test-utils.hh" +#include "utils/mu-utils-file.hh" + +static void +test_bookmarks() +{ + MemDb db; + Config conf_db{db}; + QueryMacros qm{conf_db}; + + TempDir tdir{}; + const auto bmfile{join_paths(tdir.path(), "bookmarks.ini")}; + std::ofstream os{bmfile}; + + mu_println(os, "# test\n" + "[mu]\n" + "foo=subject:bar"); + os.close(); + + auto res = qm.load_bookmarks(bmfile); + assert_valid_result(res); + + assert_equal(qm.find_macro("foo").value_or(""), "subject:bar"); + assert_equal(qm.find_macro("bar").value_or("nope"), "nope"); +} + + +static void +test_bookmarks_fail() +{ + + MemDb db; + Config conf_db{db}; + QueryMacros qm{conf_db}; + + auto res = qm.load_bookmarks("/foo/bar/non-existent"); + g_assert_false(!!res); +} + + +int +main(int argc, char* argv[]) +{ + mu_test_init(&argc, &argv); + + g_test_add_func("/query/macros/bookmarks", test_bookmarks); + g_test_add_func("/query/macros/bookmarks-fail", test_bookmarks_fail); + + return g_test_run(); +} +#endif /*BUILD_TESTS*/ diff --git a/lib/mu-query-macros.hh b/lib/mu-query-macros.hh new file mode 100644 index 00000000..1b62615b --- /dev/null +++ b/lib/mu-query-macros.hh @@ -0,0 +1,75 @@ +/* +** Copyright (C) 2023 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + + +#ifndef MU_QUERY_MACROS_HH__ +#define MU_QUERY_MACROS_HH__ + +#include +#include + +#include +#include + +#include "mu-config.hh" + +namespace Mu { + +class QueryMacros{ +public: + /** + * Construct QueryMacros object + * + * @param conf config object ref + */ + QueryMacros(const Config& conf); + + /** + * DTOR + */ + ~QueryMacros(); + + /** + * Read bookmarks (ie. macros) from a bookmark-file + * + * @param bookmarks_file path to the bookmarks file + * + * @return Ok or some error + */ + Result load_bookmarks(const std::string& bookmarks_file); + + + /** + * Find a macro (aka 'bookmark') by its name + * + * @param name the name of the bookmark + * + * @return the macro value or Nothing if not found + */ + Option find_macro(const std::string& name) const; + +private: + struct Private; + std::unique_ptr priv_; +}; + + +} // namespace Mu + +#endif /* MU_QUERY_MACROS_HH__ */ diff --git a/mu/mu-cmd-find.cc b/mu/mu-cmd-find.cc index 8dc007ec..33792958 100644 --- a/mu/mu-cmd-find.cc +++ b/mu/mu-cmd-find.cc @@ -32,7 +32,7 @@ #include "mu-maildir.hh" #include "mu-query-match-deciders.hh" #include "mu-query.hh" -#include "mu-bookmarks.hh" +#include "mu-query-macros.hh" #include "mu-query-parser.hh" #include "message/mu-message.hh" @@ -129,28 +129,20 @@ exec_cmd(const Option& msg, const OutputInfo& info, const Options& opts } static Result -resolve_bookmark(const Options& opts) +resolve_bookmark(const Store& store, const Options& opts) { - const auto bmfile = opts.runtime_path(RuntimePath::Bookmarks); - auto bm = mu_bookmarks_new(bmfile.c_str()); - if (!bm) - return Err(Error::Code::File, - "failed to open bookmarks file '{}'", bmfile); - - const auto bookmark{opts.find.bookmark}; - const auto val = mu_bookmarks_lookup(bm, bookmark.c_str()); - if (!val) { - mu_bookmarks_destroy(bm); - return Err(Error::Code::NoMatches, - "bookmark '{}' not found", bookmark); - } - - mu_bookmarks_destroy(bm); - return Ok(std::string(val)); + QueryMacros macros{store.config()}; + if (auto&& res{macros.load_bookmarks(opts.runtime_path(RuntimePath::Bookmarks))}; !res) + return Err(res.error()); + else if (auto&& bm{macros.find_macro(opts.find.bookmark)}; !bm) + return Err(Error::Code::InvalidArgument, "bookmark '{}' not found", + opts.find.bookmark); + else + return Ok(std::move(*bm)); } static Result -get_query(const Options& opts) +get_query(const Store& store, const Options& opts) { if (opts.find.bookmark.empty() && opts.find.query.empty()) return Err(Error::Code::InvalidArgument, @@ -158,7 +150,7 @@ get_query(const Options& opts) std::string bookmark; if (!opts.find.bookmark.empty()) { - const auto res = resolve_bookmark(opts); + const auto res = resolve_bookmark(store, opts); if (!res) return Err(std::move(res.error())); bookmark = res.value() + " "; @@ -507,7 +499,7 @@ process_store_query(const Store& store, const std::string& expr, const Options& Result Mu::mu_cmd_find(const Store& store, const Options& opts) { - auto expr{get_query(opts)}; + auto expr{get_query(store, opts)}; if (!expr) return Err(expr.error());