diff --git a/Makefile.am b/Makefile.am index 7ea8e8a1..f93a8905 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,7 +23,14 @@ else widgets= endif -SUBDIRS=m4 man src $(widgets) contrib toys +if HAVE_GUILE +guile=libmuguile +else +guile= +endif + +SUBDIRS=m4 man src $(widgets) $(guile) contrib toys + ACLOCAL_AMFLAGS=-I m4 diff --git a/NEWS b/NEWS index d1d4bfa6..ad91ae47 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,16 @@ * NEWS (user visible changes) + + * Release 0.9.7 <> + + - enable threading (using -t/--threads) with mu find + - don't enforce UTF-8 output, use locale (fixes issue #11) + - add mail threading (sorta fixes issue #13) + - add header line to --format=mutt-ab (mu cfind), (fixes issue #42) + - terminate mu view results with a form-feed marker (use --terminate) (fixes + issue #41) + - search X-Label: tags (fixes issue #40) + - added toys/muile, the mu guile shells, which allows for message stats etc. + ** Release 0.9.6 <2011-05-28 Sat> diff --git a/TODO b/TODO index c6ad400d..1473c883 100644 --- a/TODO +++ b/TODO @@ -1,24 +1,35 @@ * Future release - - [ ] mu stats + - [ ] config system (config file) (?) - [ ] don't make test mail files executable - - [ ] add version info to output of configure, mu.log - [ ] make 'make check' more useful for others - [ ] completion for zsh - [ ] follow symlinks when indexing - [ ] better 'usage' info - [ ] generalize mu_str_normalize for all unicode - - [ ] mail threads - [ ] check wmy (date) for begin-end - - + - [ ] tagging rw + +** release 0.9.7 [100%] + + - [X] mu stats + - [X] tagging (ro, based on X-Label) + - [X] mail threads + - [X] guile interface + - [X] add version info to output of configure, mu.log + - [X] use system locale in output + * Releases already done (see NEWS for features) ** release 0.9.6 [100%] - + + (see NEWS) + ** release 0.9.5 [100%] + (see NEWS) + ** release 0.9.4 [100%] - [X] add 'mu cfind' to find contacts @@ -37,7 +48,7 @@ - [X] xml,json,sexp output (experimental) - [X] check all strings are really UTF8 - [X] add drag & drop support for attachments - - [X] add drop & drop support for body images + - [X] add drag & drop support for body images - [X] fix size value - [X] fix matching strange folder names - [X] separate exit code for 'not found' vs other errors diff --git a/configure.ac b/configure.ac index 73800528..7a519753 100644 --- a/configure.ac +++ b/configure.ac @@ -208,6 +208,27 @@ AM_CONDITIONAL(HAVE_GIO, [test "x$have_gio" = "xyes"]) # should we build the widgets/ dir? AM_CONDITIONAL(BUILD_WIDGETS, [test "x$have_webkit" = "xyes" -a "x$have_gio" = "xyes"]) + + + +# check for guile & guile-snarf +AC_PATH_PROG(GUILE, [guile-config], [], [$PATH]) +AS_IF([test "x$GUILE" != "x"], + [GUILE_CFLAGS=`$GUILE compile`; GUILE_LIBS=`$GUILE link`]) +AC_SUBST(GUILE_LIBS) +AC_SUBST(GUILE_CFLAGS) + +AC_PATH_PROG(GUILE_SNARF, [guile-snarf], [], [$PATH]) +AS_IF([test "x$GUILE_SNARF" != "x"],[ + AC_DEFINE_UNQUOTED([GUILE_SNARF], ["$GUILE_SNARF"],[Path to guile-snarf])],[ + AC_MSG_WARN([cannot find guile-snarf])]) + +AM_CONDITIONAL(HAVE_GUILE,[test "$xGUILE" != "x" -a "x$GUILE_SNARF != "x]) + + + + + # check for xdg-open AS_IF([test "x$buildgui"="xyes"],[ AC_PATH_PROG(XDGOPEN, [xdg-open], [], [$PATH]) @@ -232,9 +253,11 @@ Makefile src/Makefile src/tests/Makefile widgets/Makefile +libmuguile/Makefile toys/Makefile toys/mug/Makefile toys/mug2/Makefile +toys/muile/Makefile man/Makefile m4/Makefile contrib/Makefile @@ -268,6 +291,10 @@ if test "x$have_webkit" = "xyes"; then echo "Webkit version : $webkit_version" fi +if test "x$GUILE" != "x"; then +echo "Guile version : `$GUILE --version 2>&1`" +fi + echo echo "Build unit tests (glib >= 2.22) : $have_gtest" echo "Build 'mug' toy-ui (requires GTK+) : $buildgui" diff --git a/libmuguile/Makefile.am b/libmuguile/Makefile.am new file mode 100644 index 00000000..80612881 --- /dev/null +++ b/libmuguile/Makefile.am @@ -0,0 +1,61 @@ +## Copyright (C) 2011 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 +## t he 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 $(top_srcdir)/gtest.mk + +# enforce compiling this dir first before decending into tests/ +SUBDIRS= . +INCLUDES=-I${top_srcdir}/src ${GUILE_CFLAGS} ${GLIB_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 +AM_CXXFLAGS=-Wall -Wextra -Wno-unused-parameter + + +noinst_LTLIBRARIES= \ + libmuguile.la + +libmuguile_la_SOURCES= \ + mu-guile-msg.c \ + mu-guile-msg.h \ + mu-guile-store.c \ + mu-guile-store.h \ + mu-guile-common.c \ + mu-guile-common.h + +libmuguile_la_LIBADD= \ + ${top_builddir}/src/libmu.la \ + ${GUILE_LIBS} + +XFILES= \ + mu-guile-msg.x \ + mu-guile-store.x + +BUILT_SOURCES=$(XFILES) $(DOCFILES) + + +snarfcppopts= $(DEFS) $(AM_CPPFLAGS) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) +SUFFIXES = .x +.c.x: + $(GUILE_SNARF) -o $@ $< $(snarfcppopts) + +## Add -MG to make the .x magic work with auto-dep code. +MKDEP = $(CC) -M -MG $(snarfcppopts) + + +DISTCLEANFILES=$(XFILES) diff --git a/libmuguile/mu-guile-common.c b/libmuguile/mu-guile-common.c new file mode 100644 index 00000000..1f3a658c --- /dev/null +++ b/libmuguile/mu-guile-common.c @@ -0,0 +1,41 @@ +/* +** Copyright (C) 2011 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include "mu-guile-common.h" + +void +mu_guile_error (const char *func_name, int status, + const char *fmt, SCM args) +{ + scm_error_scm (scm_from_locale_symbol ("MuError"), + scm_from_utf8_string (func_name ? func_name : ""), + scm_from_utf8_string (fmt), args, + scm_list_1 (scm_from_int (status))); +} + + + +void +mu_guile_g_error (const char *func_name, GError *err) +{ + scm_error_scm (scm_from_locale_symbol ("MuError"), + scm_from_utf8_string (func_name), + scm_from_utf8_string (err->message), + SCM_UNDEFINED, SCM_UNDEFINED); +} diff --git a/libmuguile/mu-guile-common.h b/libmuguile/mu-guile-common.h new file mode 100644 index 00000000..c14ba084 --- /dev/null +++ b/libmuguile/mu-guile-common.h @@ -0,0 +1,51 @@ +/* +** Copyright (C) 2011 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_GUILE_UTILS_H__ +#define __MU_GUILE_UTILS_H__ + +#include +#include + +G_BEGIN_DECLS + +/** + * + * + * @param func_name + * @param status + * @param fmt + * @param args + */ +void mu_guile_error (const char *func_name, int status, + const char *fmt, SCM args); + + +/** + * display a GError as a Guile error + * + * @param func_name function name + * @param err Gerror + */ +void mu_guile_g_error (const char *func_name, GError *err); + +G_END_DECLS + +#endif /*__MU_GUILE_UTILS_H__*/ + diff --git a/libmuguile/mu-guile-msg.c b/libmuguile/mu-guile-msg.c new file mode 100644 index 00000000..e45e0c8f --- /dev/null +++ b/libmuguile/mu-guile-msg.c @@ -0,0 +1,512 @@ +/* +** Copyright (C) 2011 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include +#include +#include + +#include "mu-guile-msg.h" +#include "mu-guile-common.h" + +struct _MuMsgWrapper { + MuMsg *_msg; + gboolean _unrefme; +}; +typedef struct _MuMsgWrapper MuMsgWrapper; + +static long MSG_TAG; + +static int +mu_guile_scm_is_msg (SCM scm) +{ + return SCM_NIMP(scm) && (long) SCM_CAR (scm) == MSG_TAG; +} + + +SCM +mu_guile_msg_to_scm (MuMsg *msg) +{ + MuMsgWrapper *msgwrap; + + g_return_val_if_fail (msg, SCM_UNDEFINED); + + msgwrap = scm_gc_malloc (sizeof (MuMsgWrapper), "msg"); + msgwrap->_msg = msg; + msgwrap->_unrefme = FALSE; + + SCM_RETURN_NEWSMOB (MSG_TAG, msgwrap); +} + +SCM_DEFINE (msg_make_from_file, "mu:msg:make-from-file", 1, 0, 0, + (SCM PATH), + "Create a message object based on the message in PATH.\n") +#define FUNC_NAME s_msg_make_from_file +{ + MuMsg *msg; + GError *err; + + SCM_ASSERT (scm_is_string (PATH), PATH, SCM_ARG1, FUNC_NAME); + + err = NULL; + msg = mu_msg_new_from_file (scm_to_utf8_string (PATH), NULL, &err); + + if (err) { + mu_guile_g_error (FUNC_NAME, err); + g_error_free (err); + } + + return msg ? mu_guile_msg_to_scm (msg) : SCM_UNDEFINED; +} +#undef FUNC_NAME + + +static SCM +msg_str_field (SCM msg_smob, MuMsgFieldId mfid) +{ + const char *val, *endptr; + + MuMsgWrapper *msgwrap; + msgwrap = (MuMsgWrapper*) SCM_CDR(msg_smob); + + val = mu_msg_get_field_string(msgwrap->_msg, mfid); + + if (val && !g_utf8_validate (val, -1, &endptr)) { + //return scm_from_utf8_string(""); + gchar *part; + SCM scm; + part = g_strndup (val, (endptr-val)); + scm = scm_from_utf8_string(part); + g_free (part); + return scm; + } else + return val ? scm_from_utf8_string(val) : SCM_UNSPECIFIED; +} + +static gint64 +msg_num_field (SCM msg_smob, MuMsgFieldId mfid) +{ + MuMsgWrapper *msgwrap; + msgwrap = (MuMsgWrapper*) SCM_CDR(msg_smob); + + return mu_msg_get_field_numeric(msgwrap->_msg, mfid); +} + + +SCM_DEFINE (msg_date, "mu:msg:date", 1, 0, 0, + (SCM MSG), + "Get the date (time in seconds since epoch) for MSG.\n") +#define FUNC_NAME s_msg_date +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return scm_from_unsigned_integer + (msg_num_field (MSG, MU_MSG_FIELD_ID_DATE)); +} +#undef FUNC_NAME + + + +SCM_DEFINE (msg_size, "mu:msg:size", 1, 0, 0, + (SCM MSG), + "Get the size in bytes for MSG.\n") +#define FUNC_NAME s_msg_size +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return scm_from_unsigned_integer + (msg_num_field (MSG, MU_MSG_FIELD_ID_SIZE)); +} +#undef FUNC_NAME + + + +SCM_DEFINE (msg_prio, "mu:msg:priority", 1, 0, 0, + (SCM MSG), + "Get the priority of MSG (low, normal or high).\n") +#define FUNC_NAME s_msg_prio +{ + MuMsgPrio prio; + MuMsgWrapper *msgwrap; + + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + msgwrap = (MuMsgWrapper*) SCM_CDR(MSG); + + prio = mu_msg_get_prio (msgwrap->_msg); + + switch (prio) { + case MU_MSG_PRIO_LOW: return scm_from_utf8_symbol("low"); + case MU_MSG_PRIO_NORMAL: return scm_from_utf8_symbol("normal"); + case MU_MSG_PRIO_HIGH: return scm_from_utf8_symbol("high"); + default: + g_return_val_if_reached (SCM_UNDEFINED); + } +} +#undef FUNC_NAME + +struct _FlagData { + MuMsgFlags flags; + SCM lst; +}; +typedef struct _FlagData FlagData; + + +static void +check_flag (MuMsgFlags flag, FlagData *fdata) +{ + if (fdata->flags & flag) { + SCM item; + item = scm_list_1 (scm_from_utf8_symbol(mu_msg_flag_name(flag))); + fdata->lst = scm_append_x (scm_list_2(fdata->lst, item)); + } +} + + +SCM_DEFINE (msg_flags, "mu:msg:flags", 1, 0, 0, + (SCM MSG), + "Get the flags for MSG (one or or more of new, passed, replied, " + "seen, trashed, draft, flagged, unread, signed, encrypted, " + "has-attach).\n") +#define FUNC_NAME s_msg_flags +{ + MuMsgWrapper *msgwrap; + FlagData fdata; + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + msgwrap = (MuMsgWrapper*) SCM_CDR(MSG); + + fdata.flags = mu_msg_get_flags (msgwrap->_msg); + fdata.lst = SCM_EOL; + mu_msg_flags_foreach ((MuMsgFlagsForeachFunc)check_flag, + &fdata); + + return fdata.lst; +} +#undef FUNC_NAME + + +SCM_DEFINE (msg_subject, "mu:msg:subject", 1, 0, 0, + (SCM MSG), "Get the subject of MSG.\n") +#define FUNC_NAME s_msg_subject +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return msg_str_field (MSG, MU_MSG_FIELD_ID_SUBJECT); +} +#undef FUNC_NAME + + +SCM_DEFINE (msg_from, "mu:msg:from", 1, 0, 0, + (SCM MSG), "Get the sender of MSG.\n") +#define FUNC_NAME s_msg_from +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return msg_str_field (MSG, MU_MSG_FIELD_ID_FROM); +} +#undef FUNC_NAME + +struct _EachContactData { + SCM lst; + MuMsgContactType ctype; +}; +typedef struct _EachContactData EachContactData; + +static void +contacts_to_list (MuMsgContact *contact, EachContactData *ecdata) +{ + if (mu_msg_contact_type (contact) == ecdata->ctype) { + SCM item; + const char *addr, *name; + + addr = mu_msg_contact_address(contact); + name = mu_msg_contact_name(contact); + + item = scm_list_1 + (scm_list_2 ( + name ? scm_from_utf8_string(name) : SCM_UNSPECIFIED, + addr ? scm_from_utf8_string(addr) : SCM_UNSPECIFIED)); + + ecdata->lst = scm_append_x (scm_list_2(ecdata->lst, item)); + } +} + + +static SCM +contact_list_field (SCM msg_smob, MuMsgFieldId mfid) +{ + MuMsgWrapper *msgwrap; + EachContactData ecdata; + + ecdata.lst = SCM_EOL; + + switch (mfid) { + case MU_MSG_FIELD_ID_TO: ecdata.ctype = MU_MSG_CONTACT_TYPE_TO; break; + case MU_MSG_FIELD_ID_CC: ecdata.ctype = MU_MSG_CONTACT_TYPE_CC; break; + case MU_MSG_FIELD_ID_BCC: ecdata.ctype = MU_MSG_CONTACT_TYPE_BCC; break; + default: g_return_val_if_reached (SCM_UNDEFINED); + } + + msgwrap = (MuMsgWrapper*) SCM_CDR(msg_smob); + + mu_msg_contact_foreach (msgwrap->_msg, + (MuMsgContactForeachFunc)contacts_to_list, + &ecdata); + return ecdata.lst; +} + + +SCM_DEFINE (msg_to, "mu:msg:to", 1, 0, 0, + (SCM MSG), "Get the list of To:-recipients of MSG.\n") +#define FUNC_NAME s_msg_to +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return contact_list_field (MSG, MU_MSG_FIELD_ID_TO); +} +#undef FUNC_NAME + + + +SCM_DEFINE (msg_cc, "mu:msg:cc", 1, 0, 0, + (SCM MSG), "Get the list of Cc:-recipients of MSG.\n") +#define FUNC_NAME s_msg_cc +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return contact_list_field (MSG, MU_MSG_FIELD_ID_CC); +} +#undef FUNC_NAME + + +SCM_DEFINE (msg_bcc, "mu:msg:bcc", 1, 0, 0, + (SCM MSG), "Get the list of Bcc:-recipients of MSG.\n") +#define FUNC_NAME s_msg_bcc +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return contact_list_field (MSG, MU_MSG_FIELD_ID_BCC); +} +#undef FUNC_NAME + + +SCM_DEFINE (msg_path, "mu:msg:path", 1, 0, 0, + (SCM MSG), "Get the filesystem path for MSG.\n") +#define FUNC_NAME s_msg_path +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return msg_str_field (MSG, MU_MSG_FIELD_ID_PATH); +} +#undef FUNC_NAME + + +SCM_DEFINE (msg_maildir, "mu:msg:maildir", 1, 0, 0, + (SCM MSG), "Get the maildir where MSG lives.\n") +#define FUNC_NAME s_msg_maildir +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return msg_str_field (MSG, MU_MSG_FIELD_ID_MAILDIR); +} +#undef FUNC_NAME + + + +SCM_DEFINE (msg_msgid, "mu:msg:message-id", 1, 0, 0, + (SCM MSG), "Get the MSG's message-id.\n") +#define FUNC_NAME s_msg_msgid +{ + return msg_str_field (MSG, MU_MSG_FIELD_ID_MSGID); +} +#undef FUNC_NAME + + +SCM_DEFINE (msg_body, "mu:msg:body", 1, 1, 0, + (SCM MSG, SCM HTML), "Get the MSG's body. If HTML is #t, " + "prefer the html-version, otherwise prefer plain text.\n") +#define FUNC_NAME s_msg_body +{ + MuMsgWrapper *msgwrap; + gboolean html; + const char *val; + + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + msgwrap = (MuMsgWrapper*) SCM_CDR(MSG); + html = SCM_UNBNDP(HTML) ? FALSE : HTML == SCM_BOOL_T; + + if (html) + val = mu_msg_get_body_html(msgwrap->_msg); + else + val = mu_msg_get_body_text(msgwrap->_msg); + + return val ? scm_from_utf8_string (val) : SCM_UNSPECIFIED; +} +#undef FUNC_NAME + + +SCM_DEFINE (msg_header, "mu:msg:header", 1, 1, 0, + (SCM MSG, SCM HEADER), "Get an arbitary HEADER from MSG.\n") +#define FUNC_NAME s_msg_header +{ + MuMsgWrapper *msgwrap; + const char *header; + const char *val; + + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + SCM_ASSERT (scm_is_string (HEADER), HEADER, SCM_ARG2, FUNC_NAME); + + msgwrap = (MuMsgWrapper*) SCM_CDR(MSG); + header = scm_to_utf8_string (HEADER); + val = mu_msg_get_header(msgwrap->_msg, header); + + return val ? scm_from_utf8_string(val) : SCM_UNDEFINED; +} +#undef FUNC_NAME + +static SCM +msg_string_list_field (SCM msg_smob, MuMsgFieldId mfid) +{ + MuMsgWrapper *msgwrap; + SCM scmlst; + const GSList *lst; + + msgwrap = (MuMsgWrapper*) SCM_CDR(msg_smob); + lst = mu_msg_get_field_string_list (msgwrap->_msg, mfid); + + for (scmlst = SCM_EOL; lst; + lst = g_slist_next(lst)) { + SCM item; + item = scm_list_1 (scm_from_utf8_string((const char*)lst->data)); + scmlst = scm_append_x (scm_list_2(scmlst, item)); + } + + return scmlst; +} + + +SCM_DEFINE (msg_tags, "mu:msg:tags", 1, 1, 0, + (SCM MSG), "Get the list of tags (contents of the " + "X-Label:-header) for MSG.\n") +#define FUNC_NAME s_msg_tags +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return msg_string_list_field (MSG, MU_MSG_FIELD_ID_TAGS); +} +#undef FUNC_NAME + + + +SCM_DEFINE (msg_refs, "mu:msg:references", 1, 1, 0, + (SCM MSG), "Get the list of referenced message-ids " + "(contents of the References: and Reply-To: headers).\n") +#define FUNC_NAME s_msg_refs +{ + SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); + + return msg_string_list_field (MSG, MU_MSG_FIELD_ID_REFS); +} +#undef FUNC_NAME + + +static SCM +msg_mark (SCM msg_smob) +{ + MuMsgWrapper *msgwrap; + msgwrap = (MuMsgWrapper*) SCM_CDR(msg_smob); + + msgwrap->_unrefme = TRUE; + + return SCM_UNSPECIFIED; +} + +static scm_sizet +msg_free (SCM msg_smob) +{ + MuMsgWrapper *msgwrap; + msgwrap = (MuMsgWrapper*) SCM_CDR(msg_smob); + + if (msgwrap->_unrefme) + mu_msg_unref (msgwrap->_msg); + + return sizeof (MuMsgWrapper); +} + +static int +msg_print (SCM msg_smob, SCM port, scm_print_state * pstate) +{ + MuMsgWrapper *msgwrap; + msgwrap = (MuMsgWrapper*) SCM_CDR(msg_smob); + + scm_puts ("#_msg), + port); + + scm_puts (">", port); + + return 1; +} + + +static void +define_symbols (void) +{ + /* message priority */ + scm_c_define ("high", scm_from_int(MU_MSG_PRIO_HIGH)); + scm_c_define ("low", scm_from_int(MU_MSG_PRIO_LOW)); + scm_c_define ("normal", scm_from_int(MU_MSG_PRIO_NORMAL)); + + /* message flags */ + scm_c_define ("new", scm_from_int(MU_MSG_FLAG_NEW)); + scm_c_define ("passed", scm_from_int(MU_MSG_FLAG_PASSED)); + scm_c_define ("replied", scm_from_int(MU_MSG_FLAG_REPLIED)); + scm_c_define ("seen", scm_from_int(MU_MSG_FLAG_SEEN)); + scm_c_define ("trashed", scm_from_int(MU_MSG_FLAG_TRASHED)); + scm_c_define ("draft", scm_from_int(MU_MSG_FLAG_DRAFT)); + scm_c_define ("flagged", scm_from_int(MU_MSG_FLAG_FLAGGED)); + scm_c_define ("unread", scm_from_int(MU_MSG_FLAG_UNREAD)); + scm_c_define ("signed", scm_from_int(MU_MSG_FLAG_SIGNED)); + scm_c_define ("encrypted", scm_from_int(MU_MSG_FLAG_ENCRYPTED)); + scm_c_define ("has-attach", scm_from_int(MU_MSG_FLAG_HAS_ATTACH)); +} + + +void* +mu_guile_msg_init (void *data) +{ + MSG_TAG = scm_make_smob_type ("msg", sizeof(MuMsgWrapper)); + + scm_set_smob_mark (MSG_TAG, msg_mark); + scm_set_smob_free (MSG_TAG, msg_free); + scm_set_smob_print (MSG_TAG, msg_print); + + define_symbols (); + +#include "mu-guile-msg.x" + + return NULL; +} + + diff --git a/libmuguile/mu-guile-msg.h b/libmuguile/mu-guile-msg.h new file mode 100644 index 00000000..856fa627 --- /dev/null +++ b/libmuguile/mu-guile-msg.h @@ -0,0 +1,52 @@ +/* +** Copyright (C) 2011 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_GUILE_MSG_H__ +#define __MU_GUILE_MSG_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + +/** + * register MuMsg-related functions/smobs with guile; use with + * scm_with_guile + * + */ +void *mu_guile_msg_init (void *data); + + +/** + * create an SCM for the MuMsg* + * + * @param msg a MuMsg instance + * + * @return an SCM for the msg + */ +SCM mu_guile_msg_to_scm (MuMsg *msg); + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + + +#endif /*__MU_GUILE_MSG_H__*/ diff --git a/libmuguile/mu-guile-store.c b/libmuguile/mu-guile-store.c new file mode 100644 index 00000000..77e0f66e --- /dev/null +++ b/libmuguile/mu-guile-store.c @@ -0,0 +1,125 @@ +/* +** Copyright (C) 2011 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include +#include +#include + +#include "mu-guile-msg.h" +#include "mu-guile-store.h" +#include "mu-guile-common.h" + +static MuQuery* +get_query (void) +{ + MuQuery *query; + GError *err; + + err = NULL; + query = mu_query_new (mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), &err); + if (err) { + mu_guile_g_error ("", err); + g_error_free (err); + return NULL; + } + + return query; +} + + +static MuMsgIter* +get_query_iter (MuQuery *query, const char* expr) +{ + MuMsgIter *iter; + GError *err; + + err = NULL; + iter = mu_query_run (query, expr, + FALSE, MU_MSG_FIELD_ID_NONE, TRUE, &err); + if (err) { + mu_guile_g_error ("", err); + g_error_free (err); + return NULL; + } + + return iter; +} + + +SCM_DEFINE (store_foreach, "mu:store:foreach", 1, 1, 0, + (SCM FUNC, SCM EXPR), + "Call FUNC for each message in the store, or, if EXPR is specified, " + "for each message matching EXPR.\n") +#define FUNC_NAME s_store_foreach +{ + MuQuery *query; + MuMsgIter *iter; + int count; + const char* expr; + + SCM_ASSERT (scm_procedure_p (FUNC), FUNC, SCM_ARG1, FUNC_NAME); + SCM_ASSERT (scm_is_string (EXPR) || EXPR == SCM_UNSPECIFIED, + EXPR, SCM_ARG2, FUNC_NAME); + + query = get_query (); + if (!query) + return SCM_UNSPECIFIED; + + expr = SCM_UNBNDP(EXPR) ? NULL : scm_to_utf8_string(EXPR); + + iter = get_query_iter (query, expr); + if (!iter) + return SCM_UNSPECIFIED; + + for (count = 0; !mu_msg_iter_is_done(iter); mu_msg_iter_next (iter)) { + + SCM msgsmob; + MuMsg *msg; + GError *err; + + err = NULL; + msg = mu_msg_iter_get_msg (iter, &err); + if (err) { + mu_guile_g_error (FUNC_NAME, err); + g_error_free (err); + continue; + } + + msgsmob = mu_guile_msg_to_scm (mu_msg_ref(msg)); + scm_call_1 (FUNC, msgsmob); + + ++count; + } + + mu_query_destroy (query); + + return scm_from_int (count); +} +#undef FUNC_NAME + + +void* +mu_guile_store_init (void *data) +{ +#include "mu-guile-store.x" + + return NULL; +} + + diff --git a/libmuguile/mu-guile-store.h b/libmuguile/mu-guile-store.h new file mode 100644 index 00000000..4ce295ea --- /dev/null +++ b/libmuguile/mu-guile-store.h @@ -0,0 +1,39 @@ +/* +** Copyright (C) 2011 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_GUILE_STORE_H__ +#define __MU_GUILE_STORE_H__ + + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + +/** + * initialize mu:store functions + * + */ +void *mu_guile_store_init (void *data); + + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif /*__MU_GUILE_STORE_H__*/ diff --git a/src/mu-msg-file.h b/src/mu-msg-file.h index d04bc8a8..a6f992e5 100644 --- a/src/mu-msg-file.h +++ b/src/mu-msg-file.h @@ -99,7 +99,6 @@ GSList* mu_msg_file_get_str_list_field (MuMsgFile *self, - /** * get a numeric value for this message -- the return value should be * cast into the actual type, e.g., time_t, MuMsgPrio etc. diff --git a/src/mu-msg.c b/src/mu-msg.c index 67e7a66e..073494d3 100644 --- a/src/mu-msg.c +++ b/src/mu-msg.c @@ -521,25 +521,19 @@ fill_contact (MuMsgContact *self, InternetAddress *addr, static void -address_list_foreach (InternetAddressList *addrlist, - MuMsgContactType ctype, - MuMsgContactForeachFunc func, - gpointer user_data) +address_list_foreach (InternetAddressList *addrlist, MuMsgContactType ctype, + MuMsgContactForeachFunc func, gpointer user_data) { int i; - - if (!addrlist) - return; - - for (i = 0; i != internet_address_list_length(addrlist); ++i) { + + for (i = 0; addrlist && i != internet_address_list_length(addrlist); + ++i) { MuMsgContact contact; if (!fill_contact(&contact, internet_address_list_get_address (addrlist, i), - ctype)) { - MU_WRITE_LOG ("ignoring contact"); + ctype)) continue; - } if (!(func)(&contact, user_data)) break; @@ -547,30 +541,25 @@ address_list_foreach (InternetAddressList *addrlist, } - static void -get_contacts_from (MuMsg *msg, MuMsgContactForeachFunc func, - gpointer user_data) +addresses_foreach (const char* addrs, MuMsgContactType ctype, + MuMsgContactForeachFunc func, gpointer user_data) { - InternetAddressList *lst; - - /* we go through this whole excercise of trying to get a *list* - * of 'From:' address (usually there is only one...), because - * internet_address_parse_string has the nice side-effect of - * splitting in names and addresses for us */ - lst = internet_address_list_parse_string ( - g_mime_message_get_sender (msg->_file->_mime_msg)); + InternetAddressList *addrlist; - if (lst) { - address_list_foreach (lst, MU_MSG_CONTACT_TYPE_FROM, - func, user_data); - g_object_unref (G_OBJECT(lst)); - } + if (!addrs) + return; + + addrlist = internet_address_list_parse_string (addrs); + if (addrlist) { + address_list_foreach (addrlist, ctype, func, user_data); + g_object_unref (addrlist); + } } void -mu_msg_contact_foreach (MuMsg *msg, MuMsgContactForeachFunc func, +msg_contact_foreach_file (MuMsg *msg, MuMsgContactForeachFunc func, gpointer user_data) { int i; @@ -583,11 +572,10 @@ mu_msg_contact_foreach (MuMsg *msg, MuMsgContactForeachFunc func, {GMIME_RECIPIENT_TYPE_BCC, MU_MSG_CONTACT_TYPE_BCC}, }; - g_return_if_fail (func && msg); - - /* first, get the from address(es) */ - get_contacts_from (msg, func, user_data); - + /* sender */ + addresses_foreach (g_mime_message_get_sender (msg->_file->_mime_msg), + MU_MSG_CONTACT_TYPE_FROM, func, user_data); + /* get to, cc, bcc */ for (i = 0; i != G_N_ELEMENTS(ctypes); ++i) { InternetAddressList *addrlist; @@ -598,6 +586,38 @@ mu_msg_contact_foreach (MuMsg *msg, MuMsgContactForeachFunc func, } +static void +msg_contact_foreach_doc (MuMsg *msg, MuMsgContactForeachFunc func, + gpointer user_data) +{ + addresses_foreach (mu_msg_get_from (msg), + MU_MSG_CONTACT_TYPE_FROM, func, user_data); + addresses_foreach (mu_msg_get_to (msg), + MU_MSG_CONTACT_TYPE_TO, func, user_data); + addresses_foreach (mu_msg_get_cc (msg), + MU_MSG_CONTACT_TYPE_CC, func, user_data); + addresses_foreach (mu_msg_get_bcc (msg), + MU_MSG_CONTACT_TYPE_BCC, func, user_data); +} + + +void +mu_msg_contact_foreach (MuMsg *msg, MuMsgContactForeachFunc func, + gpointer user_data) +{ + g_return_if_fail (msg); + g_return_if_fail (func); + + if (msg->_doc) + msg_contact_foreach_doc (msg, func, user_data); + else if (msg->_file) + msg_contact_foreach_file (msg, func, user_data); + else + g_return_if_reached (); +} + + + static int cmp_str (const char* s1, const char *s2) { diff --git a/src/mu-msg.h b/src/mu-msg.h index 3ca3490f..62162143 100644 --- a/src/mu-msg.h +++ b/src/mu-msg.h @@ -77,7 +77,7 @@ MuMsg *mu_msg_new_from_doc (XapianDocument* doc, GError **err) * @return the message with its reference count increased, or NULL in * case of error. */ -MuMsg * mu_msg_ref (MuMsg *msg); +MuMsg *mu_msg_ref (MuMsg *msg); /** * decrease the reference count for this message. if the reference @@ -305,7 +305,7 @@ MuMsgPrio mu_msg_get_prio (MuMsg *msg); * * @return the timestamp or 0 in case of error */ -time_t mu_msg_get_timestamp (MuMsg *msg); +time_t mu_msg_get_timestamp (MuMsg *msg); @@ -334,10 +334,6 @@ const char* mu_msg_get_header (MuMsg *self, const char *header); */ const GSList* mu_msg_get_references (MuMsg *msg); - - - - /** * get the list of tags (ie., X-Label) * diff --git a/src/mu-query.cc b/src/mu-query.cc index 37f2cb5e..88934a3f 100644 --- a/src/mu-query.cc +++ b/src/mu-query.cc @@ -222,29 +222,36 @@ struct _MuQuery { MuSizeRangeProcessor _size_range_processor; }; -static bool -set_query (MuQuery *mqx, Xapian::Query& q, const char* searchexpr, - GError **err) { +static const Xapian::Query +get_query (MuQuery *mqx, const char* searchexpr, GError **err) +{ + Xapian::Query query; + char *preprocessed; + + preprocessed = mu_query_preprocess (searchexpr); + try { - q = mqx->_qparser.parse_query - (searchexpr, + query = mqx->_qparser.parse_query + (preprocessed, Xapian::QueryParser::FLAG_BOOLEAN | Xapian::QueryParser::FLAG_PURE_NOT | Xapian::QueryParser::FLAG_WILDCARD | Xapian::QueryParser::FLAG_AUTO_SYNONYMS | Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE); - - return true; + g_free (preprocessed); + return query; - } MU_XAPIAN_CATCH_BLOCK; - - /* some error occured */ - g_set_error (err, 0, MU_ERROR_QUERY, "parse error in query '%s'", - searchexpr); - - return false; + } catch (...) { + /* some error occured */ + g_set_error (err, 0, MU_ERROR_QUERY, "parse error in query '%s'", + searchexpr); + g_free (preprocessed); + throw; + } } + + static void add_prefix (MuMsgFieldId mfid, Xapian::QueryParser* qparser) { @@ -343,17 +350,6 @@ mu_query_run (MuQuery *self, const char* searchexpr, gboolean threads, sortfieldid == MU_MSG_FIELD_ID_NONE, NULL); try { - Xapian::Query query; - char *preprocessed; - - preprocessed = mu_query_preprocess (searchexpr); - - if (!set_query(self, query, preprocessed, err)) { - g_free (preprocessed); - return NULL; - } - g_free (preprocessed); - Xapian::Enquire enq (self->_db); /* note, when our result will be *threaded*, we sort @@ -361,7 +357,13 @@ mu_query_run (MuQuery *self, const char* searchexpr, gboolean threads, if (!threads && sortfieldid != MU_MSG_FIELD_ID_NONE) enq.set_sort_by_value ((Xapian::valueno)sortfieldid, ascending ? true : false); - enq.set_query(query); + + if (!mu_str_is_empty(searchexpr)) /* NULL or "" */ + enq.set_query(get_query (self, searchexpr, err)); + else + enq.set_query(Xapian::Query::MatchAll); + + enq.set_cutoff(0,0); return mu_msg_iter_new ( @@ -379,17 +381,7 @@ mu_query_as_string (MuQuery *self, const char *searchexpr, GError **err) g_return_val_if_fail (searchexpr, NULL); try { - Xapian::Query query; - char *preprocessed; - - preprocessed = mu_query_preprocess (searchexpr); - - if (!set_query(self, query, preprocessed, err)) { - g_free (preprocessed); - return NULL; - } - g_free (preprocessed); - + Xapian::Query query (get_query(self, searchexpr, err)); return g_strdup(query.get_description().c_str()); } MU_XAPIAN_CATCH_BLOCK_RETURN(NULL); diff --git a/src/mu-query.h b/src/mu-query.h index d91e94e4..a93faa07 100644 --- a/src/mu-query.h +++ b/src/mu-query.h @@ -68,7 +68,7 @@ char* mu_query_version (MuQuery *store) * manpage, or http://xapian.org/docs/queryparser.html * * @param self a valid MuQuery instance - * @param expr the search expression + * @param expr the search expression or "" to match all messages * @param threads calculate message-threads * @param sortfield the field id to sort by or MU_MSG_FIELD_ID_NONE if * sorting is not desired diff --git a/src/mu-runtime.h b/src/mu-runtime.h index e51465ab..6a635652 100644 --- a/src/mu-runtime.h +++ b/src/mu-runtime.h @@ -78,45 +78,6 @@ typedef enum _MuRuntimePath MuRuntimePath; const char* mu_runtime_path (MuRuntimePath path); -/** - * get the mu home directory (typically, ~/.mu); this can only be - * called after mu_runtime_init and before mu_runtime_uninit - * - * @return mu home directory as a string which should be not be - * modified, or NULL in case of error. - */ -const char* mu_runtime_mu_home_dir (void); - -/** - * get the xapian directory (typically, ~/.mu/xapian/); this can only - * be called after mu_runtime_init and before mu_runtime_uninit - * - * @return the xapian directory as a string which should be not be - * modified, or NULL in case of error. - */ -const char* mu_runtime_xapian_dir (void); - - -/** - * get the mu bookmarks file (typically, ~/.mu/bookmarks); this can - * only be called after mu_runtime_init and before mu_runtime_uninit - * - * @return the bookmarks file as a string which should be not be - * modified, or NULL in case of error. - */ -const char* mu_runtime_bookmarks_file (void); - -/** - * get the mu contacts cache file name (typically, - * ~/.mu/contacts.cache); this can only be called after - * mu_runtime_init and before mu_runtime_uninit - * - * @return the contacts cache file name as a string which should be not be - * modified, or NULL in case of error. - */ -const char* mu_runtime_contacts_cache_file (void); - - /** * get the mu configuration options (ie., the parsed command line * parameters) diff --git a/src/mu-str.c b/src/mu-str.c index f91306f6..dde22086 100644 --- a/src/mu-str.c +++ b/src/mu-str.c @@ -443,7 +443,6 @@ mu_str_subject_normalize (const gchar* str) gchar *last_colon; g_return_val_if_fail (str, NULL); - /* FIXME: improve this */ last_colon = g_strrstr (str, ":"); if (!last_colon) return str; diff --git a/src/tests/test-mu-query.c b/src/tests/test-mu-query.c index 6cfeff3f..9518d642 100644 --- a/src/tests/test-mu-query.c +++ b/src/tests/test-mu-query.c @@ -150,6 +150,7 @@ test_mu_query_01 (void) { "foo:pepernoot", 0 }, { "funky", 1 }, { "fünkÿ", 1 }, + { "", 13 } }; diff --git a/toys/Makefile.am b/toys/Makefile.am index 57debc56..aac6132f 100644 --- a/toys/Makefile.am +++ b/toys/Makefile.am @@ -28,3 +28,7 @@ if BUILD_WIDGETS SUBDIRS += mug2 endif +# for muile, we need guile +if HAVE_GUILE +SUBDIRS += muile +endif diff --git a/toys/muile/Makefile.am b/toys/muile/Makefile.am new file mode 100644 index 00000000..938b81b1 --- /dev/null +++ b/toys/muile/Makefile.am @@ -0,0 +1,35 @@ +## Copyright (C) 2011 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 +## t he 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 $(top_srcdir)/gtest.mk + +# enforce compiling this dir first before decending into tests/ +INCLUDES=-I${top_srcdir} -I${top_srcdir}/src ${GUILE_CFLAGS} ${GLIB_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 +AM_CXXFLAGS=-Wall -Wextra -Wno-unused-parameter + +noinst_PROGRAMS= \ + muile + +muile_SOURCES= \ + muile.cc + +muile_LDFLAGS= \ + ${top_builddir}/libmuguile/libmuguile.la diff --git a/toys/muile/README b/toys/muile/README new file mode 100644 index 00000000..177947a9 --- /dev/null +++ b/toys/muile/README @@ -0,0 +1,84 @@ +* README + +** What is muile? + + `muile' is a little experiment/toy using the equally mu guile bindings, to be + found in libmuguile/ in the top-level source directory. + + `guile'[1] is an interpreter/library for the Scheme programming language[2], + specifically meant for extending other programs. It is, in fact, the + official GNU language for doing so. + + The combination of mu + guile is called `muile', and allows you to write + little Scheme-programs to query the mu-database, or inspect individual + messages. It is still in an experimental stage, but useful already. + +** How do I get it? + + The git-version and the future 0.9.7 version of mu will automatically build + muile if you have guile. I've been using guile 2.x from git, but installing + the 'guile-1.8-dev' package (Ubuntu/Debian) should do the trick (I did not + test with 1.8 though). + + Then, configure mu. The configure output should tell you about whether guile + was found (and where). If it's found, build mu, and toys/muile should be + created, as well. + +** What can I do with it? + + Go to toys/muile and start muile. You'll end up with a guile-shell where you + can type scheme [1], it looks something like this (for guile 2.x): + + ,---- + | scheme@(guile-user)> + `---- + + Now, let's load a message (of course, replace with a message on your system): + + ,---- + | scheme@(guile-user)> (define msg (mu:msg:make-from-file "/home/djcb/Maildir/cur/12131e7b20a2:2,S")) + `---- + + This defines a variable 'msg', which holds some message on your file + system. It's now easy to inspect this message: + + ,---- + | scheme@(guile-user)> (define msg (mu:msg:make-from-file "/home/djcb/Maildir/cur/12131e7b20a2:2,S")) + `---- + + Now, we can inspect this message a bit: + ,---- + | scheme@(guile-user)> (mu:msg:subject msg) + | $1 = "See me in bikini :-)" + | scheme@(guile-user)> (mu:msg:flags msg) + | $2 = (attach unread) + `---- + + and so on. Note, it's probably easiest to explore the various mu: methods + using autocompletion; to enable that make sure you have + + (use-modules (ice-9 readline)) + (activate-readline) + + in your ~/.guile configuration. + +** Can I do some statistics on my messages? + + Yes you can. It's pretty easy in guile. See the mu:stats functions. + + + + +[1] http://www.gnu.org/s/guile/ +[2] http://en.wikipedia.org/wiki/Scheme_(programming_language) + +# Local Variables: +# mode: org; org-startup-folded: nil +# End: + + + + + + + diff --git a/toys/muile/mu-stats.scm b/toys/muile/mu-stats.scm new file mode 100644 index 00000000..287e4923 --- /dev/null +++ b/toys/muile/mu-stats.scm @@ -0,0 +1,160 @@ +;; +;; Copyright (C) 2011 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. + +;; some guile/scheme functions to get various statistics of my mu +;; message store. + +;; note, this is a rather inefficient way to calculate the number; for +;; demonstration purposes only +(define* (mu:stats:count #:optional (EXPR "")) + "Count the total number of messages. If the optional EXPR is +provided, only count the messages that match it.\n" + (mu:store:foreach (lambda(msg) #f) EXPR)) + +(define* (mu:stats:average FUNC #:optional (EXPR "")) + "Count the average of the result of applying FUNC on all +messages. If the optional EXPR is provided, only consider the messages +that match it.\n" + (let* ((sum 0) + (n (mu:store:foreach + (lambda(msg) (set! sum (+ sum (FUNC msg)))) EXPR))) + (if (= n 0) 0 (exact->inexact (/ sum n))))) + +(define* (mu:stats:average-size #:optional (EXPR "")) + "Calculate the average message size. If the optional EXPR is +provided, only consider the messages that match it.\n" + (mu:stats:average (lambda(msg) (mu:msg:size msg)) EXPR)) + +(define* (mu:stats:average-recipient-number #:optional (EXPR "")) + "Calculate the average number of recipients (To: + CC: + Bcc:). If +the optional EXPR is provided, only consider the messages that match +it.\n" + (mu:stats:average (lambda(msg) + (+(length (mu:msg:to msg)) + (length (mu:msg:cc msg)) + (length (mu:msg:bcc msg)))) EXPR)) + +(define* (mu:stats:frequency FUNC #:optional (EXPR "")) + "FUNC is a function that takes a Msg, and returns the frequency of +the different values this function returns. If FUNC returns a list, +update the frequency table for each element of this list. If the +optional EXPR is provided, only consider messages that match it.\n" + (let ((table '())) + (mu:store:foreach + (lambda(msg) + ;; note, if val is not already a list, turn it into a list + ;; then, take frequency for each element in the list + (let* ((val (FUNC msg)) (vals (if (list? val) val (list val)))) + (for-each + (lambda (val) + (let ((freq (assoc-ref table val))) + (set! table (assoc-set! table val + (+ 1 (if (eq? freq #f) 0 freq)))))) vals))) EXPR) + table)) + + +(define* (mu:stats:per-weekday #:optional (EXPR "")) + "Count the total number of messages for each weekday (0-6 for +Sun..Sat). If the optional EXPR is provided, only count the messages +that match it. The result is a list of pairs (weekday . frequency).\n" + (let* ((stats (mu:stats:frequency + (lambda (msg) (tm:wday (localtime (mu:msg:date msg)))) EXPR))) + (sort stats (lambda(a b) (< (car a) (car b)))))) ;; in order of weekday + +(define* (mu:stats:per-month #:optional (EXPR "")) + "Count the total number of messages for each month (0-11 for +Jan..Dec). If the optional EXPR is provided, only count the messages +that match it. The result is a list of pairs (month . frequency).\n" + (let* ((stats (mu:stats:frequency + (lambda (msg) (tm:mon (localtime (mu:msg:date msg)))) EXPR))) + (sort stats (lambda(a b) (< (car a) (car b)))))) ;; in order of month + +(define* (mu:stats:per-hour #:optional (EXPR "")) + "Count the total number of messages for each weekday (0-6 for +Sun..Sat). If the optional EXPR is provided, only count the messages +that match it. The result is a list of pairs (weekday . frequency).\n" + (let* ((stats (mu:stats:frequency + (lambda (msg) (tm:hour (localtime (mu:msg:date msg)))) EXPR))) + (sort stats (lambda(a b) (< (car a) (car b)))))) ;; in order of hour + + +(define* (mu:stats:per-year #:optional (EXPR "")) + "Count the total number of messages for each year since 1970. If the +optional EXPR is provided, only count the messages that match it. The +result is a list of pairs (year . frequency).\n" + (let* ((stats (mu:stats:frequency + (lambda (msg) (+ 1900 (tm:year (localtime (mu:msg:date msg))))) + EXPR))) + (sort stats (lambda(a b) (< (car a) (car b)))))) ;; in order of year + + +(define* (mu:stats:top-n FUNC N #:optional (EXPR "")) + "Get the Top-N frequency of the result of FUNC applied on each +message. If the optional EXPR is provided, only consider the messages +that match it." + (let* ((freq (mu:stats:frequency FUNC EXPR)) + (top (sort freq (lambda (a b) (< (cdr b) (cdr a) ))))) + (list-head top (min (length freq) N)))) + +(define* (mu:stats:top-n-to #:optional (N 10) (EXPR "")) + "Get the Top-N To:-recipients. If the optional N is not provided, +use 10. If the optional EXPR is provided, only consider the messages +that match it." + (mu:stats:top-n + (lambda (msg) (mu:msg:to msg)) N EXPR)) + +(define* (mu:stats:top-n-from #:optional (N 10) (EXPR "")) + "Get the Top-N senders (From:). If the optional N is not provided, +use 10. If the optional EXPR is provided, only consider the messages +that match it." + (mu:stats:top-n + (lambda (msg) (mu:msg:from msg)) N EXPR)) + +(define* (mu:stats:top-n-subject #:optional (N 10) (EXPR "")) + "Get the Top-N subjects. If the optional N is not provided, +use 10. If the optional EXPR is provided, only consider the messages +that match it." + (mu:stats:top-n + (lambda (msg) (mu:msg:subject msg)) N EXPR)) + +(define (mu:stats:table pairs) + "display a list of PAIRS in a table-like fashion" + (let ((maxlen 0)) + (for-each ;; find the widest in the first col + (lambda (pair) + (set! maxlen + (max maxlen (string-length (format #f "~s " (car pair)))))) pairs) + (for-each + (lambda (pair) + (let ((first (format #f "~s" (car pair))) + (second (format #f "~s" (cdr pair)))) + (display (format #f "~A~v_~A\n" + first (- maxlen (string-length first)) second)))) + pairs))) + +(define (mu:stats:plot pairs) + "plot a table using gnuplot" + ;; create a tmpfile with the data... + (let* ((datafile (tmpnam)) + (output (open datafile (logior O_CREAT O_WRONLY) #O0644))) + (for-each + (lambda (pair) (display (format #f "~A ~A\n" (car pair) (cdr pair)) output)) + pairs) + (close-output-port output) + ;; now, display it. + (system (format #f "gnuplot -p -e 'plot \"~A\" w boxes fs pattern 2'" datafile)))) diff --git a/toys/muile/muile.cc b/toys/muile/muile.cc new file mode 100644 index 00000000..f6e97d1f --- /dev/null +++ b/toys/muile/muile.cc @@ -0,0 +1,40 @@ +/* +** Copyright (C) 2011 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include + +#include +#include +#include + + +int +main (int argc, char *argv[]) +{ + mu_runtime_init ("/home/djcb/.mu"); + + scm_with_guile (&mu_guile_msg_init, NULL); + scm_with_guile (&mu_guile_store_init, NULL); + + scm_shell (argc, argv); + + mu_runtime_uninit (); + + return 0; +}