utils: rework Mu::Error and g_ logging macros with fmt

A bit more C++ template magic to remove a lot of code.
This commit is contained in:
Dirk-Jan C. Binnema 2023-07-03 20:41:06 +03:00
parent e6ab61d233
commit 742ca33740
11 changed files with 128 additions and 163 deletions

View File

@ -36,7 +36,8 @@ endif
ifneq (${MU_HACKER},)
MESON_FLAGS:=$(MESON_FLAGS) '-Dbuildtype=debug' \
'-Db_sanitize=address' \
'-Dreadline=enabled'
'-Dreadline=enabled' \
'-Dcpp_std=c++20'
endif
.PHONY: all

View File

@ -38,7 +38,9 @@ install: false)
lib_mu_utils_dep = declare_dependency(
link_with: lib_mu_utils,
include_directories: include_directories(['.', '..'])
compile_args: '-DFMT_HEADER_ONLY',
include_directories:
include_directories(['.', '..', '../thirdparty/fmt'])
)
#

View File

@ -28,6 +28,13 @@
#include "mu-utils-format.hh"
#include <glib.h>
#ifndef FMT_HEADER_ONLY
#define FMT_HEADER_ONLY
#endif
#include <fmt/format.h>
#include <fmt/core.h>
namespace Mu {
// calculate an error enum value.
@ -76,68 +83,31 @@ struct Error final : public std::exception {
/**
* Construct an error
*
* @param codearg error-code
* @param msgarg the error description
* @param code the error-code
* @param args... libfmt-style format string and parameters
*/
Error(Code codearg, const std::string& msgarg) : code_{codearg}, what_{msgarg} {}
Error(Code codearg, std::string&& msgarg) : code_{codearg}, what_{std::move(msgarg)} {}
template<typename...T>
Error(Code code, fmt::format_string<T...> frm, T&&... args):
code_{code},
what_{fmt::format(frm, std::forward<T>(args)...)} {}
/**
* Build an error from an error-code and a format string
* Construct an error
*
* @param code error-code
* @param frm format string
* @param ... format parameters
*
* @return an Error object
* @param code the error-code
* @param gerr a GError (or {}); the error is _consumed_ by this function
* @param args... libfmt-style format string and parameters
*/
__attribute__((format(printf, 3, 0))) Error(Code codearg, const char* frm, ...)
: code_{codearg}
{
va_list args;
va_start(args, frm);
what_ = vformat(frm, args);
va_end(args);
}
Error(Error&& rhs) = default;
Error(const Error& rhs) = default;
template<typename...T>
Error(Code code, GError **gerr, fmt::format_string<T...> frm, T&&... args):
code_{code},
what_{fmt::format(frm, std::forward<T>(args)...) +
fmt::format(": {}", (gerr && *gerr) ? (*gerr)->message :
"something went wrong")}
{ g_clear_error(gerr); }
/**
* Build an error from a GError an error-code and a format string
*
* @param code error-code
* @param gerr a GError or {}, which is consumed
* @param frm format string
* @param ... format parameters
*
* @return an Error object
*/
__attribute__((format(printf, 4, 0)))
Error(Code codearg, GError** err, const char* frm, ...)
: code_{codearg}
{
va_list args;
va_start(args, frm);
what_ = vformat(frm, args);
va_end(args);
if (err && *err)
what_ += format(": %s", (*err)->message);
else
what_ += ": something went wrong";
g_clear_error(err);
}
/**
* DTOR
*
*/
virtual ~Error() override = default;
/**
* Get the descriptive message.
* Get the descriptive message for this error.
*
* @return
*/
@ -192,8 +162,8 @@ private:
return error_domain;
}
const Code code_;
std::string what_;
const Code code_;
const std::string what_;
};
} // namespace Mu

View File

