lib: replace mu-bookmarks with mu-query-macros

And add some unit tests.
This commit is contained in:
Dirk-Jan C. Binnema 2023-09-11 23:52:38 +03:00
parent e290158bcd
commit 8287b9802e
6 changed files with 256 additions and 234 deletions

View File

@ -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',

View File

@ -1,136 +0,0 @@
/*
** Copyright (C) 2010-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <glib.h>
#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);
}

View File

@ -1,76 +0,0 @@
/*
** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <glib.h>
/**
* @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__*/

160
lib/mu-query-macros.cc Normal file
View File

@ -0,0 +1,160 @@
/*
** Copyright (C) 2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <glib.h>
#include <unordered_map>
#include "utils/mu-utils.hh"
using namespace Mu;
constexpr auto MU_BOOKMARK_GROUP = "mu";
struct QueryMacros::Private {
Private(const Config& conf): conf_{conf} {}
Result<void> import_key_file(GKeyFile *kfile);
const Config& conf_;
std::unordered_map<std::string, std::string> macros_{};
};
Result<void>
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<Private>(conf)} {}
QueryMacros::~QueryMacros() = default;
Result<void>
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<std::string>
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*/

75
lib/mu-query-macros.hh Normal file
View File

@ -0,0 +1,75 @@
/*
** Copyright (C) 2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <string>
#include <memory>
#include <utils/mu-result.hh>
#include <utils/mu-option.hh>
#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<void> 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<std::string> find_macro(const std::string& name) const;
private:
struct Private;
std::unique_ptr<Private> priv_;
};
} // namespace Mu
#endif /* MU_QUERY_MACROS_HH__ */

View File

@ -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<Message>& msg, const OutputInfo& info, const Options& opts
}
static Result<std::string>
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<std::string>
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<void>
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());