diff --git a/lib/Makefile.am b/lib/Makefile.am index 7224217f..888029c6 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -92,6 +92,7 @@ libmu_la_SOURCES= \ libmu_la_LIBADD= \ $(XAPIAN_LIBS) \ $(GMIME_LIBS) \ - $(GLIB_LIBS) + $(GLIB_LIBS) \ + $(GUILE_LIBS) EXTRA_DIST=mu-msg-crypto.c diff --git a/lib/mu-str.c b/lib/mu-str.c index 65dd1f69..48422d16 100644 --- a/lib/mu-str.c +++ b/lib/mu-str.c @@ -717,3 +717,32 @@ leave: return nick; } + + + + +gchar* +mu_str_quoted_from_strv (const gchar **params) +{ + GString *str; + int i; + + g_return_val_if_fail (params, NULL); + + if (!params[0]) + return g_strdup (""); + + str = g_string_sized_new (64); /* just a guess */ + + for (i = 0; params[i]; ++i) { + + if (i > 0) + g_string_append_c (str, ' '); + + g_string_append_c (str, '"'); + g_string_append (str, params[i]); + g_string_append_c (str, '"'); + } + + return g_string_free (str, FALSE); +} diff --git a/lib/mu-str.h b/lib/mu-str.h index 98c4547b..69d92916 100644 --- a/lib/mu-str.h +++ b/lib/mu-str.h @@ -359,6 +359,17 @@ gchar* mu_str_guess_first_name (const char* name) gchar* mu_str_guess_last_name (const char* name) G_GNUC_WARN_UNUSED_RESULT; + +/** + * take a list of strings, and turn them in into their quoted + * concatenation + * + * @param params NULL-terminated array of strings + * + * @return the quoted concatenation of the strings + */ +gchar* mu_str_quoted_from_strv (const gchar **params); + G_END_DECLS #endif /*__MU_STR_H__*/ diff --git a/mu/Makefile.am b/mu/Makefile.am index f64c843f..cfb65de9 100644 --- a/mu/Makefile.am +++ b/mu/Makefile.am @@ -20,13 +20,14 @@ include $(top_srcdir)/gtest.mk # before decending into tests/ SUBDIRS= . tests -INCLUDES=-I${top_srcdir}/lib $(GLIB_CFLAGS) +INCLUDES=-I${top_srcdir}/lib $(GLIB_CFLAGS) $(GUILE_CFLAGS) # don't use -Werror, as it might break on other compilers # use -Wno-unused-parameters, because some callbacks may not # really need all the params they get AM_CFLAGS=-Wall -Wextra -Wno-unused-parameter -Wdeclaration-after-statement \ - -pedantic -Wno-variadic-macros + -pedantic -Wno-variadic-macros \ + -DMU_STATSDIR="\"$(pkgdatadir)/scripts/stats\"" AM_CXXFLAGS=-Wall -Wextra -Wno-unused-parameter bin_PROGRAMS= \ diff --git a/mu/mu-cmd-find.c b/mu/mu-cmd-find.c index b9543231..c768d8a0 100644 --- a/mu/mu-cmd-find.c +++ b/mu/mu-cmd-find.c @@ -173,35 +173,6 @@ resolve_bookmark (MuConfig *opts, GError **err) } - -gchar* -str_quoted_from_strv (const gchar **params) -{ - GString *str; - int i; - - g_return_val_if_fail (params, NULL); - - if (!params[0]) - return g_strdup (""); - - str = g_string_sized_new (64); /* just a guess */ - - for (i = 0; params[i]; ++i) { - - if (i > 0) - g_string_append_c (str, ' '); - - g_string_append_c (str, '"'); - g_string_append (str, params[i]); - g_string_append_c (str, '"'); - } - - return g_string_free (str, FALSE); -} - - - static gchar* get_query (MuConfig *opts, GError **err) { @@ -221,7 +192,7 @@ get_query (MuConfig *opts, GError **err) return NULL; } - query = str_quoted_from_strv ((const gchar**)&opts->params[1]); + query = mu_str_quoted_from_strv ((const gchar**)&opts->params[1]); if (bookmarkval) { gchar *tmp; tmp = g_strdup_printf ("%s %s", bookmarkval, query); diff --git a/mu/mu-cmd-guile.c b/mu/mu-cmd-guile.c index 0b0435cd..352d443f 100644 --- a/mu/mu-cmd-guile.c +++ b/mu/mu-cmd-guile.c @@ -23,11 +23,227 @@ #include "config.h" #endif /*HAVE_CONFIG_H*/ +#ifdef BUILD_GUILE +#include +#endif /*BUILD_GUILE*/ + #include #include +#include +#include +#include #include "mu-cmd.h" #include "mu-util.h" +#include "mu-str.h" + +struct _NamePath { + char *name; /* name of the script (ie., basename with ext)*/ + char *path; /* full path of script */ +}; +typedef struct _NamePath NamePath; + +static NamePath* +namepath_new (char *name, const char *path) +{ + NamePath *np; + + np = g_new0 (NamePath, 1); + + np->name = g_strdup (name); + np->path = g_strdup (path); + + return np; +} + +static void +namepath_destroy (NamePath *np) +{ + if (!np) + return; + + g_free (np->name); + g_free (np->path); + g_free (np); +} + +static int +namepath_cmp (NamePath *np1, NamePath *np2) +{ + return strcmp (np1->name, np2->name); +} + +static void +script_namepaths_destroy (GSList *namepaths) +{ + g_slist_foreach (namepaths, (GFunc)namepath_destroy, NULL); + g_slist_free (namepaths); +} + + +static GSList* +get_script_namepaths (const char *path, GError **err) +{ + DIR *dir; + GSList *lst; + struct dirent *dentry; + + dir = opendir (path); + if (!dir) { + mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_OPEN, + "failed to open '%s': %s", + path, strerror(errno)); + return NULL; + } + + /* create a list of names, paths */ + lst = NULL; + while ((dentry = readdir (dir))) { + const char* scmext = ".scm"; + char *name, *fullpath; + + /* only consider scm files */ + if (!g_str_has_suffix (dentry->d_name, scmext)) + continue; + + name = g_strndup (dentry->d_name, + strlen(dentry->d_name) - strlen(scmext)); + fullpath = g_strdup_printf ("%s%c%s", path, G_DIR_SEPARATOR, + dentry->d_name); + + lst = g_slist_prepend (lst, namepath_new (name, fullpath)); + + g_free (name); + g_free (fullpath); + } + + closedir (dir); + + return g_slist_sort (lst, (GCompareFunc)namepath_cmp); +} + + +static char* +find_script_path (const char *script, GError **err) +{ + GSList *scripts, *cur; + char *path; + + scripts = get_script_namepaths (MU_STATSDIR, err); + if (err && *err) + return NULL; + + for (cur = scripts, path = NULL; cur ; cur = g_slist_next (cur)) + if (g_strcmp0(((NamePath*)cur->data)->name, script) == 0) { + path = g_strdup (((NamePath*)cur->data)->path); + break; + } + + script_namepaths_destroy (scripts); + + if (!path) + mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, + "statistic '%s' not found", + script); + return path; +} + +#ifdef BUILD_GUILE + +static void +do_it (void *closure, int argc, char **argv) +{ + scm_shell (argc, argv); +} + + +static void +run_guile_script (MuConfig *opts, GError **err) +{ + char *expr, *query, *scriptpath; + char *argv[] = { + "guile", "-l", NULL, "-c", NULL, NULL + }; + + scriptpath = find_script_path (opts->stat, err); + if (!scriptpath) + return; + else + argv[2] = scriptpath; + + if (opts->params[1]) + query = mu_str_quoted_from_strv + ((const gchar**)&opts->params[1]); + else + query = NULL; + + expr = g_strdup_printf ( + "(main '(\"dummy\" \"--muhome=%s\" %s %s))", + opts->muhome, + opts->textonly ? "\"--textonly\"" : "", + query ? query : ""); + + g_free (query); + argv[4] = expr; + scm_boot_guile (5, argv, do_it, 0); + + /* never reached but let's be correct(TM)*/ + g_free (expr); + g_free (scriptpath); +} + +#else +static void +run_guile_script (MuConfig *opts, GError **err) +{ + g_return_if_reached (); +} +#endif /*!BUILD_GUILE*/ + + +static MuError +list_stats (GError **err) +{ + GSList *scripts; + + scripts = get_script_namepaths (MU_STATSDIR, err); + if (err && *err) + return MU_ERROR; + + if (!scripts) + g_print ("No statistics available\n"); + else { + GSList *cur; + g_print ("Available statistics:\n"); + for (cur = scripts; cur; cur = g_slist_next (cur)) + g_print ("\t%s\n", ((NamePath*)cur->data)->name); + } + + script_namepaths_destroy (scripts); + + return MU_OK; + +} + + +static gboolean +check_params (MuConfig *opts, GError **err) +{ + if (!mu_util_supports (MU_FEATURE_GUILE | MU_FEATURE_GNUPLOT)) { + mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, + "the 'stats' command is not supported"); + return FALSE; + } + + if (!opts->list && !opts->stat) { + mu_util_g_set_error + (err,MU_ERROR_IN_PARAMETERS, + "--stat= or --list is required"); + return FALSE; + } + + return TRUE; +} MuError @@ -37,11 +253,13 @@ mu_cmd_stats (MuConfig *opts, GError **err) g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_STATS, MU_ERROR_INTERNAL); - if (!mu_util_supports (MU_FEATURE_GUILE | MU_FEATURE_GNUPLOT)) { - mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, - "the 'stats' command is not supported"); - return MU_ERROR_IN_PARAMETERS; - } + if (!check_params (opts, err)) + return MU_ERROR; + + if (opts->list) + return list_stats (err); + + run_guile_script (opts, err); return MU_OK; } diff --git a/mu/mu-config.c b/mu/mu-config.c index 04e99a92..e9353274 100644 --- a/mu/mu-config.c +++ b/mu/mu-config.c @@ -290,6 +290,31 @@ config_options_group_cfind (void) } + +static GOptionGroup * +config_options_group_stats (void) +{ + GOptionGroup *og; + GOptionEntry entries[] = { + {"stat", 0, 0, G_OPTION_ARG_STRING, &MU_CONFIG.stat, + "statistic to show (see `mu help stats')", ""}, + {"list", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.list, + "list available statistics", NULL}, + {"textonly", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.textonly, + "use text-only output", NULL}, + {NULL, 0, 0, 0, NULL, NULL, NULL} + }; + + og = g_option_group_new("stats", "Options for the 'stats' command", + "", NULL, NULL); + g_option_group_add_entries(og, entries); + + return og; +} + + + + static void set_group_view_defaults (void) { @@ -491,6 +516,8 @@ get_option_group (MuConfigCmd cmd) return config_options_group_mkdir(); case MU_CONFIG_CMD_SERVER: return config_options_group_server(); + case MU_CONFIG_CMD_STATS: + return config_options_group_stats(); case MU_CONFIG_CMD_VERIFY: return config_options_group_verify(); case MU_CONFIG_CMD_VIEW: diff --git a/mu/mu-config.h b/mu/mu-config.h index 4d514131..56bfc49d 100644 --- a/mu/mu-config.h +++ b/mu/mu-config.h @@ -98,7 +98,7 @@ struct _MuConfig { /* general options */ gboolean quiet; /* don't give any output */ gboolean debug; /* spew out debug info */ - char *muhome; /* the House of Mu */ + gchar *muhome; /* the House of Mu */ gboolean version; /* request mu version */ gboolean log_stderr; /* log to stderr (not logfile) */ gchar** params; /* parameters (for querying) */ @@ -107,7 +107,7 @@ struct _MuConfig { gboolean verbose; /* verbose output */ /* options for indexing */ - char *maildir; /* where the mails are */ + gchar *maildir; /* where the mails are */ gboolean nocleanup; /* don't cleanup del'd mails from db */ gboolean reindex; /* re-index existing mails */ gboolean rebuild; /* empty the database before indexing */ @@ -122,21 +122,21 @@ struct _MuConfig { * times */ /* options for querying 'find' (and view-> 'summary') */ - char *fields; /* fields to show in output */ - char *sortfield; /* field to sort by (string) */ + gchar *fields; /* fields to show in output */ + gchar *sortfield; /* field to sort by (string) */ gboolean reverse; /* sort in revers order (z->a) */ gboolean threads; /* show message threads */ gboolean summary; /* OBSOLETE: use summary_len */ int summary_len; /* max # of lines for summary */ - char *bookmark; /* use bookmark */ - char *formatstr; /* output type for find + gchar *bookmark; /* use bookmark */ + gchar *formatstr; /* output type for find * (plain,links,xml,json,sexp) * and view (plain, sexp) and cfind */ MuConfigFormat format; /* the decoded formatstr */ - char *exec; /* command to execute on the + gchar *exec; /* command to execute on the * files for the matched * messages */ /* for find and cind */ @@ -163,7 +163,7 @@ struct _MuConfig { /* also 'after' --> see above */ /* output to a maildir with symlinks */ - char *linksdir; /* maildir to output symlinks */ + gchar *linksdir; /* maildir to output symlinks */ gboolean clearlinks; /* clear a linksdir before filling */ mode_t dirmode; /* mode for the created maildir */ @@ -172,10 +172,15 @@ struct _MuConfig { gboolean *save_attachments; /* extract all attachment parts */ gchar *parts; /* comma-sep'd list of parts * to save / open */ - char *targetdir; /* where to save the attachments */ + gchar *targetdir; /* where to save the attachments */ gboolean overwrite; /* should we overwrite same-named files */ gboolean play; /* after saving, try to 'play' * (open) the attmnt using xdgopen */ + /* options for mu-stats */ + gboolean textonly; /* no non-textual graphs */ + gchar *stat; /* statistic to show */ + gboolean list; /* list available stats */ + }; typedef struct _MuConfig MuConfig; diff --git a/mu/mu-help-strings.txt b/mu/mu-help-strings.txt index 1fd4f94d..e4fb8489 100644 --- a/mu/mu-help-strings.txt +++ b/mu/mu-help-strings.txt @@ -96,6 +96,16 @@ mu4e e-mail client. #END +#BEGIN MU_CONFIG_CMD_STATS +#STRING +mu stats [options] --list|--stats= [] +#STRING +Run some statistics on the mu database. Option '--stats=' selects the statistic to +show, optionally limited to the messages matching . +Option '--list' lists the available statistics +#END + + #BEGIN MU_CONFIG_CMD_VERIFY #STRING mu verify [options]