@ -51,15 +51,13 @@ maybe_open_logfile()
const auto logdir{to_string_gchar(g_path_get_dirname(MuLogPath.c_str()))};
if (g_mkdir_with_parents(logdir.c_str(), 0700) != 0) {
std::cerr << "creating " << logdir << " failed:" << g_strerror(errno)
<< std::endl;
mu_printerrln("creating {} failed: {}", logdir, g_strerror(errno));
return false;
}
MuStream.open(MuLogPath, std::ios::out | std::ios::app);
if (!MuStream.is_open()) {
std::cerr << "opening " << MuLogPath << " failed:" << g_strerror(errno)
<< std::endl;
mu_printerrln("opening {} failed: {}", MuLogPath, g_strerror(errno));
return false;
}
@ -86,8 +84,7 @@ maybe_rotate_logfile()
MuStream.close();
if (g_rename(MuLogPath.c_str(), old.c_str()) != 0)
std::cerr << "failed to rename " << MuLogPath << " -> " << old.c_str() << ": "
<< g_strerror(errno) << std::endl;
mu_printerrln("failed to rename {} -> {}: {}", MuLogPath, old, g_strerror(errno));
return maybe_open_logfile();
}
@ -197,7 +194,7 @@ static void
test_logger_threads(void)
{
const auto testpath{test_random_tmpdir() + "/test.log"};
g_message("log-file: %s", testpath.c_str());
mu_message("log-file: {}", testpath);
auto logger = Logger::make(testpath.c_str(), Logger::Options::File | Logger::Options::Debug);
assert_valid_result(logger);
@ -212,7 +209,7 @@ test_logger_threads(void)
threads.emplace_back(
std::thread([n,&running]{
while (running) {
g_debug("log message from thread <%d>", n);
mu_debug("log message from thread <{}>", n);
std::this_thread::yield();
}
}));

View File

@ -51,15 +51,15 @@ test_regex_replace()
int
main(int argc, char* argv[])
try {
mu_test_init(&argc, &argv);
mu_test_init(&argc, &argv);
g_test_add_func("/regex/match", test_regex_match);
g_test_add_func("/regex/replace", test_regex_replace);
return g_test_run();
g_test_add_func("/regex/match", test_regex_match);
g_test_add_func("/regex/replace", test_regex_replace);
return g_test_run();
} catch (const std::runtime_error& re) {
std::cerr << re.what() << "\n";
return 1;
mu_printerrln("{}", re.what());
return 1;
}
#endif /*BUILD_TESTS*/

View File

@ -39,8 +39,6 @@ template <typename T> using Result = tl::expected<T, Error>;
*/
template <typename T>
class Result<T>::expected
// note: "class", not "typename";
// https://stackoverflow.com/questions/46412754/class-name-injection-and-constructors
Ok(T&& t)
{
return std::move(t);
@ -83,55 +81,21 @@ Err(const Result<T>& res)
return res.error();
}
template <typename T>
static inline Result<void>
Ok(const T& t)
{
if (t)
return Ok();
else
return Err(t.error());
}
/*
* convenience
*/
static inline tl::unexpected<Error>
Err(Error::Code errcode, std::string&& msg="")
template <typename ...T>
inline tl::unexpected<Error>
Err(Error::Code code, fmt::format_string<T...> frm, T&&... args)
{
return Err(Error{errcode, std::move(msg)});
return Err(Error{code, frm, std::forward<T>(args)...});
}
__attribute__((format(printf, 2, 0)))
static inline tl::unexpected<Error>
Err(Error::Code errcode, const char* frm, ...)
template <typename ...T>
inline tl::unexpected<Error>
Err(Error::Code code, GError **err, fmt::format_string<T...> frm, T&&... args)
{
va_list args;
va_start(args, frm);
auto str{vformat(frm, args)};
va_end(args);
return Err(errcode, std::move(str));
}
__attribute__((format(printf, 3, 0)))
static inline tl::unexpected<Error>
Err(Error::Code errcode, GError **err, const char* frm, ...)
{
va_list args;
va_start(args, frm);
auto str{vformat(frm, args)};
va_end(args);
if (err && *err)
str += format(" (%s)", (*err)->message ? (*err)->message : "");
g_clear_error(err);
return Err(errcode, std::move(str));
return Err(Error{code, err, frm, std::forward<T>(args)...});
}
/**

View File

@ -517,7 +517,7 @@ try {
return g_test_run();
} catch (const std::runtime_error& re) {
std::cerr << re.what() << "\n";
mu_printerrln("{}", re.what());
return 1;
}

View File

@ -35,20 +35,17 @@
using namespace Mu;
char*
Mu::test_mu_common_get_random_tmpdir()
std::string
Mu::test_random_tmpdir()
{
char* dir;
int res;
auto&& dir = mu_format("{}{}mu-test-{}{}test-{:x}",
g_get_tmp_dir(),
G_DIR_SEPARATOR,
getuid(),
G_DIR_SEPARATOR,
::random() * getpid() * ::time({}));
dir = g_strdup_printf("%s%cmu-test-%d%ctest-%x",
g_get_tmp_dir(),
G_DIR_SEPARATOR,
getuid(),
G_DIR_SEPARATOR,
(int)random() * getpid() * (int)time(NULL));
res = g_mkdir_with_parents(dir, 0700);
auto res = g_mkdir_with_parents(dir.c_str(), 0700);
g_assert(res != -1);
return dir;
@ -76,12 +73,12 @@ Mu::set_en_us_utf8_locale()
setlocale(LC_ALL, "en_US.UTF-8");
if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) {
g_print("Note: Unit tests require the en_US.utf8 locale. "
"Ignoring test cases.\n");
return FALSE;
mu_println("Note: Unit tests require the en_US.utf8 locale. "
"Ignoring test cases.");
return false;
}
return TRUE;
return true;
}
static void
@ -108,8 +105,6 @@ Mu::mu_test_init(int *argc, char ***argv)
(GLogFunc)black_hole, NULL);
}
void
Mu::allow_warnings()
{
@ -128,10 +123,8 @@ Mu::TempDir::TempDir(bool autodelete): autodelete_{autodelete}
throw Mu::Error(Error::Code::File, &err,
"failed to create temporary directory");
path_ = tmpdir;
g_free(tmpdir);
g_debug("created '%s'", path_.c_str());
path_ = to_string_gchar(std::move(tmpdir));
mu_debug("created '{}'", path_);
}
Mu::TempDir::~TempDir()

View File

@ -28,11 +28,12 @@ namespace Mu {
/**
* get a dir name for a random temporary directory to do tests
*
* @return a random dir name, g_free when it's no longer needed
* @return a random dir name
*/
char* test_mu_common_get_random_tmpdir(void);
static inline std::string test_random_tmpdir() {
return to_string_gchar(test_mu_common_get_random_tmpdir());
std::string test_random_tmpdir(void);
inline gchar* test_mu_common_get_random_tmpdir() {
return g_strdup(test_random_tmpdir().c_str());
}
/**

View File

@ -17,8 +17,8 @@
** 02110-1301, USA.
*/
#ifndef __MU_UTILS_HH__
#define __MU_UTILS_HH__
#ifndef MU_UTILS_HH__
#define MU_UTILS_HH__
#include <string>
#include <string_view>
@ -37,8 +37,50 @@
#include "mu-utils-format.hh"
#include "mu-option.hh"
#ifndef FMT_HEADER_ONLY
#define FMT_HEADER_ONLY
#endif /*FMT_HEADER_ONLY*/
#include <fmt/format.h>
#include <fmt/core.h>
namespace Mu {
/*
* Logging functions connect libfmt with the Glib logging system
*/
template<typename...T>
inline void mu_debug(fmt::format_string<T...> frm, T&&... args) noexcept {
g_log("mu", G_LOG_LEVEL_DEBUG, "%s",
fmt::format(frm, std::forward<T>(args)...).c_str());
}
template<typename...T>
inline void mu_info(fmt::format_string<T...> frm, T&&... args) noexcept {
g_log("mu", G_LOG_LEVEL_INFO, "%s",
fmt::format(frm, std::forward<T>(args)...).c_str());
}
template<typename...T>
inline void mu_message(fmt::format_string<T...> frm, T&&... args) noexcept {
g_log("mu", G_LOG_LEVEL_MESSAGE, "%s",
fmt::format(frm, std::forward<T>(args)...).c_str());
}
template<typename...T>
inline void mu_warning(fmt::format_string<T...> frm, T&&... args) noexcept {
g_log("mu", G_LOG_LEVEL_WARNING, "%s",
fmt::format(frm, std::forward<T>(args)...).c_str());
}
template<typename...T>
inline void mu_critical(fmt::format_string<T...> frm, T&&... args) noexcept {
g_log("mu", G_LOG_LEVEL_CRITICAL, "%s",
fmt::format(frm, std::forward<T>(args)...).c_str());
}
template<typename...T>
inline void mu_error(fmt::format_string<T...> frm, T&&... args) noexcept {
g_log("mu", G_LOG_LEVEL_ERROR, "%s",
fmt::format(frm, std::forward<T>(args)...).c_str());
}
using StringVec = std::vector<std::string>;
/**
@ -517,4 +559,4 @@ private:
} // namespace Mu
#endif /* __MU_UTILS_HH__ */
#endif /* MU_UTILS_HH__ */

View File

@ -44,32 +44,27 @@ static std::string DB_PATH2;
static std::string
make_database(const std::string& testdir)
{
char* tmpdir{test_mu_common_get_random_tmpdir()};
auto&& tmpdir{test_random_tmpdir()};
/* use the env var rather than `--muhome` */
g_setenv("MUHOME", tmpdir, 1);
const auto cmdline{format("/bin/sh -c '"
"%s --quiet init --maildir=%s ; "
"%s --quiet index'",
MU_PROGRAM,
testdir.c_str(),
MU_PROGRAM)};
g_setenv("MUHOME", tmpdir.c_str(), 1);
const auto cmdline{mu_format(
"/bin/sh -c '"
"{} --quiet init --maildir={} ; "
"{} --quiet index'",
MU_PROGRAM, testdir, MU_PROGRAM)};
if (g_test_verbose())
g_printerr("\n%s\n", cmdline.c_str());
g_assert(g_spawn_command_line_sync(cmdline.c_str(), NULL, NULL, NULL, NULL));
auto xpath = g_strdup_printf("%s%c%s", tmpdir, G_DIR_SEPARATOR, "xapian");
g_free(tmpdir);
auto xpath = mu_format("{}{}{}",
tmpdir, G_DIR_SEPARATOR, "xapian");
/* ensure MUHOME worked */
g_assert_cmpuint(::access(xpath, F_OK), ==, 0);
g_assert_cmpuint(::access(xpath.c_str(), F_OK), ==, 0);
std::string dbpath{xpath};
g_free(xpath);
return dbpath;
return xpath;
}
static void