From 543f4a192603441032f41c70e70467bfc700ca95 Mon Sep 17 00:00:00 2001 From: djcb Date: Sun, 15 Jul 2012 12:44:52 +0300 Subject: [PATCH] * guile: some more improvements --- guile/mu-guile-message.c | 7 ++-- guile/mu-guile.texi | 64 ++++++++++++----------------------- guile/mu.scm | 4 +-- guile/mu/stats.scm | 33 +++++------------- guile/tests/test-mu-guile.scm | 19 ++++++++--- 5 files changed, 50 insertions(+), 77 deletions(-) diff --git a/guile/mu-guile-message.c b/guile/mu-guile-message.c index 1d099fd8..7f9a45b3 100644 --- a/guile/mu-guile-message.c +++ b/guile/mu-guile-message.c @@ -374,7 +374,7 @@ SCM_DEFINE (get_header, "mu:c:get-header", 2, 0, 0, { MuMsgWrapper *msgwrap; char *header; - const char *val; + SCM val; MU_GUILE_INITIALIZED_OR_ERROR; @@ -384,13 +384,14 @@ SCM_DEFINE (get_header, "mu:c:get-header", 2, 0, 0, msgwrap = (MuMsgWrapper*) SCM_CDR(MSG); header = scm_to_utf8_string (HEADER); - val = mu_msg_get_header (msgwrap->_msg, header); + val = mu_guile_scm_from_str + (mu_msg_get_header(msgwrap->_msg, header)); free (header); /* explicitly close the file backend, so we won't run of fds */ mu_msg_close_file_backend (msgwrap->_msg); - return mu_guile_scm_from_str(val); + return val; } #undef FUNC_NAME diff --git a/guile/mu-guile.texi b/guile/mu-guile.texi index 0c99abc4..93e842e0 100644 --- a/guile/mu-guile.texi +++ b/guile/mu-guile.texi @@ -204,23 +204,14 @@ Enter `,help' for help. scheme@(guile-user)> @end verbatim -Now, the first thing we need to do is load the @t{mu-guile} modules; -currently, there are six available: - -@itemize -@item @code{mu} - initialization, functions to get messages, contacts -@item @code{mu message} - functions to deal with messages -@item @code{mu contact} - functions to deal with contacts -@item @code{mu part} - functions to deal with message-parts / attachments -@item @code{mu stats} - some functions for doing statistics on your messages -@item @code{mu plot} - functions to draw graphs from the statistics (requires @t{gnuplot} -@end itemize +The first thing we need to do is loading the modules. All the basics are in +the @t{(mu)} module, with some statistical extras in @t{(mu stats)}, and some +graph plotting functionality in @t{(mu plot)}. Let's simply load all of them: @verbatim -scheme@(guile-user)> (use-modules (mu) (mu message) (mu contact) (mu part) - (mu stats) (mu plot)) +scheme@(guile-user)> (use-modules (mu) (mu stats) (mu plot)) @end verbatim Assuming you have installed everything correctly, the first time you do this, @@ -383,12 +374,12 @@ subject} of any e-mail messages we received in the year 2011. You can try this if you put the following in a separate file, make it executable, and run it like any program. -@verbatim +@lisp #!/bin/sh exec guile -s $0 $@ !# -(use-modules (mu) (mu message)) +(use-modules (mu)) (use-modules (srfi srfi-1)) (mu:initialize) @@ -405,14 +396,13 @@ exec guile -s $0 $@ subj1 subj2)) "" list-of-subjects)) -(format #t "Longest subject: ~s" longest-subject) -(newline) -@end verbatim +(format #t "Longest subject: ~s\n" longest-subject) +@end lisp There are many other ways to solve the same problem, for example by using an iterative approach with @code{mu:for-each-message}, but it should show how one -can easily write little programs to answer specific questions about an e-mail -corpus. +can easily write little programs to answer specific questions about your +e-mail corpus. @node Contacts @chapter Contacts @@ -422,10 +412,6 @@ like @code{mu:from}, @code{mu:to} etc.; @xref{Message methods}. These functions return the list of recipients as a single string; however, often it is more useful to deal with recipients as separate objects. -@t{mu-guile} offers some functionality for this in the @code{(mu contact)} -module. Also, it adds some contact-related methods for @code{} -objects. - @menu * Contact functions and objects:: * All contacts:: @@ -437,17 +423,12 @@ objects. @node Contact functions and objects @section Contact functions and objects -@verbatim -(use-modules (mu contact)) -@end verbatim - -After loading the @code{(mu contact)}, message objects (@pxref{Messages}) gain -the the @t{contacts}-methods: +Message objects (@pxref{Messages}) have a method @t{mu:contacts}: @code{(mu:contacts [])} The @t{} is a symbol, one of @code{mu:to}, @code{mu:from}, -@code{mu:cc} or @code{mu:bcc}; this will then get the contact objects for the +@code{mu:cc} or @code{mu:bcc}. This will then get the contact objects for the contacts of the corresponding type. If you leave out the contact-type (or specify @t{#t} for it, you will get a list of @emph{all} contact objects for the message. @@ -462,7 +443,7 @@ Let's get a list of all names and e-mail addresses in the 'To:' field, of messages matching 'book': @lisp -(use-modules (mu) (mu message) (mu contact)) +(use-modules (mu)) (mu:initialize) (mu:for-each-message (lambda (msg) @@ -481,10 +462,9 @@ have each of the contacts only show up @emph{once} - for that, please refer to @node All contacts @section All contacts -Sometimes it may also be useful to look at @emph{all} the different contacts -in the @t{mu} database -- that is, all the different contacts. This is useful, -for example, when exporting contacts to some external format that can then be -important in an e-mail program. +Sometimes you may want to inspect @emph{all} the different contacts in the +@t{mu} database. This is useful, for instance, when exporting contacts to some +external format that can then be important in an e-mail program. To enable this, there is the function @code{mu:for-each-contact}, defined as @@ -492,7 +472,7 @@ To enable this, there is the function @code{mu:for-each-contact}, defined as This will aggregate the unique contacts from @emph{all} messages matching @t{} (when it is left empty, it will match all messages in -the database), and execute @t{function} for each of these contacts. +the database), and execute @t{function} for each of them. The @t{function} receives an object of the type @t{}, which is a @emph{subclass} of the @t{} class discussed in @@ -551,7 +531,7 @@ adjust them later by hand, obviously. exec guile -s $0 $@ !# -(use-modules (mu) (mu message) (mu contact)) +(use-modules (mu)) (mu:initialize) ;; Get a list of contacts that were seen at least 20 times since 2010 @@ -627,7 +607,7 @@ in messages about Luxemburg: exec guile -s $0 $@ !# -(use-modules (mu) (mu message) (mu part)) +(use-modules (mu)) (mu:initialize) (define (all-attachments expr) @@ -676,7 +656,7 @@ how many messages we receive per weekday: exec guile -s $0 $@ !# -(use-modules (mu) (mu message) (mu stats) (mu plot)) +(use-modules (mu) (mu stats) (mu plot)) (mu:initialize) ;; create a list like (("Sun" . 13) ("Mon" . 23) ...) @@ -746,7 +726,7 @@ message per hour: exec guile -s $0 $@ !# -(use-modules (mu) (mu message) (mu contact) (mu stats) (mu plot)) +(use-modules (mu) (mu stats) (mu plot)) (mu:initialize) (define (mail-per-hour-table) @@ -784,8 +764,6 @@ exec guile -s $0 $@ Hour @end verbatim - - @node GNU Free Documentation License @appendix GNU Free Documentation License diff --git a/guile/mu.scm b/guile/mu.scm index a89ceb8c..8b60a73a 100644 --- a/guile/mu.scm +++ b/guile/mu.scm @@ -35,7 +35,7 @@ mu:for-each-msg mu:message-list ;; message funcs - header + mu:header ;; message accessors mu:field:bcc mu:field:body-html @@ -143,7 +143,7 @@ (define-getter mu:timestamp mu:field:timestamp) (define-getter mu:to mu:field:to) -(define-method (header (msg ) (hdr )) +(define-method (mu:header (msg ) (hdr )) "Get an arbitrary header HDR from message MSG; return #f if it does not exist." (mu:c:get-header (slot-ref msg 'msg) hdr)) diff --git a/guile/mu/stats.scm b/guile/mu/stats.scm index 9377c4bc..63ac8f04 100644 --- a/guile/mu/stats.scm +++ b/guile/mu/stats.scm @@ -24,8 +24,9 @@ :use-module (ice-9 r5rs) :export ( mu:tabulate mu:average - mu:standard-deviation - mu:pearsons-r + mu:stddev + mu:max + mu:min mu:weekday-numbers->names mu:month-numbers->names)) @@ -68,11 +69,11 @@ undefined." EXPR (or #t for all). Returns #f if undefined." (average (map func (mu:message-list expr)))) -(define* (mu:standard-deviation func #:optional (expr #t)) - "Get the standard deviation for the the values of FUNC applied to -all messages matching EXPR (or #t for all). Returns #f if undefined." +(define* (mu:stddev func #:optional (expr #t)) + "Get the standard deviation the the values of FUNC applied to all +messages matching EXPR (or #t for all). This is the 'population' stddev, not the 'sample' stddev. Returns #f if undefined." (stddev (map func (mu:message-list expr)))) - + (define* (mu:max func #:optional (expr #t)) "Get the maximum value of FUNC applied to all messages matching EXPR (or #t for all). Returns #f if undefined." @@ -82,25 +83,7 @@ EXPR (or #t for all). Returns #f if undefined." "Get the minimum value of FUNC applied to all messages matching EXPR (or #t for all). Returns #f if undefined." (apply min (map func (mu:message-list expr)))) - -(define* (mu:pearsons-r func1 func2 #:optional (expr #t)) - "Calculate Pearson's product-moment correlation coefficient between -func1 and func2. Inefficient implementation." - (let* ((msglist (mu:message-list expr)) - (lst-x (map func1 msglist)) - (lst-y (map func2 msglist)) - (avg-x (average lst-x)) - (avg-y (average lst-y)) - (denominator (sqrt (* (stddev lst-x) (stddev lst-y)))) - (n (length lst-x)) - (cov-xy 0)) - (while (not (null? lst-x)) - (set! cov-xy (+ (* (- (car lst-x) avg-x) (- (car lst-y) avg-y)))) - (set! lst-x (cdr lst-x)) - (set! lst-y (cdr lst-y))) - (/ (/ cov-xy n) denominator))) - - + ;; a list of abbreviated, localized day names (define day-names (map locale-day-short (iota 7 1))) diff --git a/guile/tests/test-mu-guile.scm b/guile/tests/test-mu-guile.scm index 91cc658d..8d52e17e 100755 --- a/guile/tests/test-mu-guile.scm +++ b/guile/tests/test-mu-guile.scm @@ -20,8 +20,9 @@ exec guile -e main -s $0 $@ ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. (setlocale LC_ALL "") +(use-modules (srfi srfi-1)) (use-modules (ice-9 getopt-long) (ice-9 optargs) (ice-9 popen) (ice-9 format)) -(use-modules (mu)) +(use-modules (mu) (mu stats)) (define (n-results-or-exit query n) "Run QUERY, and exit 1 if the number of results != N." @@ -73,6 +74,7 @@ exec guile -e main -s $0 $@ (str-equal-or-exit (mu:subject msg) "Fwd: rfc822") (str-equal-or-exit (mu:to msg) "martin") (str-equal-or-exit (mu:from msg) "foobar ") + (str-equal-or-exit (mu:header msg "X-Mailer") "Ximian Evolution 1.4.5") (if (not (equal? (mu:priority msg) mu:prio:normal)) (error-exit "Expected ~A, got ~A" (mu:priority msg) mu:prio:normal))) @@ -81,18 +83,27 @@ exec guile -e main -s $0 $@ (str-equal-or-exit (mu:subject msg) "atoms") (str-equal-or-exit (mu:to msg) "Democritus ") (str-equal-or-exit (mu:from msg) "\"Richard P. Feynman\" ") + (str-equal-or-exit (mu:header msg "Content-transfer-encoding") "7BIT") (if (not (equal? (mu:priority msg) mu:prio:high)) (error-exit "Expected ~a, got ~a" (mu:priority msg) mu:prio:high)))) +(define (num-equal-or-exit got exp) + "S1 == S2 or exit 1." + ;; (format #t "'~A' <=> '~A'\n" s1 s2) + (if (not (= exp got)) + (error-exit "Expected \"~S\", got \"~S\"\n" exp got))) (define (test-stats) "Test statistical functions." - ) - + ;; average + (num-equal-or-exit (mu:average mu:size) 20422/3) + (num-equal-or-exit (floor (mu:stddev mu:size)) + (floor 13414.7101616927)) + (num-equal-or-exit (mu:max mu:size) 46230) + (num-equal-or-exit (mu:min mu:size) 111)) (define (main args) - (let* ((optionspec '((muhome (value #t)) (test (value #t)))) (options (getopt-long args optionspec))