* mu-guile.texi: extend / improve mu-guile documentation

This commit is contained in:
djcb
2012-01-15 14:38:08 +02:00
parent 799e3e150a
commit 9d84138a73

View File

@ -28,8 +28,9 @@ Documentation License.''
Welcome to @t{mu-guile}! Welcome to @t{mu-guile}!
@t{mu-guile} is a binding of the @t{mu} email search engine and the @t{guile} @t{mu-guile} is a binding of the @t{mu} email search engine for the @t{guile}
programming language. programming language. That means that you can write simple (and not so simple)
programs to data-mine your e-mail database.
@menu @menu
* Introduction:: * Introduction::
@ -49,17 +50,14 @@ Appendices
@node Introduction @node Introduction
@chapter Introduction @chapter Introduction
@t{mu4e} is an e-mail program for @emph{GNU/Emacs}; it uses the @t{mu} maildir @t{mu} is a program for indexing / searching e-mails stored in Maildirs.
search engine as its backend, making @t{mu} fully search-based.
@t{mu} is a program for indexing / searching e-mails, as well as an
@t{emacs}-based email-client (@t{mu4e}.
@t{guile} is the @emph{GNU Ubiquitous Intelligent Language for Extensions} - a @t{guile} is the @emph{GNU Ubiquitous Intelligent Language for Extensions} - a
version of the @emph{Scheme} programming language and the official GNU version of the @emph{Scheme} programming language and the official GNU
extension language. extension language.
@t{mu-guile} connects @t{mu} and @t{guile}, and allows you to easily write @t{mu-guile} connects @t{mu} and @t{guile}, and allows you to easily write
programs to do things with your e-mails. programs for your specific needs.
@node Getting started @node Getting started
@chapter Getting started @chapter Getting started
@ -76,12 +74,10 @@ things work correctly.
@section Installation @section Installation
@t{mu-guile} is part of @t{mu} - by installing the latter, the former will be @t{mu-guile} is part of @t{mu} - by installing the latter, the former will be
installed as well. Note, however, that @t{mu-guile} requires you to have installed as well, provided that you have @t{guile} version 2.0 installed.
@t{guile} version 2.0 installed, otherwise @t{mu-guile} will not be
built/installed.
At the time of writing, there are no distribution packages for @t{mu-guile} At the time of writing, there are no distribution packages for @t{mu-guile},
yet, so we are assuming installation from source packages. so we are assuming installation from source packages.
Installation follows the normal sequence of: Installation follows the normal sequence of:
@example @example
@ -148,7 +144,7 @@ Enter `,help' for help.
scheme@(guile-user)> scheme@(guile-user)>
@end verbatim @end verbatim
Now, we need to load the @t{mu-guile} module: Now, we need to load some of the @t{mu-guile} modules:
@verbatim @verbatim
scheme@(guile-user)> (use-modules (mu) (mu message)) scheme@(guile-user)> (use-modules (mu) (mu message))
@ -161,17 +157,22 @@ loaded the modules, we need to initialize the @t{mu-guile} system:
scheme@(guile-user)> (mu:initialize) scheme@(guile-user)> (mu:initialize)
@end verbatim @end verbatim
When this is done, we can start querying the database. We'll go into various When this is done, we can start querying the database. We discuss various
functions later in this manual, but just to give an example, let's get a list methods and functions later in this manual, but just to give an example, let's
of the subjects of all messages that mention @emph{hello}: get a list of the subjects of all messages that mention @emph{hello}:
@verbatim @verbatim
scheme@(guile-user)> (for-each scheme@(guile-user)> (for-each
(lambda(msg) (lambda(msg)
(format #t "Subject: ~a\n" (subject msg))) (format #t "Subject: ~a\n" (mu:subject msg)))
(mu:message-list "hello")) (mu:message-list "hello"))
@end verbatim @end verbatim
Note, the multi-lines in the example are only for readability; since it can be
a bit uncomfortable to type long sequences at the 'REPL' (the Guile
command-line), we recommend using a tool like
Geiser@footnote{@url{http://www.nongnu.org/geiser/}}.
@node Initializing mu-guile @node Initializing mu-guile
@chapter Initializing mu-guile @chapter Initializing mu-guile
@ -195,29 +196,30 @@ scheme@(guile-user)>
@end verbatim @end verbatim
Now, the first thing we need to do is load the @t{mu-guile} modules; Now, the first thing we need to do is load the @t{mu-guile} modules;
currently, there are three available: currently, there are six available:
@itemize @itemize
@item @code{mu} - initialization, functions to get messages, contacts @item @code{mu} - initialization, functions to get messages, contacts
@item @code{mu message} - functions to deal with messages @item @code{mu message} - functions to deal with messages
@item @code{mu contact} - functions to deal with contacts @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 @end itemize
Let's simply load all of them: Let's simply load all of them:
@verbatim @verbatim
scheme@(guile-user)> (use-modules (mu) (mu message) (mu contact)) scheme@(guile-user)> (use-modules (mu) (mu message) (mu contact) (mu part)
(mu stats) (mu plot))
@end verbatim @end verbatim
Assuming you have installed everything correctly, the first time you do this, Assuming you have installed everything correctly, the first time you do this,
@t{guile} will probably respond by showing some message about compiling the @t{guile} will probably respond by showing some message about compiling the
modules, and then end with another prompt. modules, and then return to you with another prompt.
Before we can do anything with @t{mu guile}, we need to initialize the Before we can do anything with @t{mu guile}, we need to initialize the
system. The reason as to not do this automatically is to enable people to use system. This goes like this:
non-default places to keep there @t{mu} data files.
We can initialize the system with:
@verbatim @verbatim
scheme@(guile-user)> (mu:initialize) scheme@(guile-user)> (mu:initialize)
@ -230,24 +232,25 @@ your @t{mu} data in a non-standard place:
scheme@(guile-user)> (mu:initialize "/path/to/my/mu/") scheme@(guile-user)> (mu:initialize "/path/to/my/mu/")
@end verbatim @end verbatim
If all worked up until here, we're ready to go with @t{mu-guile}. If all worked up until here, we're ready to go with @t{mu-guile} - hurray! In
the next chapters we'll walk through all the modules.
@node Messages @node Messages
@chapter Messages @chapter Messages
In this chapter, we discuss how to find messages, and then how to do various In this chapter, we discuss how to find messages and how to do various things
things with them. with them.
@menu @menu
* Finding messages:: * Finding messages::
* Message functions:: * Message methods::
* Example - the longest subject:: * Example - the longest subject::
@end menu @end menu
@node Finding messages @node Finding messages
@section Finding messages @section Finding messages
Now we are ready to retrieve some messages from the system. There are two Now we are ready to retrieve some messages from the system. There are two main
principle functions to do this: functions to do this:
@itemize @itemize
@item @code{(mu:message-list [<search-expression>])} @item @code{(mu:message-list [<search-expression>])}
@ -263,26 +266,27 @@ could do:
@verbatim @verbatim
scheme@(guile-user)> (mu:message-list "subject:coffee") scheme@(guile-user)> (mu:message-list "subject:coffee")
$1 = (#<<mu-message> 9040640> #<<mu-message> 9040630> $1 = (#<<mu:message> 9040640> #<<mu:message> 9040630>
#<<mu-message> 9040570>) #<<mu:message> 9040570>)
@end verbatim @end verbatim
So, we get a list with three @t{<mu-message>} objects. We'll discuss them in a So, since apparently we have three messages matching @t{subject:coffee}, we
bit more detail in the next section, but let's just use the @code{subject} get a list of three @t{<mu:message>} objects. Let's just use the
function ('method') provided by @t{<mu-message>} objects to retrieve the @code{mu:subject} function ('method') provided by @t{<mu:message>} objects to
subject-field. retrieve the subject-field (more about methods in the next section).
For your convenience, @t{guile} has saved the result in @t{$1}, so to get the For your convenience, @t{guile} has saved the result of our last query in a
subject of the first message in the list, we can do: variable called @t{$1}, so to get the subject of the first message in the
list, we can do:
@verbatim @verbatim
scheme@(guile-user)> (subject (car $1)) scheme@(guile-user)> (mu:subject (car $1))
$2 = "Re: best coffee ever!" $2 = "Re: best coffee ever!"
@end verbatim @end verbatim
The second function we mentioned, @code{mu:for-each-message}, executes some The second function we mentioned, @code{mu:for-each-message}, executes some
function for each message matched by the search expression (or @emph{all} function for each message matched by the search expression (or @emph{all}
message if the search expression is omitted). messages if the search expression is omitted).
@verbatim @verbatim
scheme@(guile-user)> (mu:for-each-message scheme@(guile-user)> (mu:for-each-message
@ -300,78 +304,72 @@ Using @code{mu:message-list} and/or
@code{mu:for-each-message}@footnote{Implementation node: @code{mu:for-each-message}@footnote{Implementation node:
@code{mu:message-list} is implemented in terms of @code{mu:for-each-message}, @code{mu:message-list} is implemented in terms of @code{mu:for-each-message},
not the other way around. Due to the way @t{mu} works, not the other way around. Due to the way @t{mu} works,
@code{mu:for-each-message} is rather more efficient than a combination for @code{mu:for-each-message} is rather more efficient than a combination of
@code{for-each} and @code{mu:message-list}} and a couple of @t{<mu-message>} @code{for-each} and @code{mu:message-list}} and a couple of @t{<mu:message>}
methods, together with that Guile/Scheme provides should allow for many methods, together with what Guile/Scheme provides, should allow for many
interesting programs. interesting programs.
@node Message functions @node Message methods
@section Message functions @section Message methods
Now that we've seen how to retrieve lists of message objects Now that we've seen how to retrieve lists of message objects
(@code{<mu-message>}), let's see what we can do with such an object. (@code{<mu:message>}), let's see what we can do with such an object.
@code{<mu-message>} defines the following methods @code{<mu:message>} defines the following methods that all take a single
@footnote{A note on naming: functions we have seen before -- @code{<mu:message>} object as a parameter. We won't go into the exact meanings
@code{mu:initialize}, @code{mu:message-list} and @code{mu:for-each-message}
are prefixed with @t{mu:}. This is not the case for the @code{<mu-message>}
methods to be discussed next, such as the methods @code{subject} and
@code{from}. Reason for this is that it is not @emph{needed}, since these
methods only recognized for @code{<mu-message>} objects, and do not affect
anything else, while the @code{mu:}-prefixed are 'globally visible' and thus
we need to be careful about naming conflicts}
that all only take a single
@code{<mu-message>} object as a parameter. We won't go into the exact meanings
for all of these functions here - for the details about various flags / for all of these functions here - for the details about various flags /
properties, please refer to the @t{mu-find} man-page. properties, please refer to the @t{mu-find} man-page.
@itemize @itemize
@item @code{bcc}: the @t{Bcc} field of the message, or @t{#f} if there is none @item @code{(mu:bcc msg)}: the @t{Bcc} field of the message, or @t{#f} if there is none
@item @code{body-html}: : the html body of the message, or @t{#f} if there is none @item @code{(mu:body-html msg)}: : the html body of the message, or @t{#f} if there is none
@item @code{body-txt}: the plain-text body of the message, or @t{#f} if there is none @item @code{(mu:body-txt msg)}: the plain-text body of the message, or @t{#f} if there is none
@item @code{cc}: the @t{Bcc} field of the message, or @t{#f} if there is none @item @code{(mu:cc msg)}: the @t{Bcc} field of the message, or @t{#f} if there is none
@item @code{date}: the @t{Date} field of the message, or 0 if there is none @item @code{(mu:date msg)}: the @t{Date} field of the message, or 0 if there is none
@item @code{flags}: list of message-flags for this message @item @code{(mu:flags msg)}: list of message-flags for this message
@item @code{from}: the @t{From} field of the message, or @t{#f} if there is none @item @code{(mu:from msg)}: the @t{From} field of the message, or @t{#f} if there is none
@item @code{maildir}: the maildir this message lives in, or @t{#f} if there is none @item @code{(mu:maildir msg)}: the maildir this message lives in, or @t{#f} if there is none
@item @code{message-id}: the @t{Message-Id} field of the message, or @t{#f} if there is none @item @code{(mu:message-id msg)}: the @t{Message-Id} field of the message, or @t{#f} if there is none
@item @code{path}: the file system path for this message @item @code{(mu:path msg)}: the file system path for this message
@item @code{priority}: the priority of this message (either @t{mu:low}, @t{mu:normal} @item @code{(mu:priority msg)}: the priority of this message (either @t{mu:prio:low}, @t{mu:prio:normal} or @t{mu:prio:high}
or @t{mu:high} @item @code{(mu:references msg)}: the list of messages (message-ids) this message
@item @code{references}: the list of messages (message-ids) this message refers to in(mu: the @t{References:} header
refers to in the @t{References:} header @item @code{(mu:size msg)}: size of the message in bytes
@item @code{size}: size of the message in bytes @item @code{(mu:subject msg)}: the @t{Subject} field of the message, or @t{#f} if there is none.
@item @code{subject}: the @t{Subject} field of the message, or @t{#f} if there is none. @item @code{(mu:tags msg)}: list of tags for this message
@item @code{tags}: list of tags for this message @item @code{(mu:to msg)}: the sender of the message, or @t{#f} if there is none.
@item @code{to}: the sender of the message, or @t{#f} if there is none.
@end itemize @end itemize
With these functions, we can query messages for their properties; for example: With these methods, we can query messages for their properties; for example:
@verbatim @verbatim
scheme@(guile-user)> (define msg (car (mu:message-list "snow"))) scheme@(guile-user)> (define msg (car (mu:message-list "snow")))
scheme@(guile-user)> (subject msg) scheme@(guile-user)> (mu:subject msg)
$1 = "Re: Running in the snow is beautiful" $1 = "Re: Running in the snow is beautiful"
scheme@(guile-user)> (flags msg) scheme@(guile-user)> (mu:flags msg)
$2 = (mu:replied mu:seen) $2 = (mu:flag:replied mu:flag:seen)
scheme@(guile-user)> (strftime "%F" (localtime (date msg))) scheme@(guile-user)> (strftime "%F" (localtime (mu:date msg)))
$3 = "2011-01-15" $3 = "2011-01-15"
@end verbatim @end verbatim
There are a couple more functions: There are a couple more methods:
@itemize @itemize
@item @code{(header <mu-message> "<header-name>")} returns an arbitrary message @item @code{(mu:header msg "<header-name>")} returns an arbitrary message
header (or @t{#f} if not found) -- e.g. @code{(header msg "User-Agent")} header (or @t{#f} if not found) -- e.g. @code{(header msg "User-Agent")}
@item @code{(contacts <mu-message> contact-type)} which returns a list @item If you include the @t{mu contact} module, the @code{(mu:contacts
of contacts (names/e-mail addresses in the To/From/Cc/Bcc-fields). @xref{Contacts}. msg [contact-type])} method (to get a list of contacts) is
added. @xref{Contacts}.
@item If you include the @t{mu part} module, the @code{((mu:parts msg)} and
@code{(mu:attachments msg)} methods are added. @xref{Attachments and other parts}.
@end itemize @end itemize
@node Example - the longest subject @node Example - the longest subject
@section Example - the longest subject @section Example - the longest subject
Now, let's write a little example -- let's find out what is the @emph{longest Now, let's write a little example -- let's find out what is the @emph{longest
subject} of any e-mail messages we received in the year 2011. If you put the subject} of any e-mail messages we received in the year 2011. You can try
following in a separate file, make it executable, and run it like any program. this if you put the following in a separate file, make it executable, and run
it like any program.
@verbatim @verbatim
#!/bin/sh #!/bin/sh
@ -386,7 +384,7 @@ exec guile -s $0 $@
;; note: (subject msg) => #f if there is no subject ;; note: (subject msg) => #f if there is no subject
(define list-of-subjects (define list-of-subjects
(map (lambda (msg) (map (lambda (msg)
(or (subject msg) "")) (mu:message-list "date:2011..2011"))) (or (mu:subject msg) "")) (mu:message-list "date:2011..2011")))
;; see the mu-find manpage for the date syntax ;; see the mu-find manpage for the date syntax
(define longest-subject (define longest-subject
@ -408,16 +406,18 @@ corpus.
@chapter Contacts @chapter Contacts
We can retrieve the sender and recipients of an e-mail message using methods We can retrieve the sender and recipients of an e-mail message using methods
like @code{from}, @code{to}, @code{cc} and @code{bcc}; @xref{Message like @code{mu:from}, @code{mu:to} etc.; @xref{Message methods}. These
functions}. These functions return the list of recipients as a single string; functions return the list of recipients as a single string; however, often it
however, often it is more useful to deal with recipients as separate objects. is more useful to deal with recipients as separate objects.
@t{mu-guile} offers some functionality for this in the @code{(mu contact)} @t{mu-guile} offers some functionality for this in the @code{(mu contact)}
module. module. Also, it adds some contact-related methods for @code{<mu:message>}
objects.
@menu @menu
* Contact functions and objects:: * Contact functions and objects::
* All contacts:: * All contacts::
* Utility functions::
* Example - mutt export:: * Example - mutt export::
@end menu @end menu
@ -432,7 +432,7 @@ module.
After loading the @code{(mu contact)}, message objects (@pxref{Messages}) gain After loading the @code{(mu contact)}, message objects (@pxref{Messages}) gain
the the @t{contacts}-methods: the the @t{contacts}-methods:
@code{(contacts <message-object> [<contact-type>])} @code{(mu:contacts <message-object> [<contact-type>])}
The @t{<contact-type>} is a symbol, one of @code{mu:to}, @code{mu:from}, The @t{<contact-type>} 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
@ -440,10 +440,10 @@ 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 specify @t{#t} for it, you will get a list of @emph{all} contact objects for
the message. the message.
A contact object (@code{<mu-contact>}) has two methods: A contact object (@code{<mu:contact>}) has two methods:
@itemize @itemize
@item @code{name} returns the name of the contact, or #f if there is none @item @code{mu:name} returns the name of the contact, or #f if there is none
@item @code{email} returns the e-mail address of the contact, or #f if there is none @item @code{mu:email} returns the e-mail address of the contact, or #f if there is none
@end itemize @end itemize
Let's get a list of all names and e-mail addresses in the 'To:' field, of Let's get a list of all names and e-mail addresses in the 'To:' field, of
@ -457,8 +457,8 @@ messages matching 'book':
(for-each (for-each
(lambda (contact) (lambda (contact)
(format #t "~a => ~a\n" (format #t "~a => ~a\n"
(or (email contact) "") (or (name contact) "no-name"))) (or (mu:email contact) "") (or (mu:name contact) "no-name")))
(contacts msg mu:to))) (mu:contacts msg mu:field:to)))
"book") "book")
@end lisp @end lisp
@ -476,26 +476,39 @@ important in an e-mail program.
To enable this, there is the function @code{mu:for-each-contact}, defined as To enable this, there is the function @code{mu:for-each-contact}, defined as
@code{(mu:for-each-contact <function> [<search-expression>])}. @code{(mu:for-each-contact function [search-expression])}.
This will aggregate the unique contacts from @emph{all} messages matching This will aggregate the unique contacts from @emph{all} messages matching
@t{<search-expression>} (when it is left empty, it will match all messages in @t{<search-expression>} (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 these contacts.
The @t{<function>} receives an object of the type @t{<contact-with-stats>}, The @t{function} receives an object of the type @t{<mu:contact-with-stats>},
which is a @emph{subclass} of the @t{<contact>} class discussed in which is a @emph{subclass} of the @t{<mu:contact>} class discussed in
@xref{Contact functions and objects}. @t{<contact-with-stats>} objects expose @xref{Contact functions and objects}. @t{<mu:contact-with-stats>} objects
the following methods: expose the following additional methods:
@itemize @itemize
@item @code{frequency}: returns the @emph{number of times} this contact occured in @item @code{(mu:frequency <contact>)}: returns the @emph{number of times} this contact occured in
one of the address fields one of the address fields
@item @code{last-seen}: returns the @emph{most recent time} the contact was @item @code{(mu:last-seen <contact>)}: returns the @emph{most recent time} the contact was
seen in one of the address fields, as a @t{time_t} value seen in one of the address fields, as a @t{time_t} value
@end itemize @end itemize
The function aggregates per e-mail address; if a certain e-mail address occurs The method assumes an e-mail address is unique for a certain contact; if a
with different names, it uses the most recent non-empty name. certain e-mail address occurs with different names, it uses the most recent
non-empty name.
@node Utility functions
@section Utility functions
To make dealing with contacts even easier, there are a number of utility
functions that can save you a bit of typing.
For converting contacts to some textual form, there is @code{(mu:contact->string
<mu:contact> format)}, which takes a contact and returns a text string with
the given format. Currently supported formats are @t{"org-contact}, @t{"mutt-alias"},
@t{"mutt-ab"}, @t{"wanderlust"} and @t{"plain"}.
@node Example - mutt export @node Example - mutt export
@section Example - mutt export @section Example - mutt export
@ -510,46 +523,41 @@ something like:
alias <nick> [<name>] "<" <email> ">" alias <nick> [<name>] "<" <email> ">"
@end verbatim @end verbatim
Many of the names in our database could be random people writing things in Anyway, there is the function @code{(mu:contact->string <mu:contact> format)}
mailing lists, so we may want to limit it to people we have seen at least 10 that we can use to do the conversion.
times in the last year.
It is a bit hard to @emph{guess} the nick name for e-mail contacts, so we are We may want to focus on people with whom we have frequent correspondence; so
going to assume it is the lowercase version of the first word in we may want to limit ourselves to people we have seen at least 10 times in the
@t{<name>}. You can always adjust them later by hand, obviously. last year.
It is a bit hard to @emph{guess} the nick name for e-mail contacts, but
@code{mu:contact->string} tries something based on the name. You can always
adjust them later by hand, obviously.
@lisp @lisp
#!/bin/sh #!/bin/sh
exec guile -s $0 $@ exec guile -s $0 $@
!# !#
(use-modules (mu) (mu message) (mu contact)) (use-modules (mu) (mu message) (mu contact))
(mu:initialize) (mu:initialize)
;; Get a list of contacts that were seen at least 20 times since 2010
(define (selected-contacts) (define (selected-contacts)
"Get a list of contacts that were seen at least 20 times since
2010."
(let ((addrs '()) (let ((addrs '())
(start (car (mktime (car (strptime "%F" "2010-01-01"))))) (start (car (mktime (car (strptime "%F" "2010-01-01")))))
(minfreq 20)) (minfreq 20))
(mu:for-each-contact (mu:for-each-contact
(lambda (contact) (lambda (contact)
(if (and (email contact) (if (and (mu:email contact)
(>= (frequency contact) minfreq) (>= (mu:frequency contact) minfreq)
(>= (last-seen contact) start)) (>= (mu:last-seen contact) start))
(set! addrs (cons contact addrs))))) (set! addrs (cons contact addrs)))))
addrs)) addrs))
(define (guess-nick contact)
"Guess a nick name for CONTACT."
(string-map
(lambda(kar)
(if (char-alphabetic? kar) kar #\_))
(string-downcase (or (name contact) (email contact)))))
(for-each (for-each
(lambda (contact) (lambda (contact)
(format #t "alias ~a ~a <~a>\n" (format #t "~a\n" (mu:contact->string contact "mutt-alias")))
(guess-nick contact) (name contact) (email contact)))
(selected-contacts)) (selected-contacts))
@end lisp @end lisp
@ -570,39 +578,37 @@ is the @t{mu part} module.
@node Parts and their methods @node Parts and their methods
@section Parts and their methods @section Parts and their methods
The module defines the @code{<mu-part>} class, and adds two methods to The module defines the @code{<mu-part>} class, and adds two methods to
@code{<mu-message>} objects: @code{<mu:message>} objects:
@itemize @itemize
@item @code{(parts msg)} - returns a list @code{<mu-part>} objects, one for @item @code{(mu:parts msg)} - returns a list @code{<mu-part>} objects, one for
each MIME-parts in the message. each MIME-parts in the message.
@item @code{(attachments)} - like @code{parts}, but only list those MIME-parts @item @code{(mu:attachments msg)} - like @code{parts}, but only list those MIME-parts
that look like proper attachments. that look like proper attachments.
@end itemize @end itemize
A @code{<mu-part>} object exposes a few methods to get information about the A @code{<mu:part>} object exposes a few methods to get information about the
part: part:
@itemize @itemize
@item @code{name} - returns the file name of the mime-part, or @code{#f} if @item @code{(mu:name <part>)} - returns the file name of the mime-part, or @code{#f} if
there is none. there is none.
@item @code{mime-type} - returns the mime-type of the mime-part, or @code{#f} @item @code{(mu:mime-type <part>)} - returns the mime-type of the mime-part, or @code{#f}
if there is none. if there is none.
@item @code{size} - returns the size in bytes of the mime-part @item @code{(mu:size <part>)} - returns the size in bytes of the mime-part
@end itemize @end itemize
Then, we may want to save the part to a file; this can be done using either: Then, we may want to save the part to a file; this can be done using either:
@itemize @itemize
@item @code{(save part)} - save a part to a temporary file, return the file @item @code{(mu:save part <part>)} - save a part to a temporary file, return the file
name@footnote{the temporary filename is a predictable function of (user-id, name@footnote{the temporary filename is a predictable function of (user-id,
msg-path, part-index)} msg-path, part-index)}
@item @code{(save-as part path)} - save part to file at path @item @code{(mu:save-as <part> <path>)} - save part to file at path
@end itemize @end itemize
@node Attachment example @node Attachment example
@section Attachment example @section Attachment example
Let's look at some small examples. Let's look at some small example. Let's get a list of the biggest attachments
in messages about Luxemburg:
First, let's get a list of the biggest attachments in messages about
Luxemburg:
@lisp @lisp
#!/bin/sh #!/bin/sh
@ -620,8 +626,8 @@ matching EXPR."
(lambda (msg) (lambda (msg)
(for-each (for-each
(lambda (att) ;; add (filename . size) to the list (lambda (att) ;; add (filename . size) to the list
(set! pairs (cons (cons (name att) (or (size att) 0)) pairs))) (set! pairs (cons (cons (mu:name att) (or (mu:size att) 0)) pairs)))
(attachments msg))) (mu:attachments msg)))
expr) expr)
pairs)) pairs))
@ -645,9 +651,9 @@ probably be a bit more elegant.
@t{mu-guile} offers some convenience functions to determine various statistics @t{mu-guile} offers some convenience functions to determine various statistics
about the messages in the database. about the messages in the database.
First, there is @code{(mu:tabulate-messages <function> [<search-expr>])}. This @code{(mu:tabulate-messages <function> [<search-expr>])} applies
function applies @t{<function>} to each message matching @t{<search-expr>} @t{<function>} to each message matching @t{<search-expr>} (leave empty to
(leave empty to match @emph{all} messages), and returns a associative list match @emph{all} messages), and returns a associative list (a list of pairs)
with each of the different results of @t{<function>} and their frequencies. with each of the different results of @t{<function>} and their frequencies.
This can best be demonstrated with a little example. Suppose we want to know This can best be demonstrated with a little example. Suppose we want to know
@ -658,34 +664,36 @@ how many messages we receive per weekday:
exec guile -s $0 $@ exec guile -s $0 $@
!# !#
(use-modules (mu) (mu message) (mu stats)) (use-modules (mu) (mu message) (mu stats) (mu plot))
(mu:initialize) (mu:initialize)
(define (weekday-table) ;; create a list like (("Sun" . 13) ("Mon" . 23) ...)
"Returns something like (define weekday-table
'((0 . 12) (5 . 20) (2 . 16) ... ) (mu:weekday-numbers->names
that is, an unsorted list of (<weekday> . <frequency>)." (sort
(mu:tabulate-messages (mu:tabulate-messages
(lambda (msg) (lambda (msg)
(tm:wday (localtime (date msg)))))) (tm:wday (localtime (mu:date msg)))))
(lambda (a b) (< (car a) (car b))))))
;; sort & display (for-each
(let ((table (weekday-table))) (lambda (elm)
(for-each (format #t "~a: ~a\n" (car elm) (cdr elm)))
(lambda (pair) weekday-table)
(let ((days '("Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat")))
(format #t "~a: ~a\n"
(list-ref days (car pair)) (cdr pair))))
(sort (weekday-table)(lambda (a b) (< (car a) (car b))))))
@end lisp @end lisp
The function @code{weekday-table} use @code{mu:tabulate-message} to get the The function @code{weekday-table} uses @code{mu:tabulate-message} to get the
frequencies per hour -- this returns a list of pairs: frequencies per hour -- this returns a list of pairs:
@verbatim @verbatim
((5 . 2339) (0 . 2278) (4 . 2800) (2 . 3184) (6 . 1856) (3 . 2833) (1 . 2993)) ((5 . 2339) (0 . 2278) (4 . 2800) (2 . 3184) (6 . 1856) (3 . 2833) (1 . 2993))
@end verbatim @end verbatim
The script then output these numbers in following form: We sort these pairs by the day number, and then apply
@code{mu:weekday-numbers->names}, which takes the list, and returns a list
where the day numbers are replace by there abbreviated name (in the current
locale). Note, there is also @code{mu:month-numbers->names}.
The script then outputs these numbers in the following form:
@verbatim @verbatim
Sun: 2278 Sun: 2278
@ -702,6 +710,25 @@ Clearly, Saturday is a slow day for e-mail...
@node Plotting data @node Plotting data
@chapter Plotting data @chapter Plotting data
You can plot the results in the format produced by @code{mu:tabulate} with the
@t{(mu plot)} module, an experimental module that requires the
@t{gnuplot}@footnote{@url{http://www.gnuplot.info/}} program to be installed
on your system.
The @code{mu:plot} function takes the following arguments:
@code{(mu:plot <data> <title> <x-label> <y-label> [<want-ascii>])}
Here, @code{<data>} is a table of data in the format that @code{mu:tabulate}
produces. @code{<title>}, @code{<x-label>} and @code{<y-lablel>} are,
respectively, the title of the graph, and the labels for X- and
Y-axis. Finally, if you pass @t{#t} for the final @code{<want-ascii>}
parameter, a plain-text rendering of the graph will be produced; otherwise, a
graphical window will be shown.
An example should clarify how this works in practice; let's plot the number of
message per hour:
@lisp @lisp
#!/bin/sh #!/bin/sh
exec guile -s $0 $@ exec guile -s $0 $@
@ -714,10 +741,10 @@ exec guile -s $0 $@
(sort (sort
(mu:tabulate-messages (mu:tabulate-messages
(lambda (msg) (lambda (msg)
(tm:hour (localtime (date msg))))) (tm:hour (localtime (mu:date msg)))))
(lambda (x y) (< (car x) (car y))))) (lambda (x y) (< (car x) (car y)))))
(mu:plot-ascii (mail-per-hour-table) "Mail per hour" "Hour" "Frequency") (mu:plot (mail-per-hour-table) "Mail per hour" "Hour" "Frequency" #t)
@end lisp @end lisp
@verbatim @verbatim