Merge branch 'procmule'

This commit is contained in:
Dirk-Jan C. Binnema 2011-07-15 23:53:23 +03:00
commit f2de56d4ca
24 changed files with 1356 additions and 127 deletions

View File

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

12
NEWS
View File

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

25
TODO
View File

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

View File

@ -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"

61
libmuguile/Makefile.am Normal file
View File

@ -0,0 +1,61 @@
## Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## 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)

View File

@ -0,0 +1,41 @@
/*
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include "mu-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 : "<nameless>"),
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);
}

View File

@ -0,0 +1,51 @@
/*
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_GUILE_UTILS_H__
#define __MU_GUILE_UTILS_H__
#include <libguile.h>
#include <glib.h>
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__*/

512
libmuguile/mu-guile-msg.c Normal file
View File

@ -0,0 +1,512 @@
/*
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <mu-msg.h>
#include <mu-query.h>
#include <mu-runtime.h>
#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("<invalid>");
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);
if (msg_smob == SCM_BOOL_F)
scm_puts ("#f", port);
else
scm_puts (mu_msg_get_path(msgwrap->_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;
}

52
libmuguile/mu-guile-msg.h Normal file
View File

@ -0,0 +1,52 @@
/*
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_GUILE_MSG_H__
#define __MU_GUILE_MSG_H__
#include <libguile.h>
#include <mu-msg.h>
#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__*/

125
libmuguile/mu-guile-store.c Normal file
View File

@ -0,0 +1,125 @@
/*
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <mu-query.h>
#include <mu-store.h>
#include <mu-runtime.h>
#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 ("<internal>", 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 ("<internal>", 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;
}

View File

@ -0,0 +1,39 @@
/*
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_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__*/

View File

@ -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.

View File

@ -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)
{

View File

@ -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)
*

View File

@ -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);

View File

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

View File

@ -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)

View File

@ -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;

View File

@ -150,6 +150,7 @@ test_mu_query_01 (void)
{ "foo:pepernoot", 0 },
{ "funky", 1 },
{ "fünkÿ", 1 },
{ "", 13 }
};

View File

@ -28,3 +28,7 @@ if BUILD_WIDGETS
SUBDIRS += mug2
endif
# for muile, we need guile
if HAVE_GUILE
SUBDIRS += muile
endif

35
toys/muile/Makefile.am Normal file
View File

@ -0,0 +1,35 @@
## Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## 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

84
toys/muile/README Normal file
View File

@ -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:

160
toys/muile/mu-stats.scm Normal file
View File

@ -0,0 +1,160 @@
;;
;; Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;;
;; This program is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the
;; Free Software Foundation; either version 3, or (at your option) any
;; later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software Foundation,
;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
;; 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))))

40
toys/muile/muile.cc Normal file
View File

@ -0,0 +1,40 @@
/*
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <mu-runtime.h>
#include <libguile.h>
#include <libmuguile/mu-guile-msg.h>
#include <libmuguile/mu-guile-store.h>
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;
}