diff --git a/guile/mu-guile.cc b/guile/mu-guile.cc index 853b123a..4a295d10 100644 --- a/guile/mu-guile.cc +++ b/guile/mu-guile.cc @@ -31,6 +31,8 @@ #include #include +#include + using namespace Mu; SCM diff --git a/lib/message/mu-message.cc b/lib/message/mu-message.cc index e5bd40c2..f81b01fe 100644 --- a/lib/message/mu-message.cc +++ b/lib/message/mu-message.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2022 Dirk-Jan C. Binnema +** Copyright (C) 2022-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 @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include diff --git a/lib/mu-maildir.cc b/lib/mu-maildir.cc index 8b27aa73..470512fd 100644 --- a/lib/mu-maildir.cc +++ b/lib/mu-maildir.cc @@ -34,7 +34,7 @@ #include "glibconfig.h" #include "mu-maildir.hh" #include "utils/mu-utils.hh" -#include "utils/mu-util.h" +#include "utils/mu-utils-file.hh" using namespace Mu; @@ -61,7 +61,8 @@ get_dtype(struct dirent* dentry, const std::string& path, bool use_lstat) slowpath: #endif /*HAVE_STRUCT_DIRENT_D_TYPE*/ - return mu_util_get_dtype(path.c_str(), use_lstat); + + return determine_dtype(path, use_lstat); } static Mu::Result @@ -77,7 +78,7 @@ create_maildir(const std::string& path, mode_t mode) /* if subdir already exists, don't try to re-create * it */ - if (mu_util_check_dir(fullpath.c_str(), TRUE, TRUE)) + if (check_dir(fullpath, true/*readable*/, true/*writable*/)) continue; int rv{g_mkdir_with_parents(fullpath.c_str(), static_cast(mode))}; @@ -85,7 +86,7 @@ create_maildir(const std::string& path, mode_t mode) /* note, g_mkdir_with_parents won't detect an error if * there's already such a dir, but with the wrong * permissions; so we need to check */ - if (rv != 0 || !mu_util_check_dir(fullpath.c_str(), TRUE, TRUE)) + if (rv != 0 || !check_dir(fullpath, true/*readable*/, true/*writable*/)) return Err(Error{Error::Code::File, "creating dir failed for %s: %s", fullpath.c_str(), g_strerror(errno)}); diff --git a/lib/mu-store.cc b/lib/mu-store.cc index f34b6d40..880a7661 100644 --- a/lib/mu-store.cc +++ b/lib/mu-store.cc @@ -41,6 +41,7 @@ #include "utils/mu-error.hh" #include "utils/mu-utils.hh" +#include #include "utils/mu-xapian-utils.hh" using namespace Mu; diff --git a/lib/tests/test-mu-maildir.cc b/lib/tests/test-mu-maildir.cc index d3713f64..5b9d545d 100644 --- a/lib/tests/test-mu-maildir.cc +++ b/lib/tests/test-mu-maildir.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2008-2022 Dirk-Jan C. Binnema +** Copyright (C) 2008-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 @@ -29,7 +29,6 @@ #include "utils/mu-test-utils.hh" #include "mu-maildir.hh" #include "utils/mu-result.hh" -#include "utils/mu-util.h" using namespace Mu; diff --git a/lib/tests/test-mu-store.cc b/lib/tests/test-mu-store.cc index 7801ad06..872c56e3 100644 --- a/lib/tests/test-mu-store.cc +++ b/lib/tests/test-mu-store.cc @@ -33,6 +33,7 @@ #include "mu-store.hh" #include "utils/mu-result.hh" #include +#include #include "mu-maildir.hh" using namespace Mu; diff --git a/lib/utils/meson.build b/lib/utils/meson.build index 93902e2b..b8e446af 100644 --- a/lib/utils/meson.build +++ b/lib/utils/meson.build @@ -16,22 +16,22 @@ lib_mu_utils=static_library('mu-utils', [ - 'mu-command-handler.cc', - 'mu-logger.cc', + 'mu-command-handler.cc', + 'mu-logger.cc', 'mu-option.cc', - 'mu-readline.cc', - 'mu-sexp.cc', + 'mu-readline.cc', + 'mu-sexp.cc', 'mu-test-utils.cc', - 'mu-util.c', - 'mu-utils.cc'], - dependencies: [ - glib_dep, + 'mu-utils.cc', + 'mu-utils-file.cc'], + dependencies: [ + glib_dep, gio_dep, - config_h_dep, - readline_dep - ], - include_directories: include_directories(['.','..']), - install: false) + config_h_dep, + readline_dep + ], + include_directories: include_directories(['.','..']), + install: false) lib_mu_utils_dep = declare_dependency( link_with: lib_mu_utils, @@ -43,14 +43,20 @@ lib_mu_utils_dep = declare_dependency( # test('test-sexp', executable('test-sexp', 'mu-sexp.cc', - install: false, - cpp_args: ['-DBUILD_TESTS'], - dependencies: [glib_dep, lib_mu_utils_dep])) + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, lib_mu_utils_dep])) test('test-command-handler', executable('test-command-handler', 'mu-command-handler.cc', - install: false, - cpp_args: ['-DBUILD_TESTS'], - dependencies: [glib_dep, lib_mu_utils_dep])) + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, lib_mu_utils_dep])) + +test('test-utils-file', + executable('test-utils-file', 'mu-utils-file.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, config_h_dep, lib_mu_utils_dep])) subdir('tests') diff --git a/lib/utils/mu-error.hh b/lib/utils/mu-error.hh index 9aa1c8ec..55a8002c 100644 --- a/lib/utils/mu-error.hh +++ b/lib/utils/mu-error.hh @@ -1,5 +1,5 @@ /* -** Copyright (C) 2019-2022 Dirk-Jan C. Binnema +** Copyright (C) 2019-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 @@ -24,7 +24,6 @@ #include #include "mu-utils-format.hh" -#include "mu-util.h" #include namespace Mu { @@ -164,11 +163,18 @@ struct Error final : public std::exception { * @param err GError** (or NULL) */ void fill_g_error(GError **err) const noexcept{ - g_set_error(err, MU_ERROR_DOMAIN, static_cast(code_), + g_set_error(err, error_quark(), static_cast(code_), "%s", what_.c_str()); } private: + static inline GQuark error_quark (void) { + static GQuark error_domain = 0; + if (G_UNLIKELY(error_domain == 0)) + error_domain = g_quark_from_static_string("mu-error-quark"); + return error_domain; + } + const Code code_; std::string what_; }; diff --git a/lib/utils/mu-util.c b/lib/utils/mu-util.c deleted file mode 100644 index a2e3bc61..00000000 --- a/lib/utils/mu-util.c +++ /dev/null @@ -1,468 +0,0 @@ -/* -** Copyright (C) 2008-2021 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 of the License, 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 - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif /*_GNU_SOURCE*/ - -#include "mu-util.h" -#ifdef HAVE_WORDEXP_H -#include /* for shell-style globbing */ -#endif /*HAVE_WORDEXP_H*/ - -#include - -#include -#include /* for setlocale() */ - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - - -static char* -do_wordexp (const char *path) -{ -#ifdef HAVE_WORDEXP_H - wordexp_t wexp; - char *dir; - - if (!path) { - /* g_debug ("%s: path is empty", __func__); */ - return NULL; - } - - if (wordexp (path, &wexp, 0) != 0) { - /* g_debug ("%s: expansion failed for %s", __func__, path); */ - return NULL; - } - - /* we just pick the first one */ - dir = g_strdup (wexp.we_wordv[0]); - - /* strangely, below seems to lead to a crash on MacOS (BSD); - so we have to allow for a tiny leak here on that - platform... maybe instead of __APPLE__ it should be - __BSD__? - - Hmmm., cannot reproduce that crash anymore, so commenting - it out for now... - */ -/* #ifndef __APPLE__ */ - wordfree (&wexp); -/* #endif /\*__APPLE__*\/ */ - return dir; - -# else /*!HAVE_WORDEXP_H*/ -/* E.g. OpenBSD does not have wordexp.h, so we ignore it */ - return path ? g_strdup (path) : NULL; -#endif /*HAVE_WORDEXP_H*/ -} - - -/* note, the g_debugs are commented out because this function may be - * called before the log handler is installed. */ -char* -mu_util_dir_expand (const char *path) -{ - char *dir; - char resolved[PATH_MAX + 1]; - - g_return_val_if_fail (path, NULL); - - dir = do_wordexp (path); - if (!dir) - return NULL; /* error */ - - /* don't try realpath if the dir does not exist */ - if (access (dir, F_OK) != 0) - return dir; - - /* now resolve any symlinks, .. etc. */ - if (realpath (dir, resolved) == NULL) { - /* g_debug ("%s: could not get realpath for '%s': %s", */ - /* __func__, dir, g_strerror(errno)); */ - g_free (dir); - return NULL; - } else - g_free (dir); - - return g_strdup (resolved); -} - -GQuark -mu_util_error_quark (void) -{ - static GQuark error_domain = 0; - - if (G_UNLIKELY(error_domain == 0)) - error_domain = g_quark_from_static_string - ("mu-error-quark"); - - return error_domain; -} - -gboolean -mu_util_check_dir (const gchar* path, gboolean readable, gboolean writeable) -{ - int mode; - struct stat statbuf; - - if (!path) - return FALSE; - - mode = F_OK | (readable ? R_OK : 0) | (writeable ? W_OK : 0); - - if (access (path, mode) != 0) { - /* g_debug ("Cannot access %s: %s", path, g_strerror (errno)); */ - return FALSE; - } - - if (stat (path, &statbuf) != 0) { - /* g_debug ("Cannot stat %s: %s", path, g_strerror (errno)); */ - return FALSE; - } - - return S_ISDIR(statbuf.st_mode) ? TRUE: FALSE; -} - - -gchar* -mu_util_guess_maildir (void) -{ - const gchar *mdir1, *home; - - /* first, try MAILDIR */ - mdir1 = g_getenv ("MAILDIR"); - - if (mdir1 && mu_util_check_dir (mdir1, TRUE, FALSE)) - return g_strdup (mdir1); - - /* then, try /Maildir */ - home = g_get_home_dir(); - if (home) { - char *mdir2; - mdir2 = g_strdup_printf ("%s%cMaildir", - home, G_DIR_SEPARATOR); - if (mu_util_check_dir (mdir2, TRUE, FALSE)) - return mdir2; - g_free (mdir2); - } - - /* nope; nothing found */ - return NULL; -} - -gboolean -mu_util_create_dir_maybe (const gchar *path, mode_t mode, gboolean nowarn) -{ - struct stat statbuf; - - g_return_val_if_fail (path, FALSE); - - /* if it exists, it must be a readable dir */ - if (stat (path, &statbuf) == 0) { - if ((!S_ISDIR(statbuf.st_mode)) || - (access (path, W_OK|R_OK) != 0)) { - if (!nowarn) - g_warning ("not a read-writable" - "directory: %s", path); - return FALSE; - } - } - - if (g_mkdir_with_parents (path, mode) != 0) { - if (!nowarn) - g_warning ("failed to create %s: %s", - path, g_strerror(errno)); - return FALSE; - } - - return TRUE; -} - -gboolean -mu_util_supports (MuFeature feature) -{ - /* check for Guile support */ -#ifndef BUILD_GUILE - if (feature & MU_FEATURE_GUILE) - return FALSE; -#endif /*BUILD_GUILE*/ - - /* check for Gnuplot */ - if (feature & MU_FEATURE_GNUPLOT) - if (!mu_util_program_in_path ("gnuplot")) - return FALSE; - - return TRUE; -} - - -gboolean -mu_util_program_in_path (const char *prog) -{ - gchar *path; - - g_return_val_if_fail (prog, FALSE); - - path = g_find_program_in_path (prog); - g_free (path); - - return (path != NULL) ? TRUE : FALSE; -} - - -/* - * Set the child to a group leader to avoid being killed when the - * parent group is killed. - */ -static void -maybe_setsid (G_GNUC_UNUSED gpointer user_data) -{ -#if HAVE_SETSID - setsid(); -#endif /*HAVE_SETSID*/ -} - -gboolean -mu_util_play (const char *path, GError **err) -{ - GFile *gf; - gboolean rv, is_native; - const gchar *argv[3]; - const char *prog; - - g_return_val_if_fail (path, FALSE); - - gf = g_file_new_for_path(path); - is_native = g_file_is_native(gf); - g_object_unref(gf); - - if (!is_native) { - mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_EXECUTE, - "'%s' is not a native file", path); - return FALSE; - } - - prog = g_getenv ("MU_PLAY_PROGRAM"); - if (!prog) { -#ifdef __APPLE__ - prog = "open"; -#else - prog = "xdg-open"; -#endif /*!__APPLE__*/ - } - - if (!mu_util_program_in_path (prog)) { - mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_EXECUTE, - "cannot find '%s' in path", prog); - return FALSE; - } - - argv[0] = prog; - argv[1] = path; - argv[2] = NULL; - - err = NULL; - rv = g_spawn_async (NULL, (gchar**)&argv, NULL, - G_SPAWN_SEARCH_PATH, maybe_setsid, - NULL, NULL, err); - return rv; -} - - -unsigned char -mu_util_get_dtype (const char *path, gboolean use_lstat) -{ - int res; - struct stat statbuf; - - g_return_val_if_fail (path, DT_UNKNOWN); - - if (use_lstat) - res = lstat (path, &statbuf); - else - res = stat (path, &statbuf); - - if (res != 0) { - g_warning ("%sstat failed on %s: %s", - use_lstat ? "l" : "", path, g_strerror(errno)); - return DT_UNKNOWN; - } - - /* we only care about dirs, regular files and links */ - if (S_ISREG (statbuf.st_mode)) - return DT_REG; - else if (S_ISDIR (statbuf.st_mode)) - return DT_DIR; - else if (S_ISLNK (statbuf.st_mode)) - return DT_LNK; - - return DT_UNKNOWN; -} - - - -gboolean -mu_util_locale_is_utf8 (void) -{ - const gchar *dummy; - static int is_utf8 = -1; - - if (G_UNLIKELY(is_utf8 == -1)) - is_utf8 = g_get_charset(&dummy) ? 1 : 0; - - return is_utf8 ? TRUE : FALSE; -} - -gboolean -mu_util_fputs_encoded (const char *str, FILE *stream) -{ - int rv; - char *conv; - - g_return_val_if_fail (stream, FALSE); - - /* g_get_charset return TRUE when the locale is UTF8 */ - if (mu_util_locale_is_utf8()) - return fputs (str, stream) == EOF ? FALSE : TRUE; - - /* charset is _not_ utf8, so we need to convert it */ - conv = NULL; - if (g_utf8_validate (str, -1, NULL)) - conv = g_locale_from_utf8 (str, -1, NULL, NULL, NULL); - - /* conversion failed; this happens because is some cases GMime may gives - * us non-UTF-8 strings from e.g. wrongly encoded message-subjects; if - * so, we escape the string */ - conv = conv ? conv : g_strescape (str, "\n\t"); - rv = conv ? fputs (conv, stream) : EOF; - g_free (conv); - - return (rv == EOF) ? FALSE : TRUE; -} - - - -gboolean -mu_util_g_set_error (GError **err, MuError errcode, const char *frm, ...) -{ - va_list ap; - char *msg; - - /* don't bother with NULL errors, or errors already set */ - if (!err || *err) - return FALSE; - - msg = NULL; - va_start (ap, frm); - g_vasprintf (&msg, frm, ap); - va_end (ap); - - g_set_error (err, MU_ERROR_DOMAIN, errcode, "%s", msg); - - g_free (msg); - - return FALSE; -} - - - -__attribute__((format(printf, 2, 0))) static gboolean -print_args (FILE *stream, const char *frm, va_list args) -{ - gchar *str; - gboolean rv; - - str = g_strdup_vprintf (frm, args); - - rv = mu_util_fputs_encoded (str, stream); - - g_free (str); - - return rv; -} - - -gboolean -mu_util_print_encoded (const char *frm, ...) -{ - va_list args; - gboolean rv; - - g_return_val_if_fail (frm, FALSE); - - va_start (args, frm); - rv = print_args (stdout, frm, args); - va_end (args); - - return rv; -} - -char* -mu_str_summarize (const char* str, size_t max_lines) -{ - char *summary; - size_t nl_seen; - unsigned i,j; - gboolean last_was_blank; - - g_return_val_if_fail (str, NULL); - g_return_val_if_fail (max_lines > 0, NULL); - - /* len for summary <= original len */ - summary = g_new (gchar, strlen(str) + 1); - - /* copy the string up to max_lines lines, replace CR/LF/tab with - * single space */ - for (i = j = 0, nl_seen = 0, last_was_blank = TRUE; - nl_seen < max_lines && str[i] != '\0'; ++i) { - - if (str[i] == '\n' || str[i] == '\r' || - str[i] == '\t' || str[i] == ' ' ) { - - if (str[i] == '\n') - ++nl_seen; - - /* no double-blanks or blank at end of str */ - if (!last_was_blank && str[i+1] != '\0') - summary[j++] = ' '; - - last_was_blank = TRUE; - } else { - - summary[j++] = str[i]; - last_was_blank = FALSE; - } - } - - summary[j] = '\0'; - return summary; -} diff --git a/lib/utils/mu-util.h b/lib/utils/mu-util.h deleted file mode 100644 index 3dedc0af..00000000 --- a/lib/utils/mu-util.h +++ /dev/null @@ -1,349 +0,0 @@ -/* -** Copyright (C) 2008-2022 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 of the License, 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_UTIL_H__ -#define __MU_UTIL_H__ - -#include -#include -#include -#include /* for mode_t */ - -/* hopefully, this should get us a sane PATH_MAX */ -#include -/* not all systems provide PATH_MAX in limits.h */ -#ifndef PATH_MAX -#include -#ifndef PATH_MAX -#define PATH_MAX MAXPATHLEN -#endif /*!PATH_MAX*/ -#endif /*PATH_MAX*/ - -G_BEGIN_DECLS - -/** - * get the expanded path; ie. perform shell expansion on the path. the - * path does not have to exist - * - * @param path path to expand - * - * @return the expanded path as a newly allocated string, or NULL in - * case of error - */ -char* mu_util_dir_expand(const char* path) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; - -/** - * guess the maildir; first try $MAILDIR; if it is unset or - * non-existent, try ~/Maildir if both fail, return NULL - * - * @return full path of the guessed Maildir, or NULL; must be freed (gfree) - */ -char* mu_util_guess_maildir (void) - G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; - -/** - * if path exists, check that's a read/writeable dir; otherwise try to - * create it (with perms 0700) - * - * @param path path to the dir - * @param mode to set for the dir (as per chmod(1)) - * @param nowarn, if TRUE, don't write warnings (if any) to stderr - * - * @return TRUE if a read/writeable directory `path' exists after - * leaving this function, FALSE otherwise - */ -gboolean mu_util_create_dir_maybe (const gchar *path, mode_t mode, - gboolean nowarn) G_GNUC_WARN_UNUSED_RESULT; - -/** - * check whether path is a directory, and optionally, if it's readable - * and/or writeable - * - * @param path dir path - * @param readable check for readability - * @param writeable check for writability - * - * @return TRUE if dir exist and has the specified properties - */ -gboolean mu_util_check_dir (const gchar* path, gboolean readable, - gboolean writeable) - G_GNUC_WARN_UNUSED_RESULT; - -/** - * is the current locale utf-8 compatible? - * - * @return TRUE if it's utf8 compatible, FALSE otherwise - */ -gboolean mu_util_locale_is_utf8 (void) G_GNUC_CONST; - -/** - * get a 'summary' of the string, ie. the first /n/ lines of the - * strings, with all newlines removed, replaced by single spaces - * - * @param str the source string - * @param max_lines the maximum number of lines to include in the summary - * - * @return a newly allocated string with the summary. use g_free to free it. - */ -char* mu_str_summarize (const char* str, size_t max_lines) - G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; - - -/** - * write a string (assumed to be in utf8-format) to a stream, - * converted to the current locale - * - * @param str a string - * @param stream a stream - * - * @return TRUE if printing worked, FALSE otherwise - */ -gboolean mu_util_fputs_encoded (const char *str, FILE *stream); - -/** - * print a formatted string (assumed to be in utf8-format) to stdout, - * converted to the current locale - * - * @param a standard printf() format string, followed by a parameter list - * - * @return TRUE if printing worked, FALSE otherwise - */ -gboolean mu_util_print_encoded (const char *frm, ...) G_GNUC_PRINTF(1,2); - - -/** - * Try to 'play' (ie., open with it's associated program) a file. On MacOS, the - * the program 'open' is used for this; on other platforms 'xdg-open' to do the - * actual opening. In addition you can set it to another program by setting the - * MU_PLAY_PROGRAM environment variable - * - * This requires a 'native' file, see g_file_is_native() - * - * @param path full path of the file to open - * @param err receives error information, if any - * - * @return TRUE if it succeeded, FALSE otherwise - */ -gboolean mu_util_play (const char *path, GError **err); - -/** - * Check whether program prog exists in PATH - * - * @param prog a program (executable) - * - * @return TRUE if it exists and is executable, FALSE otherwise - */ -gboolean mu_util_program_in_path (const char *prog); - - -enum _MuFeature { - MU_FEATURE_GUILE = 1 << 0, /* do we support Guile 2.0? */ - MU_FEATURE_GNUPLOT = 1 << 1, /* do we have gnuplot installed? */ -}; -typedef enum _MuFeature MuFeature; - -/** - * Check whether mu supports some particular feature - * - * @param feature a feature (multiple features can be logical-or'd together) - * - * @return TRUE if the feature is supported, FALSE otherwise - */ -gboolean mu_util_supports (MuFeature feature); - - - -/** - * Get an error-query for mu, to be used in `g_set_error'. Recent - * version of Glib warn when using 0 for the error-domain in - * g_set_error. - * - * - * @return an error quark for mu - */ -GQuark mu_util_error_quark (void) G_GNUC_CONST; -#define MU_ERROR_DOMAIN (mu_util_error_quark()) - - -/* - * for OSs with out support for direntry->d_type, like Solaris - */ -#ifndef DT_UNKNOWN -enum { - DT_UNKNOWN = 0, -#define DT_UNKNOWN DT_UNKNOWN - DT_FIFO = 1, -#define DT_FIFO DT_FIFO - DT_CHR = 2, -#define DT_CHR DT_CHR - DT_DIR = 4, -#define DT_DIR DT_DIR - DT_BLK = 6, -#define DT_BLK DT_BLK - DT_REG = 8, -#define DT_REG DT_REG - DT_LNK = 10, -#define DT_LNK DT_LNK - DT_SOCK = 12, -#define DT_SOCK DT_SOCK - DT_WHT = 14 -#define DT_WHT DT_WHT -}; -#endif /*DT_UNKNOWN*/ - - -/** - * get the d_type (as in direntry->d_type) for the file at path, using either - * stat(3) or lstat(3) - * - * @param path full path - * @param use_lstat whether to use lstat (otherwise use stat) - * - * @return DT_REG, DT_DIR, DT_LNK, or DT_UNKNOWN (other values are not supported - * currently ) - */ -unsigned char mu_util_get_dtype (const char *path, gboolean use_lstat); - - -/** - * we need this when using Xapian::Document* from C - * - */ -typedef gpointer XapianDocument; - -/** - * we need this when using Xapian::Enquire* from C - * - */ -typedef gpointer XapianEnquire; - - -/* print a warning for a GError, and free it */ -#define MU_HANDLE_G_ERROR(GE) \ - do { \ - if (!(GE)) \ - g_warning ("%s:%u: an error occurred in %s", \ - __FILE__, __LINE__, __func__); \ - else { \ - g_warning ("error %u: %s", (GE)->code, (GE)->message); \ - g_error_free ((GE)); \ - } \ - } while (0) - - -#define MU_G_ERROR_CODE(GE) ((GE)&&(*(GE))?(MuError)(*(GE))->code:MU_ERROR) - - -enum _MuError { - /* no error at all! */ - MU_OK = 0, - - /* generic error */ - MU_ERROR = 1, - MU_ERROR_IN_PARAMETERS = 2, - MU_ERROR_INTERNAL = 3, - MU_ERROR_NO_MATCHES = 4, - - /* not really an error; for callbacks */ - MU_IGNORE = 5, - - MU_ERROR_SCRIPT_NOT_FOUND = 8, - - /* general xapian related error */ - MU_ERROR_XAPIAN = 11, - - /* (parsing) error in the query */ - MU_ERROR_XAPIAN_QUERY = 13, - - /* missing data for a document */ - MU_ERROR_XAPIAN_MISSING_DATA = 17, - /* can't get write lock */ - MU_ERROR_XAPIAN_CANNOT_GET_WRITELOCK = 19, - /* could not write */ - MU_ERROR_XAPIAN_STORE_FAILED = 21, - /* could not remove */ - MU_ERROR_XAPIAN_REMOVE_FAILED = 22, - /* database was modified; reload */ - MU_ERROR_XAPIAN_MODIFIED = 23, - /* database was modified; reload */ - MU_ERROR_XAPIAN_NEEDS_REINDEX = 24, - /* database schema version doesn't match */ - MU_ERROR_XAPIAN_SCHEMA_MISMATCH = 25, - /* failed to open the database */ - MU_ERROR_XAPIAN_CANNOT_OPEN = 26, - - /* GMime related errors */ - - /* gmime parsing related error */ - MU_ERROR_GMIME = 30, - - /* contacts related errors */ - MU_ERROR_CONTACTS = 50, - MU_ERROR_CONTACTS_CANNOT_RETRIEVE = 51, - - /* crypto related errors */ - MU_ERROR_CRYPTO = 60, - - - /* File errors */ - /* generic file-related error */ - MU_ERROR_FILE = 70, - MU_ERROR_FILE_INVALID_NAME = 71, - MU_ERROR_FILE_CANNOT_LINK = 72, - MU_ERROR_FILE_CANNOT_OPEN = 73, - MU_ERROR_FILE_CANNOT_READ = 74, - MU_ERROR_FILE_CANNOT_EXECUTE = 75, - MU_ERROR_FILE_CANNOT_CREATE = 76, - MU_ERROR_FILE_CANNOT_MKDIR = 77, - MU_ERROR_FILE_STAT_FAILED = 78, - MU_ERROR_FILE_READDIR_FAILED = 79, - MU_ERROR_FILE_INVALID_SOURCE = 80, - MU_ERROR_FILE_TARGET_EQUALS_SOURCE = 81, - MU_ERROR_FILE_CANNOT_WRITE = 82, - MU_ERROR_FILE_CANNOT_UNLINK = 83, - - /* not really an error, used in callbacks */ - MU_STOP = 99 -}; -typedef enum _MuError MuError; - - -/** - * set an error if it's not already set, and return FALSE - * - * @param err errptr, or NULL - * @param errcode error code - * @param frm printf-style format, followed by parameters - * - * @return FALSE - */ -gboolean mu_util_g_set_error (GError **err, MuError errcode, const char *frm, ...) - G_GNUC_PRINTF(3,4); - -#define MU_COLOR_RED "\x1b[31m" -#define MU_COLOR_GREEN "\x1b[32m" -#define MU_COLOR_YELLOW "\x1b[33m" -#define MU_COLOR_BLUE "\x1b[34m" -#define MU_COLOR_MAGENTA "\x1b[35m" -#define MU_COLOR_CYAN "\x1b[36m" -#define MU_COLOR_DEFAULT "\x1b[0m" - -G_END_DECLS - -#endif /*__MU_UTIL_H__*/ diff --git a/lib/utils/mu-utils-file.cc b/lib/utils/mu-utils-file.cc new file mode 100644 index 00000000..9bc8c1d6 --- /dev/null +++ b/lib/utils/mu-utils-file.cc @@ -0,0 +1,285 @@ +/* +** 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 "config.h" + +#include "mu-utils.hh" +#include "mu-utils-file.hh" + +#include + +#include +#include + +using namespace Mu; + + +Mu::Option +Mu::program_in_path(const std::string& name) +{ + if (char *path = g_find_program_in_path(name.c_str()); path) + return to_string_gchar(std::move(path)/*consumes*/); + else + return Nothing; +} + +/* + * Set the child to a group leader to avoid being killed when the + * parent group is killed. + */ +static void +maybe_setsid (G_GNUC_UNUSED gpointer user_data) +{ +#if HAVE_SETSID + setsid(); +#endif /*HAVE_SETSID*/ +} + +Mu::Result +Mu::play (const std::string& path) +{ + /* check nativity */ + GFile *gf = g_file_new_for_path(path.c_str()); + auto is_native = g_file_is_native(gf); + g_object_unref(gf); + if (!is_native) + return Err(Error::Code::File, "'%s' is not a native file", path.c_str()); + + const char *prog{g_getenv ("MU_PLAY_PROGRAM")}; + if (!prog) { +#ifdef __APPLE__ + prog = "open"; +#else + prog = "xdg-open"; +#endif /*!__APPLE__*/ + } + + const auto program_path{program_in_path(prog)}; + if (!program_path) + return Err(Error::Code::File, "cannot find '%s' in path", prog); + + const gchar *argv[3]{}; + argv[0] = program_path->c_str(); + argv[1] = path.c_str(); + argv[2] = nullptr; + + GError *err{}; + if (!g_spawn_async ({}, (gchar**)&argv, {}, G_SPAWN_SEARCH_PATH, maybe_setsid, + {}, {}, &err)) + return Err(Error::Code::File, &err/*consumes*/, "failed to open '%s' with '%s'", + path. c_str(), program_path->c_str()); + + return Ok(); +} + + +bool +Mu::check_dir (const std::string& path, bool readable, bool writeable) +{ + const auto mode = F_OK | (readable ? R_OK : 0) | (writeable ? W_OK : 0); + + if (::access (path.c_str(), mode) != 0) + return false; + + struct stat statbuf{}; + if (::stat (path.c_str(), &statbuf) != 0) + return false; + + return S_ISDIR(statbuf.st_mode) ? true : false; +} + +uint8_t +Mu::determine_dtype (const std::string& path, bool use_lstat) +{ + int res; + struct stat statbuf{}; + + if (use_lstat) + res = ::lstat(path.c_str(), &statbuf); + else + res = ::stat(path.c_str(), &statbuf); + + if (res != 0) { + g_warning ("%sstat failed on %s: %s", + use_lstat ? "l" : "", path.c_str(), g_strerror(errno)); + return DT_UNKNOWN; + } + + /* we only care about dirs, regular files and links */ + if (S_ISREG (statbuf.st_mode)) + return DT_REG; + else if (S_ISDIR (statbuf.st_mode)) + return DT_DIR; + else if (S_ISLNK (statbuf.st_mode)) + return DT_LNK; + + return DT_UNKNOWN; +} + +std::string +Mu::canonicalize_filename(const std::string& path, const std::string& relative_to) +{ + auto str{to_string_opt_gchar( + g_canonicalize_filename( + path.c_str(), + relative_to.empty() ? nullptr : relative_to.c_str())).value()}; + + // remove trailing '/'... is this needed? + if (str[str.length()-1] == G_DIR_SEPARATOR) + str.erase(str.length() - 1); + + return str; +} + + +std::string +Mu::runtime_path(Mu::RuntimePath path, const std::string& muhome) +{ + auto [mu_cache, mu_config] = + std::invoke([&]()->std::pair { + + static std::string mu{"/mu"}; + if (muhome.empty()) + return { g_get_user_cache_dir() + mu, + g_get_user_config_dir() + mu }; + else + return { muhome, muhome }; + }); + + switch (path) { + case Mu::RuntimePath::Cache: + return mu_cache; + case Mu::RuntimePath::XapianDb: + return mu_cache + "/xapian"; + case Mu::RuntimePath::LogFile: + return mu_cache + "/mu.log"; + case Mu::RuntimePath::Bookmarks: + return mu_config + "/bookmarks"; + case Mu::RuntimePath::Config: + return mu_config; + case Mu::RuntimePath::Scripts: + return mu_config + "/scripts"; + default: + throw std::logic_error("unknown path"); + } +} + + +#ifdef BUILD_TESTS + +/* + * Tests. + * + */ + +#include +#include +#include +#include + +#include "utils/mu-test-utils.hh" + +static void +test_check_dir_01(void) +{ + if (g_access("/usr/bin", F_OK) == 0) { + g_assert_cmpuint( + check_dir("/usr/bin", true, false) == true, + ==, + g_access("/usr/bin", R_OK) == 0); + } +} + +static void +test_check_dir_02(void) +{ + if (g_access("/tmp", F_OK) == 0) { + g_assert_cmpuint( + check_dir("/tmp", false, true) == true, + ==, + g_access("/tmp", W_OK) == 0); + } +} + +static void +test_check_dir_03(void) +{ + if (g_access(".", F_OK) == 0) { + g_assert_cmpuint( + check_dir(".", true, true) == true, + ==, + g_access(".", W_OK | R_OK) == 0); + } +} + +static void +test_check_dir_04(void) +{ + /* not a dir, so it must be false */ + g_assert_cmpuint( + check_dir("test-util.c", true, true), + ==, + false); +} + +static void +test_determine_dtype_with_lstat(void) +{ + g_assert_cmpuint( + determine_dtype(MU_TESTMAILDIR, true), ==, DT_DIR); + g_assert_cmpuint( + determine_dtype(MU_TESTMAILDIR2, true), ==, DT_DIR); + g_assert_cmpuint( + determine_dtype(MU_TESTMAILDIR2 "/Foo/cur/mail5", true), + ==, DT_REG); +} + + +static void +test_program_in_path(void) +{ + g_assert_true(!!program_in_path("ls")); +} + + +int +main(int argc, char* argv[]) +{ + mu_test_init(&argc, &argv); + + /* check_dir */ + g_test_add_func("/utils/check-dir-01", + test_check_dir_01); + g_test_add_func("/utils/check-dir-02", + test_check_dir_02); + g_test_add_func("/utils/check-dir-03", + test_check_dir_03); + g_test_add_func("/utils/check-dir-04", + test_check_dir_04); + + g_test_add_func("/utils/determine-dtype-with-lstat", + test_determine_dtype_with_lstat); + + g_test_add_func("/utils/program-in-path", + test_program_in_path); + + return g_test_run(); +} + +#endif /*BUILD_TESTS*/ diff --git a/lib/utils/mu-utils-file.hh b/lib/utils/mu-utils-file.hh new file mode 100644 index 00000000..f47ada5a --- /dev/null +++ b/lib/utils/mu-utils-file.hh @@ -0,0 +1,141 @@ +/* +** 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_UTILS_FILE_HH__ +#define MU_UTILS_FILE_HH__ + +#include +#include +#include + +#include +#include + +namespace Mu { + +/** + * Try to 'play' (ie., open with it's associated program) a file. On MacOS, the + * the program 'open' is used for this; on other platforms 'xdg-open' to do the + * actual opening. In addition you can set it to another program by setting thep + * MU_PLAY_PROGRAM environment variable + * + * This requires a 'native' file, see g_file_is_native() + * + * @param path full path of the file to open + * + * @return Ok() if succeeded, some error otherwise. + */ +Result play(const std::string& path); + +/** + * Find program in PATH + * + * @param name the name of the program + * + * @return either the full path to program, or Nothing if not found. + */ +Option program_in_path(const std::string& name); + +/** + * Check if the directory has the given attributes + * + * @param path path to dir + * @param readable is it readable? + * @param writeable is it writable? + * + * @return true if is is a directory with given attributes; false otherwise. + */ +bool check_dir(const std::string& path, bool readable, bool writeable); + +/** + * See g_canonicalize_filename + * + * @param filename + * @param relative_to + * + * @return + */ +std::string canonicalize_filename(const std::string& path, const std::string& relative_to); + +/* + * for OSs with out support for direntry->d_type, like Solaris + */ +#ifndef DT_UNKNOWN +enum { + DT_UNKNOWN = 0, +#define DT_UNKNOWN DT_UNKNOWN + DT_FIFO = 1, +#define DT_FIFO DT_FIFO + DT_CHR = 2, +#define DT_CHR DT_CHR + DT_DIR = 4, +#define DT_DIR DT_DIR + DT_BLK = 6, +#define DT_BLK DT_BLK + DT_REG = 8, +#define DT_REG DT_REG + DT_LNK = 10, +#define DT_LNK DT_LNK + DT_SOCK = 12, +#define DT_SOCK DT_SOCK + DT_WHT = 14 +#define DT_WHT DT_WHT +}; +#endif /*DT_UNKNOWN*/ + + /** + * get the d_type (as in direntry->d_type) for the file at path, using either + * stat(3) or lstat(3) + * + * @param path full path + * @param use_lstat whether to use lstat (otherwise use stat) + * + * @return DT_REG, DT_DIR, DT_LNK, or DT_UNKNOWN (other values are not supported + * currently) + */ +uint8_t determine_dtype(const std::string& path, bool use_lstat); + + +/** + * Well-known runtime paths + * + */ +enum struct RuntimePath { + XapianDb, + Cache, + LogFile, + Config, + Scripts, + Bookmarks +}; + +/** + * Get some well-known Path for internal use when don't have + * access to the command-line + * + * @param path the RuntimePath to find + * @param muhome path to muhome directory, or empty for the default. + * + * @return the path name + */ +std::string runtime_path(RuntimePath path, const std::string& muhome=""); + +} +#endif /* MU_UTILS_FILE_HH__ */ + diff --git a/lib/utils/mu-utils.cc b/lib/utils/mu-utils.cc index 356e5d8c..151b2213 100644 --- a/lib/utils/mu-utils.cc +++ b/lib/utils/mu-utils.cc @@ -45,7 +45,6 @@ #include "mu-utils.hh" #include "mu-utils-format.hh" -#include "mu-util.h" #include "mu-error.hh" #include "mu-option.hh" @@ -572,23 +571,6 @@ Mu::from_lexnum(const std::string& str) return val; } - -std::string -Mu::canonicalize_filename(const std::string& path, const std::string& relative_to) -{ - auto str{to_string_opt_gchar( - g_canonicalize_filename( - path.c_str(), - relative_to.empty() ? nullptr : relative_to.c_str())).value()}; - - // remove trailing '/'... is this needed? - if (str[str.length()-1] == G_DIR_SEPARATOR) - str.erase(str.length() - 1); - - return str; -} - - bool Mu::locale_workaround() try { @@ -627,35 +609,110 @@ Mu::timezone_available(const std::string& tz) return have_tz; } - std::string -Mu::runtime_path(Mu::RuntimePath path, const std::string& muhome) +Mu::summarize(const std::string& str, size_t max_lines) { - auto [mu_cache, mu_config] = - std::invoke([&]()->std::pair { + size_t nl_seen; + unsigned i,j; + gboolean last_was_blank; - static std::string mu{"/mu"}; - if (muhome.empty()) - return { g_get_user_cache_dir() + mu, - g_get_user_config_dir() + mu }; - else - return { muhome, muhome }; - }); + if (str.empty()) + return {}; - switch (path) { - case Mu::RuntimePath::Cache: - return mu_cache; - case Mu::RuntimePath::XapianDb: - return mu_cache + "/xapian"; - case Mu::RuntimePath::LogFile: - return mu_cache + "/mu.log"; - case Mu::RuntimePath::Bookmarks: - return mu_config + "/bookmarks"; - case Mu::RuntimePath::Config: - return mu_config; - case Mu::RuntimePath::Scripts: - return mu_config + "/scripts"; - default: - throw std::logic_error("unknown path"); + /* len for summary <= original len */ + char *summary = g_new (gchar, str.length() + 1); + + /* copy the string up to max_lines lines, replace CR/LF/tab with + * single space */ + for (i = j = 0, nl_seen = 0, last_was_blank = TRUE; + nl_seen < max_lines && i < str.length(); ++i) { + + if (str[i] == '\n' || str[i] == '\r' || + str[i] == '\t' || str[i] == ' ' ) { + + if (str[i] == '\n') + ++nl_seen; + + /* no double-blanks or blank at end of str */ + if (!last_was_blank && str[i+1] != '\0') + summary[j++] = ' '; + + last_was_blank = TRUE; + } else { + + summary[j++] = str[i]; + last_was_blank = FALSE; + } } + + summary[j] = '\0'; + + return to_string_gchar(std::move(summary)/*consumes*/); +} + + + + +static bool +locale_is_utf8 (void) +{ + const gchar *dummy; + static int is_utf8 = -1; + if (G_UNLIKELY(is_utf8 == -1)) + is_utf8 = g_get_charset(&dummy) ? 1 : 0; + + return !!is_utf8; +} + +bool +Mu::fputs_encoded (const std::string& str, FILE *stream) +{ + g_return_val_if_fail (stream, false); + + /* g_get_charset return TRUE when the locale is UTF8 */ + if (locale_is_utf8()) + return ::fputs (str.c_str(), stream) == EOF ? false: true; + + /* charset is _not_ utf8, so we need to convert it */ + char *conv{}; + if (g_utf8_validate (str.c_str(), -1, NULL)) + conv = g_locale_from_utf8 (str.c_str(), -1, {}, {}, {}); + + /* conversion failed; this happens because is some cases GMime may gives + * us non-UTF-8 strings from e.g. wrongly encoded message-subjects; if + * so, we escape the string */ + conv = conv ? conv : g_strescape (str.c_str(), "\n\t"); + int rv = conv ? ::fputs (conv, stream) : EOF; + g_free (conv); + + return (rv == EOF) ? false: true; +} + +__attribute__((format(printf, 2, 0))) +static bool +print_args (FILE *stream, const char *frm, va_list args) +{ + gchar *str; + gboolean rv; + + str = g_strdup_vprintf (frm, args); + rv = fputs_encoded (str, stream); + g_free (str); + + return rv; +} + +bool +Mu::print_encoded (const char *frm, ...) +{ + va_list args; + gboolean rv; + + g_return_val_if_fail (frm, false); + + va_start (args, frm); + rv = print_args (stdout, frm, args); + va_end (args); + + return rv; } diff --git a/lib/utils/mu-utils.hh b/lib/utils/mu-utils.hh index 41c02faf..4f77b08e 100644 --- a/lib/utils/mu-utils.hh +++ b/lib/utils/mu-utils.hh @@ -109,6 +109,27 @@ static inline std::string join(const std::vector& svec, char sepa) return join(svec, std::string(1, sepa)); } +/** + * write a string (assumed to be in utf8-format) to a stream, + * converted to the current locale + * + * @param str a string + * @param stream a stream + * + * @return true if printing worked, false otherwise + */ +bool fputs_encoded (const std::string& str, FILE *stream); + +/** + * print a formatted string (assumed to be in utf8-format) to stdout, + * converted to the current locale + * + * @param a standard printf() format string, followed by a parameter list + * + * @return true if printing worked, false otherwise + */ +bool print_encoded (const char *frm, ...) G_GNUC_PRINTF(1,2); + /** * Parse a date string to the corresponding time_t * * @@ -162,30 +183,6 @@ bool locale_workaround(); */ bool timezone_available(const std::string& tz); -/** - * Well-known runtime paths - * - */ -enum struct RuntimePath { - XapianDb, - Cache, - LogFile, - Config, - Scripts, - Bookmarks -}; - -/** - * Get some well-known Path for internal use when don't have - * access to the command-line - * - * @param path the RuntimePath to find - * @param muhome path to muhome directory, or empty for the default. - * - * @return the path name - */ -std::string runtime_path(RuntimePath path, const std::string& muhome=""); - // https://stackoverflow.com/questions/19053351/how-do-i-use-a-custom-deleter-with-a-stdunique-ptr-member template @@ -246,16 +243,6 @@ private: std::string name_; }; -/** - * See g_canonicalize_filename - * - * @param filename - * @param relative_to - * - * @return - */ -std::string canonicalize_filename(const std::string& path, const std::string& relative_to); - /** * Convert a size string to a size in bytes * @@ -276,6 +263,17 @@ Option parse_size(const std::string& sizestr, bool first); */ std::string size_to_string(int64_t size); +/** + * get a crude 'summary' of the string, ie. the first /n/ lines of the strings, + * with all newlines removed, replaced by single spaces + * + * @param str the source string + * @param max_lines the maximum number of lines to include in the summary + * + * @return a newly allocated string with the summary. use g_free to free it. + */ +std::string summarize(const std::string& str, size_t max_lines); + /** * Convert any ostreamable<< value to a string * @@ -446,7 +444,6 @@ to_second(const P& p, typename P::value_type::first_type f) return Nothing; } - /** * Convert string view in something printable with %.*s */ @@ -489,6 +486,15 @@ private: const bool color_; }; +#define MU_COLOR_RED "\x1b[31m" +#define MU_COLOR_GREEN "\x1b[32m" +#define MU_COLOR_YELLOW "\x1b[33m" +#define MU_COLOR_BLUE "\x1b[34m" +#define MU_COLOR_MAGENTA "\x1b[35m" +#define MU_COLOR_CYAN "\x1b[36m" +#define MU_COLOR_DEFAULT "\x1b[0m" + + /// Allow using enum structs as bitflags #define MU_TO_NUM(ET, ELM) std::underlying_type_t(ELM) #define MU_TO_ENUM(ET, NUM) static_cast(NUM) diff --git a/lib/utils/tests/meson.build b/lib/utils/tests/meson.build index 335fcfa7..83d4e492 100644 --- a/lib/utils/tests/meson.build +++ b/lib/utils/tests/meson.build @@ -18,18 +18,13 @@ ################################################################################ # tests # -test('test-mu-util', - executable('test-mu-util', - 'test-mu-util.c', - install: false, - dependencies: [glib_dep,config_h_dep, lib_mu_utils_dep])) test('test-option', executable('test-option', - 'test-option.cc', - install: false, - dependencies: [glib_dep, lib_mu_utils_dep])) + 'test-option.cc', + install: false, + dependencies: [glib_dep, lib_mu_utils_dep])) test('test-mu-utils', executable('test-mu-utils', - 'test-utils.cc', - install: false, - dependencies: [glib_dep, lib_mu_utils_dep])) + 'test-utils.cc', + install: false, + dependencies: [glib_dep, lib_mu_utils_dep])) diff --git a/lib/utils/tests/test-mu-util.c b/lib/utils/tests/test-mu-util.c deleted file mode 100644 index e453ea58..00000000 --- a/lib/utils/tests/test-mu-util.c +++ /dev/null @@ -1,286 +0,0 @@ -/* -** Copyright (C) 2008-2013 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. -** -*/ - -#if HAVE_CONFIG_H -#include "config.h" -#endif /*HAVE_CONFIG_H*/ - -#include -#include - -#include -#include -#include - -#include "mu-util.h" - -static void -test_mu_util_dir_expand_00(void) -{ -#ifdef HAVE_WORDEXP_H - gchar *got, *expected; - - got = mu_util_dir_expand("~/IProbablyDoNotExist"); - expected = g_strdup_printf("%s%cIProbablyDoNotExist", - getenv("HOME"), G_DIR_SEPARATOR); - - g_assert_cmpstr(got, ==, expected); - - g_free(got); - g_free(expected); -#endif /*HAVE_WORDEXP_H*/ -} - -static void -test_mu_util_dir_expand_01(void) -{ - /* XXXX: the testcase does not work when using some dir - * setups; (see issue #585), although the code should still - * work. Turn of the test for now */ - return; - -#ifdef HAVE_WORDEXP_H - { - gchar *got, *expected; - - got = mu_util_dir_expand("~/Desktop"); - expected = g_strdup_printf("%s%cDesktop", - getenv("HOME"), G_DIR_SEPARATOR); - - g_assert_cmpstr(got, ==, expected); - - g_free(got); - g_free(expected); - } -#endif /*HAVE_WORDEXP_H*/ -} - -static void -test_mu_util_guess_maildir_01(void) -{ - char* got; - const char* expected; - - /* skip the test if there's no /tmp */ - if (access("/tmp", F_OK)) - return; - - g_setenv("MAILDIR", "/tmp", TRUE); - - got = mu_util_guess_maildir(); - expected = "/tmp"; - - g_assert_cmpstr(got, ==, expected); - g_free(got); -} - -static void -test_mu_util_guess_maildir_02(void) -{ - char *got, *mdir; - - g_unsetenv("MAILDIR"); - - mdir = g_strdup_printf("%s%cMaildir", - getenv("HOME"), G_DIR_SEPARATOR); - got = mu_util_guess_maildir(); - - if (access(mdir, F_OK) == 0) - g_assert_cmpstr(got, ==, mdir); - else - g_assert_cmpstr(got, ==, NULL); - - g_free(got); - g_free(mdir); -} - -static void -test_mu_util_check_dir_01(void) -{ - if (g_access("/usr/bin", F_OK) == 0) { - g_assert_cmpuint( - mu_util_check_dir("/usr/bin", TRUE, FALSE) == TRUE, - ==, - g_access("/usr/bin", R_OK) == 0); - } -} - -static void -test_mu_util_check_dir_02(void) -{ - if (g_access("/tmp", F_OK) == 0) { - g_assert_cmpuint( - mu_util_check_dir("/tmp", FALSE, TRUE) == TRUE, - ==, - g_access("/tmp", W_OK) == 0); - } -} - -static void -test_mu_util_check_dir_03(void) -{ - if (g_access(".", F_OK) == 0) { - g_assert_cmpuint( - mu_util_check_dir(".", TRUE, TRUE) == TRUE, - ==, - g_access(".", W_OK | R_OK) == 0); - } -} - -static void -test_mu_util_check_dir_04(void) -{ - /* not a dir, so it must be false */ - g_assert_cmpuint( - mu_util_check_dir("test-util.c", TRUE, TRUE), - ==, - FALSE); -} - -static void -test_mu_util_get_dtype_with_lstat(void) -{ - g_assert_cmpuint( - mu_util_get_dtype(MU_TESTMAILDIR, TRUE), ==, DT_DIR); - g_assert_cmpuint( - mu_util_get_dtype(MU_TESTMAILDIR2, TRUE), ==, DT_DIR); - g_assert_cmpuint( - mu_util_get_dtype(MU_TESTMAILDIR2 "/Foo/cur/mail5", TRUE), - ==, DT_REG); -} - -static void -test_mu_util_supports(void) -{ - gboolean has_guile; - gchar* path; - -#ifdef BUILD_GUILE - has_guile = TRUE; -#else - has_guile = FALSE; -#endif /*BUILD_GUILE*/ - - g_assert_cmpuint(mu_util_supports(MU_FEATURE_GUILE), ==, has_guile); - - path = g_find_program_in_path("gnuplot"); - g_free(path); - - g_assert_cmpuint(mu_util_supports(MU_FEATURE_GNUPLOT), ==, - path ? TRUE : FALSE); - - g_assert_cmpuint( - mu_util_supports(MU_FEATURE_GNUPLOT | MU_FEATURE_GUILE), - ==, - has_guile && path ? TRUE : FALSE); -} - -static void -test_mu_util_program_in_path(void) -{ - g_assert_cmpuint(mu_util_program_in_path("ls"), ==, TRUE); -} - - -static void -test_mu_util_summarize(void) -{ - const char *txt = - "Khiron was fortified and made the seat of a pargana during " - "the reign of Asaf-ud-Daula.\n\the headquarters had previously " - "been at Satanpur since its foundation and fortification by " - "the Bais raja Sathna.\n\nKhiron was also historically the seat " - "of a taluqdari estate belonging to a Janwar dynasty.\n" - "There were also several Kayasth qanungo families, " - "including many descended from Rai Sahib Rai, who had been " - "a chakladar under the Nawabs of Awadh."; - - char *summ = mu_str_summarize(txt, 3); - g_assert_cmpstr(summ, ==, - "Khiron was fortified and made the seat of a pargana " - "during the reign of Asaf-ud-Daula. he headquarters had " - "previously been at Satanpur since its foundation and " - "fortification by the Bais raja Sathna. "); - g_free (summ); -} - - -static void -test_mu_error(void) -{ - GQuark q; - GError *err; - gboolean res; - - q = mu_util_error_quark(); - g_assert_true(q != 0); - - - err = NULL; - res = mu_util_g_set_error(&err, MU_ERROR_IN_PARAMETERS, - "Hello, %s!", "World"); - - g_assert_false(res); - g_assert_cmpuint(err->domain, ==, q); - g_assert_cmpuint(err->code, ==, MU_ERROR_IN_PARAMETERS); - g_assert_cmpstr(err->message,==,"Hello, World!"); - - g_clear_error(&err); -} - - -int -main(int argc, char* argv[]) -{ - g_test_init(&argc, &argv, NULL); - - /* mu_util_dir_expand */ - g_test_add_func("/mu-util/mu-util-dir-expand-00", - test_mu_util_dir_expand_00); - g_test_add_func("/mu-util/mu-util-dir-expand-01", - test_mu_util_dir_expand_01); - - /* mu_util_guess_maildir */ - g_test_add_func("/mu-util/mu-util-guess-maildir-01", - test_mu_util_guess_maildir_01); - g_test_add_func("/mu-util/mu-util-guess-maildir-02", - test_mu_util_guess_maildir_02); - - /* mu_util_check_dir */ - g_test_add_func("/mu-util/mu-util-check-dir-01", - test_mu_util_check_dir_01); - g_test_add_func("/mu-util/mu-util-check-dir-02", - test_mu_util_check_dir_02); - g_test_add_func("/mu-util/mu-util-check-dir-03", - test_mu_util_check_dir_03); - g_test_add_func("/mu-util/mu-util-check-dir-04", - test_mu_util_check_dir_04); - - g_test_add_func("/mu-util/mu-util-get-dtype-with-lstat", - test_mu_util_get_dtype_with_lstat); - - g_test_add_func("/mu-util/mu-util-supports", test_mu_util_supports); - g_test_add_func("/mu-util/mu-util-program-in-path", - test_mu_util_program_in_path); - - g_test_add_func("/mu-util/summarize", test_mu_util_summarize); - g_test_add_func("/mu-util/error", test_mu_error); - - return g_test_run(); -} diff --git a/lib/utils/tests/test-utils.cc b/lib/utils/tests/test-utils.cc index a135a9a3..56b3d4de 100644 --- a/lib/utils/tests/test-utils.cc +++ b/lib/utils/tests/test-utils.cc @@ -282,19 +282,6 @@ test_locale_workaround() g_assert_true(locale_workaround()); } -static void -test_error() -{ - GError *err; - err = g_error_new(MU_ERROR_DOMAIN, 77, "Hello, %s", "world"); - Error ex{Error::Code::Crypto, &err, "boo"}; - g_assert_cmpstr(ex.what(), ==, "boo: Hello, world"); - - ex.fill_g_error(&err); - g_assert_cmpuint(err->code, ==, static_cast(Error::Code::Crypto)); - g_clear_error(&err); -} - enum struct TestEnum { A, B, C }; constexpr AssocPairs test_epairs = {{ @@ -323,6 +310,31 @@ test_enum_pairs(void) g_assert_true(to_type("c").value() == TestEnum::C); } + + +static void +test_summarize(void) +{ + const char *txt = + "Khiron was fortified and made the seat of a pargana during " + "the reign of Asaf-ud-Daula.\n\the headquarters had previously " + "been at Satanpur since its foundation and fortification by " + "the Bais raja Sathna.\n\nKhiron was also historically the seat " + "of a taluqdari estate belonging to a Janwar dynasty.\n" + "There were also several Kayasth qanungo families, " + "including many descended from Rai Sahib Rai, who had been " + "a chakladar under the Nawabs of Awadh."; + + const auto summ = summarize(txt, 3); + g_assert_cmpstr(summ.c_str(), ==, + "Khiron was fortified and made the seat of a pargana " + "during the reign of Asaf-ud-Daula. he headquarters had " + "previously been at Satanpur since its foundation and " + "fortification by the Bais raja Sathna. "); +} + + + int main(int argc, char* argv[]) { @@ -335,12 +347,12 @@ main(int argc, char* argv[]) g_test_add_func("/utils/remove-ctrl", test_remove_ctrl); g_test_add_func("/utils/clean", test_clean); g_test_add_func("/utils/format", test_format); + g_test_add_func("/utils/summarize", test_summarize); g_test_add_func("/utils/split", test_split); g_test_add_func("/utils/join", test_join); g_test_add_func("/utils/define-bitmap", test_define_bitmap); g_test_add_func("/utils/to-from-lexnum", test_to_from_lexnum); g_test_add_func("/utils/locale-workaround", test_locale_workaround); - g_test_add_func("/utils/error", test_error); g_test_add_func("/utils/enum-pairs", test_enum_pairs); return g_test_run(); diff --git a/mu/mu-cmd-cfind.cc b/mu/mu-cmd-cfind.cc index aa8dead6..d75fc5a1 100644 --- a/mu/mu-cmd-cfind.cc +++ b/mu/mu-cmd-cfind.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2022 Dirk-Jan C. Binnema +** Copyright (C) 2022-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 @@ -27,7 +27,6 @@ #include #include -#include #include using namespace Mu; @@ -113,14 +112,14 @@ output_plain(ItemType itype, OptContact contact, const Options& opts) const auto col2{opts.nocolor ? "" : MU_COLOR_GREEN}; const auto coldef{opts.nocolor ? "" : MU_COLOR_DEFAULT}; - mu_util_print_encoded("%s%s%s%s%s%s%s\n", - col1, - contact->name.c_str(), - coldef, - contact->name.empty() ? "" : " ", - col2, - contact->email.c_str(), - coldef); + print_encoded("%s%s%s%s%s%s%s\n", + col1, + contact->name.c_str(), + coldef, + contact->name.empty() ? "" : " ", + col2, + contact->email.c_str(), + coldef); } static void @@ -130,8 +129,8 @@ output_mutt_alias(ItemType itype, OptContact contact, const Options& opts) return; const auto nick{guess_nick(*contact)}; - mu_util_print_encoded("alias %s %s <%s>\n", nick.c_str(), - contact->name.c_str(), contact->email.c_str()); + print_encoded("alias %s %s <%s>\n", nick.c_str(), + contact->name.c_str(), contact->email.c_str()); } static void @@ -143,9 +142,9 @@ output_mutt_address_book(ItemType itype, OptContact contact, const Options& opts if (!contact) return; - mu_util_print_encoded("%s\t%s\t\n", - contact->email.c_str(), - contact->name.c_str()); + print_encoded("%s\t%s\t\n", + contact->email.c_str(), + contact->name.c_str()); } static void @@ -156,10 +155,10 @@ output_wanderlust(ItemType itype, OptContact contact, const Options& opts) auto nick=guess_nick(*contact); - mu_util_print_encoded("%s \"%s\" \"%s\"\n", - contact->email.c_str(), - nick.c_str(), - contact->name.c_str()); + print_encoded("%s \"%s\" \"%s\"\n", + contact->email.c_str(), + nick.c_str(), + contact->name.c_str()); } static void @@ -168,9 +167,9 @@ output_org_contact(ItemType itype, OptContact contact, const Options& opts) if (!contact || contact->name.empty()) return; - mu_util_print_encoded("* %s\n:PROPERTIES:\n:EMAIL: %s\n:END:\n\n", - contact->name.c_str(), - contact->email.c_str()); + print_encoded("* %s\n:PROPERTIES:\n:EMAIL: %s\n:END:\n\n", + contact->name.c_str(), + contact->email.c_str()); } static void @@ -201,9 +200,9 @@ output_csv(ItemType itype, OptContact contact, const Options& opts) if (!contact) return; - mu_util_print_encoded("%s,%s\n", - contact->name.empty() ? "" : Mu::quote(contact->name).c_str(), - Mu::quote(contact->email).c_str()); + print_encoded("%s,%s\n", + contact->name.empty() ? "" : Mu::quote(contact->name).c_str(), + Mu::quote(contact->email).c_str()); } static void @@ -216,7 +215,7 @@ output_json(ItemType itype, OptContact contact, const Options& opts) g_print (" {\n"); const std::string name = contact->name.empty() ? "null" : Mu::quote(contact->name); - mu_util_print_encoded( + print_encoded( " \"email\" : \"%s\",\n" " \"name\" : %s,\n" " \"display\" : %s,\n" diff --git a/mu/mu-cmd-extract.cc b/mu/mu-cmd-extract.cc index 1318f602..80d47192 100644 --- a/mu/mu-cmd-extract.cc +++ b/mu/mu-cmd-extract.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2010-2022 Dirk-Jan C. Binnema +** Copyright (C) 2010-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 @@ -19,8 +19,8 @@ #include "config.h" #include "mu-cmd.hh" -#include "utils/mu-util.h" #include "utils/mu-utils.hh" +#include "utils/mu-utils-file.hh" #include "utils/mu-regex.hh" #include @@ -38,16 +38,10 @@ save_part(const Message::Part& part, size_t idx, const Options& opts) if (auto&& res{part.to_file(path, opts.extract.overwrite)}; !res) return Err(res.error()); - - if (opts.extract.play) { - GError *err{}; - if (auto res{mu_util_play(path.c_str(), &err)}; - res != MU_OK) - return Err(Error::Code::Play, &err, "playing '%s' failed", - path.c_str()); - } - - return Ok(); + else if (opts.extract.play) + return play(path); + else + return Ok(); } static Result @@ -113,19 +107,18 @@ show_part(const MessagePart& part, size_t index, bool color) /* filename */ color_maybe(MU_COLOR_GREEN); const auto fname{part.raw_filename()}; - mu_util_fputs_encoded(fname ? fname->c_str() : "", stdout); - - mu_util_fputs_encoded(" ", stdout); + fputs_encoded(fname.value_or(""), stdout); + fputs_encoded(" ", stdout); /* content-type */ color_maybe(MU_COLOR_BLUE); const auto ctype{part.mime_type()}; - mu_util_fputs_encoded(ctype ? ctype->c_str() : "", stdout); + fputs_encoded(ctype.value_or(""), stdout); /* /\* disposition *\/ */ color_maybe(MU_COLOR_MAGENTA); - mu_util_print_encoded(" [%s]", part.is_attachment() ? - "attachment" : "inline"); + print_encoded(" [%s]", part.is_attachment() ? + "attachment" : "inline"); /* size */ if (part.size() > 0) { color_maybe(MU_COLOR_CYAN); @@ -159,7 +152,7 @@ Mu::mu_cmd_extract(const Options& opts) opts.extract.filename_rx.empty()) return show_parts(opts.extract.message, opts); /* show, don't save */ - if (!mu_util_check_dir(opts.extract.targetdir.c_str(), FALSE, TRUE)) + if (!check_dir(opts.extract.targetdir, false/*!readable*/, true/*writeable*/)) return Err(Error::Code::File, "target '%s' is not a writable directory", opts.extract.targetdir.c_str()); diff --git a/mu/mu-cmd-find.cc b/mu/mu-cmd-find.cc index 1534ec16..cbab1371 100644 --- a/mu/mu-cmd-find.cc +++ b/mu/mu-cmd-find.cc @@ -36,7 +36,6 @@ #include "message/mu-message.hh" #include "utils/mu-option.hh" -#include "utils/mu-util.h" #include "mu-cmd.hh" #include "utils/mu-utils.hh" @@ -258,12 +257,10 @@ print_summary(const Message& msg, const Options& opts) if (!body) return; - const auto summ{to_string_opt_gchar( - mu_str_summarize(body->c_str(), - opts.find.summary_len.value_or(0)))}; + const auto summ{summarize(body->c_str(), opts.find.summary_len.value_or(0))}; g_print("Summary: "); - mu_util_fputs_encoded(summ ? summ->c_str() : "", stdout); + fputs_encoded(summ, stdout); g_print("\n"); } @@ -311,8 +308,8 @@ output_plain_fields(const Message& msg, const std::string& fields, else { ansi_color_maybe(field_opt->id, color); - nonempty += mu_util_fputs_encoded( - display_field(msg, field_opt->id).c_str(), stdout); + nonempty += fputs_encoded( + display_field(msg, field_opt->id), stdout); ansi_reset_maybe(field_opt->id, color); } } diff --git a/mu/mu-cmd-index.cc b/mu/mu-cmd-index.cc index 269dc725..93954eec 100644 --- a/mu/mu-cmd-index.cc +++ b/mu/mu-cmd-index.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2008-2022 Dirk-Jan C. Binnema +** Copyright (C) 2008-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 @@ -33,8 +33,6 @@ #include "index/mu-indexer.hh" #include "mu-store.hh" -#include "utils/mu-util.h" - using namespace Mu; static std::atomic caught_signal; diff --git a/mu/mu-cmd.cc b/mu/mu-cmd.cc index 304bfca5..5042239a 100644 --- a/mu/mu-cmd.cc +++ b/mu/mu-cmd.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2010-2022 Dirk-Jan C. Binnema +** Copyright (C) 2010-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 @@ -35,8 +35,6 @@ #include "message/mu-message.hh" #include "message/mu-mime-object.hh" -#include "utils/mu-util.h" - #include "utils/mu-error.hh" #include "utils/mu-utils.hh" #include "message/mu-message.hh" @@ -86,12 +84,12 @@ print_field(const std::string& field, const std::string& val, bool color) return; color_maybe(MU_COLOR_MAGENTA); - mu_util_fputs_encoded(field.c_str(), stdout); + fputs_encoded(field, stdout); color_maybe(MU_COLOR_DEFAULT); fputs(": ", stdout); color_maybe(MU_COLOR_GREEN); - mu_util_fputs_encoded(val.c_str(), stdout); + fputs_encoded(val, stdout); color_maybe(MU_COLOR_DEFAULT); fputs("\n", stdout); @@ -120,12 +118,10 @@ body_or_summary(const Message& message, const Options& opts) } if (opts.view.summary_len) { - gchar* summ; - summ = mu_str_summarize(body->c_str(), *opts.view.summary_len); + const auto summ{summarize(body->c_str(), *opts.view.summary_len)}; print_field("Summary", summ, color); - g_free(summ); } else { - mu_util_print_encoded("%s", body->c_str()); + print_encoded("%s", body->c_str()); if (!g_str_has_suffix(body->c_str(), "\n")) g_print("\n"); } diff --git a/mu/mu-options.hh b/mu/mu-options.hh index fc0eb69c..04eff1f0 100644 --- a/mu/mu-options.hh +++ b/mu/mu-options.hh @@ -1,5 +1,5 @@ /* -** Copyright (C) 2022 Dirk-Jan C. Binnema +** Copyright (C) 2022-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 @@ -26,6 +26,8 @@ #include #include #include +#include + #include #include #include