* merge branch 'master' of github.com:djcb/mu

This commit is contained in:
djcb 2013-06-13 23:56:35 +03:00
commit fa514435ea
20 changed files with 403 additions and 206 deletions

View File

@ -28,7 +28,7 @@ Documentation License.''
@titlepage
@title @t{mu-guile} - extending @t{mu} with Guile Scheme
@subtitle{version @value{mu-version}}
@subtitle version @value{mu-version}
@author Dirk-Jan C. Binnema
@c The following two commands start the copyright page.
@ -59,7 +59,7 @@ Welcome to @t{mu-guile}!
@t{mu} is a program for indexing and searching your e-mails. It can search
your messages in many different ways, but sometimes that may not be
enough. If you have very specific queries, or want do generate some
statistics, you need some more power.
statistics, you need some more power.
@t{mu-guile} is made for those cases. @t{mu-guile} exposes the internals of
@t{mu} and its database to the @t{guile} programming language. Guile is the
@ -80,6 +80,7 @@ Trust me, it's not very hard -- and it it's @emph{fun}!
* Attachments and other parts::
* Statistics::
* Plotting data::
* Writing scripts::
Appendices
@ -94,21 +95,22 @@ Appendices
* Making sure it works::
@end menu
This chapter walks you through the installation and the basic setup.
This chapter walks you through the installation and the basic setup.
@node Installation
@section Installation
@t{mu-guile} is part of @t{mu} - by installing the latter, the former is
installed as well. At the time of writing, there are no distribution-provided
packaged versions of @t{mu-guile}; so for now, you need to follow the steps
below.
necessarily installed as well. At the time of writing, there are no
distribution-provided packaged versions of @t{mu-guile}; so for now, you need
to follow the steps below.
@subsection Guile 2.x
@t{mu-guile} is built automatically when @t{mu} is built, if you have
@t{guile} installed (@t{mu} checks for this during @t{configure}). Thus, the
first step is to ensure you have @t{guile} installed.
@t{guile} version 2 or higher. (@t{mu} checks for this during
@t{configure}). Thus, the first step is to ensure you have @t{guile}
installed.
On Debian/Ubuntu you can install @t{guile} 2.x using the @t{guile-2.0-dev}
package (and its dependencies):
@ -116,7 +118,7 @@ package (and its dependencies):
$ sudo apt-get install guile-2.0-dev
@end example
At the time of write, there are no official packages for
At the time of writing, there are no official packages for
Fedora@footnote{@url{https://bugzilla.redhat.com/show_bug.cgi?id=678238}}. If
you are using Fedora or any other system that does not have packages, you need
to compile @t{guile} from
@ -139,9 +141,6 @@ $ sudo yum install gnuplot
@subsection mu
At the time of writing, there are no distribution packages for @t{mu-guile},
so we are assuming installation from source packages.
Assuming @t{guile} 2.x is installed correctly, @t{mu} finds it during its
@t{configure}-stage, and creates @t{mu-guile}. Building @t{mu} follows the
normal steps -- please see the @t{mu} documentation for the details.
@ -227,7 +226,7 @@ Now, copy-paste the following after the prompt:
@noindent
After pressing @key{Enter}, you should get a list of all subjects of messages
that match @t{hello}:
that match @t{hello}:
@verbatim
...
@ -240,7 +239,7 @@ Subject: When all is lost
@noindent
If all this works, congratulations! @t{mu-guile} is installed now, ready to
serve your every whim!
serve your every searching need!
@node Initializing mu-guile
@chapter Initializing mu-guile
@ -269,7 +268,7 @@ This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.
Enter `,help' for help.
scheme@(guile-user)>
scheme@(guile-user)>
@end verbatim
@end cartouche
@ -293,7 +292,7 @@ scheme@(guile-user)> (mu:initialize)
This opens the database for reading, using the default location of
@file{~/.mu}@footnote{If you keep your @t{mu} database in a non-standard
place, use @code{(mu:initialize "/path/to/my/mu/")}}
Now, @t{mu-guile} is ready to go. In the next chapter, we go through the
modules and show what you can do with them.
@ -311,15 +310,15 @@ In this chapter, we discuss searching messages and doing things with them.
@node Finding messages
@section Finding messages
Now we are ready to retrieve some messages from the system. There are two main
functions to do this:
procedures to do this:
@itemize
@item @code{(mu:message-list [<search-expression>])}
@item @code{(mu:for-each-message <function> [<search-expression>])}
@item @code{(mu:for-each-message <procedure> [<search-expression>])}
@end itemize
@noindent
The first function, @code{mu:message-list} returns a list of all messages
The first procedure, @code{mu:message-list} returns a list of all messages
matching @t{<search-expression>}; if you leave @t{<search-expression>} out, it
returns @emph{all} messages. For example, to get all messages with @t{coffee}
in the subject line:
@ -333,7 +332,7 @@ $1 = (#<<mu:message> 9040640> #<<mu:message> 9040630>
@noindent
Apparently, we have three messages matching @t{subject:coffee}, so we get a
list of three @code{<mu:message>} objects. Let's just use the
@code{mu:subject} function ('method') provided by @code{<mu:message>} objects
@code{mu:subject} procedure ('method') provided by @code{<mu:message>} objects
to retrieve the subject-field (more about methods in the next section).
For your convenience, @t{guile} has saved the result of our last query in a
@ -346,8 +345,8 @@ $2 = "Re: best coffee ever!"
@end verbatim
@noindent
The second function we mentioned, @code{mu:for-each-message}, executes some
function for each message matched by the search expression (or @emph{all}
The second procedure we mentioned, @code{mu:for-each-message}, executes some
procedure for each message matched by the search expression (or @emph{all}
messages if the search expression is omitted):
@verbatim
@ -380,7 +379,7 @@ Now that we've seen how to retrieve lists of message objects
@code{<mu:message>} defines the following methods that all 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 procedures here - for the details about various flags /
properties, please refer to the @t{mu-find} man-page.
@itemize
@ -472,19 +471,19 @@ e-mail corpus.
We can retrieve the sender and recipients of an e-mail message using methods
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
procedures return the list of recipients as a single string; however, often it
is more useful to deal with recipients as separate objects.
@menu
* Contact functions and objects::
* Contact procedures and objects::
* All contacts::
* Utility functions::
* Utility procedures::
* Example - mutt export::
@end menu
@node Contact functions and objects
@section Contact functions and objects
@node Contact procedures and objects
@section Contact procedures and objects
Message objects (@pxref{Messages}) have a method @t{mu:contacts}:
@ -529,17 +528,17 @@ 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
To enable this, there is the procedure @code{mu:for-each-contact}, defined as
@code{(mu:for-each-contact function [search-expression])}.
@code{(mu:for-each-contact procedure [search-expression])}.
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
the database), and execute @t{function} for each of them.
the database), and execute @t{procedure} for each of them.
The @t{function} receives an object of the type @t{<mu:contact-with-stats>},
The @t{procedure} receives an object of the type @t{<mu:contact-with-stats>},
which is a @emph{subclass} of the @t{<mu:contact>} class discussed in
@xref{Contact functions and objects}. @t{<mu:contact-with-stats>} objects
@xref{Contact procedures and objects}. @t{<mu:contact-with-stats>} objects
expose the following additional methods:
@itemize
@ -553,11 +552,11 @@ The method assumes an e-mail address is unique for a certain contact; if a
certain e-mail address occurs with different names, it uses the most recent
non-empty name.
@node Utility functions
@section Utility functions
@node Utility procedures
@section Utility procedures
To make dealing with contacts even easier, there are a number of utility
functions that can save you a bit of typing.
procedures 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
@ -578,8 +577,8 @@ something like:
alias <nick> [<name>] "<" <email> ">"
@end verbatim
Anyway, there is the function @code{(mu:contact->string <mu:contact> format)}
that we can use to do the conversion.
@t{mu guile} provides the procedure @code{(mu:contact->string <mu:contact>
format)} that we can use to do the conversion.
We may want to focus on people with whom we have frequent correspondence; so
we may want to limit ourselves to people we have seen at least 10 times in the
@ -654,7 +653,7 @@ if there is none.
Then, we may want to save the part to a file; this can be done using either:
@itemize
@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 procedure of (user-id,
msg-path, part-index)}
@item @code{(mu:save-as <part> <path>)} - save part to file at path
@end itemize
@ -703,7 +702,7 @@ probably be a bit more elegant.
@node Statistics
@chapter Statistics
@t{mu-guile} offers some convenience functions to determine various statistics
@t{mu-guile} offers some convenience procedures to determine various statistics
about the messages in the database.
@menu
@ -743,16 +742,16 @@ scheme@@(guile-user)> ;; subject length
scheme@@(guile-user)> (mu:correl mu:size (lambda (msg)
(string-length (mu:subject msg))) "subject:hello")
$5 = -0.10804368622292
scheme@@(guile-user)>
scheme@@(guile-user)>
@end example
@node Tabulating values
@section Tabulating values
@code{(mu:tabulate <function> [<search-expr>])} applies @t{<function>} to each
@code{(mu:tabulate <procedure> [<search-expr>])} applies @t{<procedure>} to each
message matching @t{<search-expr>} (leave empty to 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. For fields that contain lists
results of @t{<procedure>} and their frequencies. For fields that contain lists
of values (such as address-fields), each of the values in the list is added
separately.
@ -785,7 +784,7 @@ exec guile -s $0 $@
@end lisp
The function @code{weekday-table} uses @code{mu:tabulate-message} to get the
The procedure @code{weekday-table} uses @code{mu:tabulate-message} to get the
frequencies per hour -- this returns a list of pairs:
@verbatim
((5 . 2339) (0 . 2278) (4 . 2800) (2 . 3184) (6 . 1856) (3 . 2833) (1 . 2993))
@ -826,10 +825,10 @@ something like this:
(sort
(mu:tabulate mu:subject)
(lambda (a b) (> (cdr a) (cdr b))))
10)
10)
@end lisp
If this is not short enough, @t{mu-guile} offers a convenience function to do
If this is not short enough, @t{mu-guile} offers a convenience procedure to do
this: @code{mu:top-n-most-frequent}. For example, to get the top-10 people we
sent mail to most often:
@ -848,7 +847,7 @@ You can plot the results in the format produced by @code{mu:tabulate} with the
@t{gnuplot}@footnote{@url{http://www.gnuplot.info/}} program to be installed
on your system.
The @code{mu:plot-histogram} function takes the following arguments:
The @code{mu:plot-histogram} procedure takes the following arguments:
@code{(mu:plot-histogram <data> <title> <x-label> <y-label> [<want-ascii>])}
@ -908,6 +907,57 @@ Frequency
@end verbatim
@end cartouche
@node Writing scripts
@chapter Writing scripts
The @t{mu} program has built-in support for running guile-scripts, and comes
with a number of examples.
You can get a list of all scripts with the @t{mu script} command:
@verbatim
$ mu script
Available scripts (use --verbose for details):
* find-dups: find duplicate messages
* msgs-count: count the number of messages matching some query
* msgs-per-day: graph the number of messages per day
* msgs-per-hour: graph the number of messages per hour
* msgs-per-month: graph the number of messages per month
* msgs-per-year: graph the number of messages per year
* msgs-per-year-month: graph the number of messages per year-month
@end verbatim
You can then execute such a script by its name:
@verbatim
$ mu msgs-per-month --textonly --query=hello
Messages per month matching hello
240 ++-+-----+----+-----+-----+-----+----+-----+-----+-----+----+-----+-++
| + + + + "/tmp/filewi9H0N" using 2:xticlabels(1) ****** |
220 ++ * * ******
| * * * *
200 ++ * * * +*
| * * * *
180 ++ ****** * * * +*
| * * * * * *
160 ****** * * * * * +*
* * * * * * * *
* ******* * * * * ****** * *
140 *+ ** * * * * * * ******** +*
* ** ******* * * * * * ** ** *
120 *+ ** ** ******* * * * * ** ** +*
* ** ** ** * * * ******* ** ** *
100 *+ ** ** ** * * * * ** ** ** +*
* + ** + ** + ** + * + * + + * + * + ** + ** + ** + *
80 **********************************************************************
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Month
@end verbatim
Please refer to the @t{mu-script} man-page for some details on writing your
own scripts.
@node GNU Free Documentation License
@appendix GNU Free Documentation License

View File

@ -277,7 +277,6 @@ either \"org-contact\", \"mutt-alias\", \"mutt-ab\",
"\""))
(else (error "Unsupported format")))))
;; message parts

View File

@ -22,7 +22,8 @@ EXTRA_DIST= \
msgs-per-hour.scm \
msgs-per-month.scm \
msgs-per-day.scm \
msgs-per-year-month.scm
msgs-per-year-month.scm \
find-dups.scm
muguiledistscriptdir = $(pkgdatadir)/scripts/
muguiledistscript_SCRIPTS = $(EXTRA_DIST)

97
guile/scripts/find-dups.scm Executable file
View File

@ -0,0 +1,97 @@
#!/bin/sh
exec guile -e main -s $0 $@
!#
;;
;; Copyright (C) 2013 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.
;; INFO: find duplicate messages
;; INFO: options:
;; INFO: --muhome=<muhome>: path to mu home dir
(use-modules (mu) (mu script) (mu stats))
(use-modules (ice-9 getopt-long) (ice-9 optargs)
(ice-9 popen) (ice-9 format) (ice-9 rdelim))
(define (md5sum path)
(let* ((port (open-pipe* OPEN_READ "md5sum" path))
(md5 (read-delimited " " port)))
(close-pipe port)
md5))
(define (find-dups)
(let ((id-table (make-hash-table 20000)))
;; fill the hash with <msgid-size> => <list of paths>
(mu:for-each-message
(lambda (msg)
(let* ((id (format #f "~a-~d" (mu:message-id msg)
(mu:size msg)))
(lst (hash-ref id-table id)))
(if lst
(set! lst (cons (mu:path msg) lst))
(set! lst (list (mu:path msg))))
(hash-set! id-table id lst))))
;; list all the paths with multiple elements; check the md5sum to
;; make 100%-minus-ε sure they are really the same file.
(hash-for-each
(lambda (id paths)
(if (> (length paths) 1)
(let ((hash (make-hash-table 10)))
(for-each
(lambda (path)
(let* ((md5 (md5sum path)) (lst (hash-ref hash md5)))
(if lst
(set! lst (cons path lst))
(set! lst (list path)))
(hash-set! hash md5 lst)))
paths)
;; hash now maps the md5sum to the messages...
(hash-for-each
(lambda (md5 mpaths)
(if (> (length mpaths) 1)
(begin
(format #t "md5sum: ~a:\n" md5)
(let ((num 1))
(for-each
(lambda (path)
(format #t "\t~d. ~a\n" num path)
(set! num (+ 1 num)))
mpaths)))))
hash))))
id-table)))
(define (main args)
"Run some statistics function.
Interpret argument-list ARGS (like command-line
arguments). Possible arguments are:
--muhome (path to alternative mu home directory)."
(setlocale LC_ALL "")
(let* ((optionspec '( (muhome (value #t))
(help (single-char #\h) (value #f))))
(options (getopt-long args optionspec))
(help (option-ref options 'help #f))
(muhome (option-ref options 'muhome #f)))
(mu:initialize muhome)
(find-dups)))
;; Local Variables:
;; mode: scheme
;; End:

View File

@ -520,7 +520,7 @@ count_colons (const char *str)
static MuMsgIterThreadInfo*
thread_info_new (gchar *threadpath, gboolean root, gboolean child,
thread_info_new (gchar *threadpath, gboolean root, gboolean first_child,
gboolean empty_parent, gboolean has_child, gboolean is_dup)
{
MuMsgIterThreadInfo *ti;
@ -529,9 +529,9 @@ thread_info_new (gchar *threadpath, gboolean root, gboolean child,
ti->threadpath = threadpath;
ti->level = count_colons (threadpath); /* hacky... */
ti->prop = 0;
ti->prop = MU_MSG_ITER_THREAD_PROP_NONE;
ti->prop |= root ? MU_MSG_ITER_THREAD_PROP_ROOT : 0;
ti->prop |= child ? MU_MSG_ITER_THREAD_PROP_FIRST_CHILD : 0;
ti->prop |= first_child ? MU_MSG_ITER_THREAD_PROP_FIRST_CHILD : 0;
ti->prop |= empty_parent ? MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT : 0;
ti->prop |= is_dup ? MU_MSG_ITER_THREAD_PROP_DUP : 0;
ti->prop |= has_child ? MU_MSG_ITER_THREAD_PROP_HAS_CHILD : 0;

View File

@ -159,6 +159,8 @@ gboolean mu_msg_iter_calculate_threads (MuMsgIter *iter);
enum _MuMsgIterThreadProp {
MU_MSG_ITER_THREAD_PROP_NONE = 0 << 0,
MU_MSG_ITER_THREAD_PROP_ROOT = 1 << 0,
MU_MSG_ITER_THREAD_PROP_FIRST_CHILD = 1 << 1,
MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT = 1 << 2,

View File

@ -438,7 +438,6 @@ mu_util_locale_is_utf8 (void)
gboolean
mu_util_fputs_encoded (const char *str, FILE *stream)
{
char *conv;
int rv;
g_return_val_if_fail (str, FALSE);
@ -449,10 +448,14 @@ mu_util_fputs_encoded (const char *str, FILE *stream)
rv = fputs (str, stream);
else { /* charset is _not_ utf8, so we actually have to
* convert it..*/
GError *err;
unsigned bytes;
err = NULL;
GError *err;
unsigned bytes;
char *conv;
err = NULL;
conv = g_locale_from_utf8 (str, -1, (gsize*)&bytes, NULL, &err);
if (!conv || err) {
/* conversion failed; this happens because is
* some cases GMime may gives us non-UTF-8
@ -462,14 +465,15 @@ mu_util_fputs_encoded (const char *str, FILE *stream)
g_warning ("%s: g_locale_from_utf8 failed: %s",
__FUNCTION__,
err ? err->message : "conversion failed");
g_clear_error (&err);
g_free (conv);
conv = g_strescape (str, NULL);
}
g_clear_error (&err);
rv = fputs (conv, stream);
g_free (conv);
}
}
return (rv == EOF) ? FALSE : TRUE;
}

View File

@ -1,4 +1,4 @@
.TH MU FIND 1 "December 2012" "User Manuals"
.TH MU FIND 1 "June 2013" "User Manuals"
.SH NAME
@ -53,9 +53,9 @@ messages.
A wildcard search is a search where a \fB*\fR matches the last \fIn\fR
character(s) in some string. The string must always start with one or more
characters before the wildcards. Since version 0.9.6, \fBmu\fR also supports
wildcard searches for all fields except maildirs and paths. So, to get all
mails with a subject containing a word starting with \fBcom\fR, you can use:
characters before the wildcard. \fBmu\fR supports wildcard searches for all
fields except maildirs and paths. To get all mails with a subject containing a
word starting with \fBcom\fR, you can use:
.nf
$ mu find 'subject:com*'
@ -68,9 +68,6 @@ remember that the '*' invokes the wildcard search only when used as the
rightmost character of a search term. Furthermore, it is \fBnot\fR a regular
expression.
In older versions of mu, queries were logged in \fI<mu-home>/mu.log\fR;
however, since version 0.9, mu no longer does this.
The basic way to search a message is to type some words matching it, as you
would do in an internet search engine. For example,
@ -656,7 +653,7 @@ non-zero return value, for example:
Please report bugs if you find them:
.BR http://code.google.com/p/mu0/issues/list
If you have specific messages which are not matched correctly, please attach
them (appropriately censored of course).
them (appropriately censored if needed).
.SH AUTHOR

View File

@ -1,32 +1,31 @@
.TH MU SCRIPT 1 "March 2013" "User Manuals"
.TH MU SCRIPT 1 "June 2013" "User Manuals"
.SH NAME
mu script\- run a mu script
mu script\- show the available mu scripts, and run them.
.SH SYNOPSIS
.B mu script [options] [--script=<script>] [<pattern>] [-- [script-options]]
.B mu script [options] [<pattern>]
.B mu <script-name> [<script-options>]
.SH DESCRIPTION
\fBmu script\fR is the \fBmu\fR command to list available \fBmu\fR scripts,
and run them. The scripts are implemented in the Guile programming language,
and thus only work if your \fBmu\fR is built with support for Guile. In
\fBmu script\fR is the \fBmu\fR command to list available \fBmu\fR scripts.
The scripts are to be implemented in the Guile programming language, and
therefore only work if your \fBmu\fR is built with support for Guile. In
addition, many scripts require you to have \fBgnuplot\fR installed.
Without any parameters, \fBmu script\fR lists the available scripts. If you
provide a pattern (regular expression), only the scripts whose name or
one-line description match this pattern, are listed. See the examples below.
provide a pattern (a regular expression), only the scripts whose name or
one-line description match this pattern are listed. See the examples below.
\fBmu\fR ships with a number of scripts.
.SH OPTIONS
.TP
\fB\-\-script=\fR\fI<script>\fR
run the given script.
\fB\-\-verbose\fR,\fB\-v\fR
when listing the available scripts, show the long descriptions.
@ -45,10 +44,10 @@ List all available scripts matching \fImonth\fR (long descriptions):
$ mu script -v month
.fi
Run the \fImsgs-per-month\fR script, and pass it the \fI--textonly\fR
parameter:
Run the \fImsgs-per-month\fR script for messages matching 'hello', and pass it
the \fI--textonly\fR parameter:
.nf
$ mu script --script=msgs-per-month -- --textonly
$ mu msgs-per-month --query=hello --textonly
.fi
.SH RETURN VALUE
@ -60,8 +59,8 @@ code when this is not the case.
You can make your own Scheme scripts accessible through \fBmu script\fR by
putting them in \fI<muhome>/scripts\fR (which is typically
\fI~/.mu/scripts\fR). It is a good idea to document it using some special
comments in the source code:
\fI~/.mu/scripts\fR). It is a good idea to document the scripts by using some
special comments in the source code:
.nf
;; INFO: this is my script -- one-line description
;; INFO: (longer description)

View File

@ -177,9 +177,9 @@ mu_cmd_script (MuConfig *opts, GError **err)
if (err && *err)
goto leave;
if (!opts->script) {
if (g_strcmp0 (opts->cmdstr, "script") == 0) {
print_scripts (scripts, !opts->nocolor, opts->verbose,
opts->params[1], err);
opts->script_params[0], err);
goto leave;
}
@ -191,8 +191,7 @@ mu_cmd_script (MuConfig *opts, GError **err)
}
/* do it! */
mu_script_guile_run (msi, opts->muhome,
(const gchar**)&opts->params[1], err);
mu_script_guile_run (msi, opts->muhome, opts->script_params, err);
leave:
/* this won't be reached, unless there is some error */
mu_script_info_list_destroy (scripts);

View File

@ -367,7 +367,7 @@ mu_cmd_remove (MuStore *store, MuConfig *opts, GError **err)
g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_REMOVE,
MU_ERROR_INTERNAL);
/* note: params[0] will be 'add' */
/* note: params[0] will be 'remove' */
if (!opts->params[0] || !opts->params[1]) {
g_warning ("usage: mu remove <file> [<files>]");
mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS,
@ -593,6 +593,8 @@ set_log_options (MuConfig *opts)
logopts |= MU_LOG_OPTIONS_DEBUG;
}
MuError
mu_cmd_execute (MuConfig *opts, GError **err)
{
@ -627,10 +629,7 @@ mu_cmd_execute (MuConfig *opts, GError **err)
case MU_CONFIG_CMD_SERVER:
merr = with_store (mu_cmd_server, opts, FALSE, err); break;
default:
show_usage ();
mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS,
"unknown command '%s'", opts->cmdstr);
return MU_ERROR_IN_PARAMETERS;
merr = MU_ERROR_IN_PARAMETERS; break;
}
return merr;

View File

@ -300,13 +300,14 @@ config_options_group_script (void)
{
GOptionGroup *og;
GOptionEntry entries[] = {
{"script", 0, 0, G_OPTION_ARG_STRING, &MU_CONFIG.script,
"script to run (see `mu help script')", "<script>"},
{NULL, 0, 0, 0, NULL, NULL, NULL}
{G_OPTION_REMAINING, 0,0, G_OPTION_ARG_STRING_ARRAY,
&MU_CONFIG.params, "script parameters", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}
};
og = g_option_group_new("script", "Options for the 'script' command",
"", NULL, NULL);
g_option_group_add_entries(og, entries);
return og;
@ -461,7 +462,7 @@ cmd_from_string (const char *str)
{ "index", MU_CONFIG_CMD_INDEX },
{ "mkdir", MU_CONFIG_CMD_MKDIR },
{ "remove", MU_CONFIG_CMD_REMOVE },
{ "script", MU_CONFIG_CMD_SCRIPT },
{ "script", MU_CONFIG_CMD_SCRIPT },
{ "server", MU_CONFIG_CMD_SERVER },
{ "verify", MU_CONFIG_CMD_VERIFY },
{ "view", MU_CONFIG_CMD_VIEW }
@ -474,7 +475,8 @@ cmd_from_string (const char *str)
if (strcmp (str, cmd_map[i].name) == 0)
return cmd_map[i].cmd;
return MU_CONFIG_CMD_UNKNOWN;
/* if we don't recognize it, it may be some script */
return MU_CONFIG_CMD_SCRIPT;
}
@ -673,6 +675,17 @@ parse_params (int *argcp, char ***argvp, GError **err)
rv = g_option_context_parse (context, argcp, argvp, err) &&
cmd_help ();
break;
case MU_CONFIG_CMD_SCRIPT:
/* all unknown commands are passed to 'script' */
g_option_context_set_ignore_unknown_options (context, TRUE);
group = get_option_group (MU_CONFIG.cmd);
g_option_context_add_group (context, group);
rv = g_option_context_parse (context, argcp, argvp, err);
MU_CONFIG.script = g_strdup (MU_CONFIG.cmdstr);
/* argvp contains the script parameters */
MU_CONFIG.script_params = (const char**)&((*argvp)[1]);
break;
default:
group = get_option_group (MU_CONFIG.cmd);
if (group)

View File

@ -183,6 +183,7 @@ struct _MuConfig {
* (open) the attmnt using xdgopen */
/* options for mu-script */
gchar *script; /* script to run */
const char **script_params; /* parameters for scripts */
};
typedef struct _MuConfig MuConfig;

View File

@ -142,25 +142,21 @@ mu4e e-mail client.
#BEGIN MU_CONFIG_CMD_SCRIPT
#STRING
mu script [--script=<script>] [<pattern>] [-v] -- [script-options]
mu script [<pattern>] [-v]
mu <script-name> [<script options>]
#STRING
Without any parameter, list the available scripts. With <pattern>,
list only those scripts whose name or one-line description matches it.
With -v, give longer descriptions of each script.
With --script=<script>, run the script whose name is <script>; pass
any arguments to the script after the '--' double-dash.
List the available scripts and/or run them (if mu was built with support for
scripts). With <pattern>, list only those scripts whose name or one-line
description matches it. With -v, get a longer description for each script.
Some examples:
List all available scripts (one-line descriptions):
$ mu script
List all available scripts matching 'month' (long descriptions):
$ mu script -v month
Run the 'msgs-per-month' script, and pass it the '--textonly' parameter:
$ mu script --script=msgs-per-month -- --textonly
(as mentioned, parameters to the script follow the '--')
$ mu msgs-per-month --textonly
#END

View File

@ -192,11 +192,16 @@ If needed, set the Fcc header, and register the handler function."
(defconst mu4e~compose-hidden-headers
(defvar mu4e-compose-hidden-headers
`("^References:" "^Face:" "^X-Face:"
"^X-Draft-From:" "^User-agent:")
"Hidden headers when composing.")
(defun mu4e~compose-hide-headers ()
"Hide the headers as per `mu4e-compose-hidden-headers'."
(let ((message-hidden-headers mu4e-compose-hidden-headers))
(message-hide-headers)))
(defconst mu4e~compose-address-fields-regexp
"^\\(To\\|B?Cc\\|Reply-To\\|From\\):")
@ -211,8 +216,7 @@ appear on disk."
(mu4e~compose-set-friendly-buffer-name)
(mu4e~draft-insert-mail-header-separator)
;; hide some headers again
(let ((message-hidden-headers mu4e~compose-hidden-headers))
(message-hide-headers))
(mu4e~compose-hide-headers)
(set-buffer-modified-p nil)
;; update the file on disk -- ie., without the separator
(mu4e~proc-add (buffer-file-name) mu4e~draft-drafts-folder)) nil t))
@ -255,7 +259,7 @@ appear on disk."
(define-derived-mode mu4e-compose-mode message-mode "mu4e:compose"
"Major mode for the mu4e message composition, derived from `message-mode'.
\\{message-mode-map}."
(let ((message-hidden-headers mu4e~compose-hidden-headers))
(progn
(use-local-map mu4e-compose-mode-map)
(make-local-variable 'message-default-charset)
;; if the default charset is not set, use UTF-8
@ -279,7 +283,7 @@ appear on disk."
(define-key mu4e-compose-mode-map (kbd "C-S-u") 'mu4e-update-mail-and-index)
(define-key mu4e-compose-mode-map (kbd "C-c C-u") 'mu4e-update-mail-and-index)
;; setup the fcc-stuff, if needed
(add-hook 'message-send-hook
(defun mu4e~compose-save-before-sending ()
@ -293,7 +297,7 @@ appear on disk."
(setq mu4e-sent-func 'mu4e-sent-handler)
(mu4e~proc-sent (buffer-file-name) mu4e~draft-drafts-folder)) nil))
;; mark these two hooks as permanent-local, so they'll survive mode-changes
;; (put 'mu4e~compose-save-before-sending 'permanent-local-hook t)
;; (put 'mu4e~compose-save-before-sending 'permanent-local-hook t)
(put 'mu4e~compose-mark-after-sending 'permanent-local-hook t))
(defconst mu4e~compose-buffer-max-name-length 30
@ -312,12 +316,7 @@ appear on disk."
(truncate-string-to-width str
mu4e~compose-buffer-max-name-length
nil nil t)))))
(defconst mu4e~compose-hidden-headers
'("^References:" "^Face:" "^X-Face:" "^X-Draft-From:"
"^User-Agent:" "^In-Reply-To:")
"List of regexps with message headers that are to be hidden.")
(defun mu4e~compose-handler (compose-type &optional original-msg includes)
"Create a new draft message, or open an existing one.
@ -371,8 +370,7 @@ tempfile)."
(set (make-local-variable 'mu4e-compose-parent-message) original-msg)
(put 'mu4e-compose-parent-message 'permanent-local t)
;; hide some headers
(let ((message-hidden-headers mu4e~compose-hidden-headers))
(message-hide-headers))
(mu4e~compose-hide-headers)
;; switch on the mode
(mu4e-compose-mode))

View File

@ -102,7 +102,7 @@ e-mail addresses. If LST is nil, returns nil."
(let ((name (car addrcell))
(email (cdr addrcell)))
(if name
(format "\"%s\" <%s>" name email)
(format "%s <%s>" (mu4e~rfc822-quoteit name) email)
(format "%s" email))))
lst ", ")))

View File

@ -1,6 +1,6 @@
;; mu4e-mark.el -- part of mu4e, the mu mail user agent
;;
;; Copyright (C) 2011-2012 Dirk-Jan C. Binnema
;; Copyright (C) 2011-2013 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
@ -82,6 +82,24 @@ where
"Clear the marks subsystem."
(clrhash mu4e~mark-map))
(defmacro mu4e~mark-in-context (&rest body)
"Evaluate BODY in the context of the headers buffer in case this
is either a headers or view buffer, and "
`(cond
((eq major-mode 'mu4e-headers-mode) ,@body)
((eq major-mode 'mu4e-view-mode)
(if (buffer-live-p mu4e~view-headers-buffer)
(let* ((msg (mu4e-message-at-point))
(docid (mu4e-message-field msg :docid)))
(with-current-buffer mu4e~view-headers-buffer
(if (mu4e~headers-goto-docid docid)
,@body
(mu4e-error "cannot find message in headers buffer."))))
(mu4e-error "no headers buffer connected to view")))
(t (progn (mu4e-message "%S" major-mode) ,@body))))
(defun mu4e-mark-at-point (mark &optional target)
"Mark (or unmark) message at point.
MARK specifies the mark-type. For `move'-marks and `trash'-marks
@ -229,19 +247,19 @@ as well."
If there are such marks, replace them with a _real_ mark (ask the
user which one)."
(interactive)
(let ((markpair))
(maphash
(lambda (docid val)
(let ((mark (car val)) (target (cdr val)))
(when (eql mark 'something)
(unless markpair
(setq markpair
(mu4e~mark-get-markpair "Set deferred mark(s) to: " nil)))
(save-excursion
(when (mu4e~headers-goto-docid docid)
(mu4e-mark-set (car markpair) (cdr markpair)))))))
mu4e~mark-map)))
(mu4e~mark-in-context
(let ((markpair))
(maphash
(lambda (docid val)
(let ((mark (car val)) (target (cdr val)))
(when (eql mark 'something)
(unless markpair
(setq markpair
(mu4e~mark-get-markpair "Set deferred mark(s) to: " nil)))
(save-excursion
(when (mu4e~headers-goto-docid docid)
(mu4e-mark-set (car markpair) (cdr markpair)))))))
mu4e~mark-map))))
(defun mu4e~mark-check-target (target)
"Check if the target exists if not, offer to create it."
@ -264,46 +282,48 @@ work well.
If NO-CONFIRMATION is non-nil, don't ask user for confirmation."
(interactive)
(let ((marknum (hash-table-count mu4e~mark-map)))
(if (zerop marknum)
(message "Nothing is marked")
(mu4e-mark-resolve-deferred-marks)
(when (or no-confirmation
(y-or-n-p
(format "Are you sure you want to execute %d mark%s?"
marknum (if (> marknum 1) "s" ""))))
(maphash
(lambda (docid val)
(let ((mark (car val)) (target (cdr val)))
;; note: whenever you do something with the message,
;; it looses its N (new) flag
(case mark
(refile (mu4e~proc-move docid (mu4e~mark-check-target target) "-N"))
(delete (mu4e~proc-remove docid))
(flag (mu4e~proc-move docid nil "+F-u-N"))
(move (mu4e~proc-move docid (mu4e~mark-check-target target) "-N"))
(read (mu4e~proc-move docid nil "+S-u-N"))
(trash (mu4e~proc-move docid (mu4e~mark-check-target target) "+T-N"))
(unflag (mu4e~proc-move docid nil "-F-N"))
(unread (mu4e~proc-move docid nil "-S+u-N"))
(otherwise (mu4e-error "Unrecognized mark %S" mark)))))
mu4e~mark-map))
(mu4e-mark-unmark-all)
(message nil))))
(mu4e~mark-in-context
(let ((marknum (hash-table-count mu4e~mark-map)))
(if (zerop marknum)
(message "Nothing is marked")
(mu4e-mark-resolve-deferred-marks)
(when (or no-confirmation
(y-or-n-p
(format "Are you sure you want to execute %d mark%s?"
marknum (if (> marknum 1) "s" ""))))
(maphash
(lambda (docid val)
(let ((mark (car val)) (target (cdr val)))
;; note: whenever you do something with the message,
;; it looses its N (new) flag
(case mark
(refile (mu4e~proc-move docid (mu4e~mark-check-target target) "-N"))
(delete (mu4e~proc-remove docid))
(flag (mu4e~proc-move docid nil "+F-u-N"))
(move (mu4e~proc-move docid (mu4e~mark-check-target target) "-N"))
(read (mu4e~proc-move docid nil "+S-u-N"))
(trash (mu4e~proc-move docid (mu4e~mark-check-target target) "+T-N"))
(unflag (mu4e~proc-move docid nil "-F-N"))
(unread (mu4e~proc-move docid nil "-S+u-N"))
(otherwise (mu4e-error "Unrecognized mark %S" mark)))))
mu4e~mark-map))
(mu4e-mark-unmark-all)
(message nil)))))
(defun mu4e-mark-unmark-all ()
"Unmark all marked messages."
(interactive)
(when (or (null mu4e~mark-map) (zerop (hash-table-count mu4e~mark-map)))
(mu4e-warn "Nothing is marked"))
(maphash
(lambda (docid val)
(save-excursion
(when (mu4e~headers-goto-docid docid)
(mu4e-mark-set 'unmark))))
mu4e~mark-map)
;; in any case, clear the marks map
(mu4e~mark-clear))
(mu4e~mark-in-context
(when (or (null mu4e~mark-map) (zerop (hash-table-count mu4e~mark-map)))
(mu4e-warn "Nothing is marked"))
(maphash
(lambda (docid val)
(save-excursion
(when (mu4e~headers-goto-docid docid)
(mu4e-mark-set 'unmark))))
mu4e~mark-map)
;; in any case, clear the marks map
(mu4e~mark-clear)))
(defun mu4e-mark-docid-marked-p (docid)
"Is the given docid marked?"
@ -313,25 +333,27 @@ If NO-CONFIRMATION is non-nil, don't ask user for confirmation."
(defun mu4e-mark-marks-num ()
"Return the number of marks in the current buffer."
(if mu4e~mark-map (hash-table-count mu4e~mark-map) 0))
(defun mu4e-mark-handle-when-leaving ()
"If there are any marks in the current buffer, handle those
according to the value of `mu4e-headers-leave-behavior'. This
function is to be called before any further action (like searching,
quiting the buffer) is taken; returning t means 'take the following
action', return nil means 'don't do anything'"
(let ((marknum (mu4e-mark-marks-num))
(what mu4e-headers-leave-behavior))
(unless (zerop marknum) ;; nothing to do?
(when (eq what 'ask)
(setq what (mu4e-read-option
(format "There are %d existing mark(s); should we: " marknum)
'( ("apply marks" . apply)
("ignore marks?" . ignore)))))
;; we determined what to do... now do it
(when (eq what 'apply)
(mu4e-mark-execute-all t)))))
action', return nil means 'don't do anything'."
(mu4e~mark-in-context
(let ((marknum (mu4e-mark-marks-num))
(what mu4e-headers-leave-behavior))
(unless (zerop marknum) ;; nothing to do?
(when (eq what 'ask)
(setq what (mu4e-read-option
(format "There are %d existing mark(s); should we: " marknum)
'( ("apply marks" . apply)
("ignore marks?" . ignore)))))
;; we determined what to do... now do it
(when (eq what 'apply)
(mu4e-mark-execute-all t))))))
(provide 'mu4e-mark)
;; End of mu4e-mark.el

View File

@ -553,13 +553,14 @@ process."
(defun mu4e~rfc822-phrase-type (ph)
"Return either atom, quoted-string, a corner-case or nil. This
checks for quotes around the phrase first
checks for empty string first. Then quotes around the phrase
(returning 'rfc822-quoted-string). Then whether there is a quote
inside the phrase (returning 'rfc822-containing-quote).
The reverse of the RFC atext definition is then tested.
If it matches, nil is returned, if not, it is an 'rfc822-atom, which
is returned."
(cond
((= (length ph) 0) 'rfc822-empty)
((= (aref ph 0) ?\")
(if (string-match "\"\\([^\"\\\n]\\|\\\\.\\|\\\\\n\\)*\"" ph)
'rfc822-quoted-string

View File

@ -780,10 +780,10 @@ Also number them so they can be opened using `mu4e-view-go-to-url'."
(defmacro mu4e~view-in-headers-context (&rest body)
"Evaluate BODY in the current headers buffer, with moved to the
current message."
"Evaluate BODY in the context of the headers buffer connected to
this view."
`(progn
(unless '(buffer-live-p mu4e~view-headers-buffer)
(unless (buffer-live-p mu4e~view-headers-buffer)
(mu4e-error "no headers-buffer connected"))
(let* ((msg (mu4e-message-at-point))
(docid (mu4e-message-field msg :docid)))

View File

@ -732,7 +732,7 @@ respectively. In the example, we use @code{:human-date}, which shows when the
time when the message was sent today, and the date otherwise.
@item The header field used for sorting is indicated by ``@t{V}'' or
``@t{^}''@footnote{or you can use little graphical triangles; see variable
@code{mu4e-use-fancy-chars}}, indicating the sort order (descending or
@code{mu4e-use-fancy-chars}}, corresponding to the sort order (descending or
ascending, respectively). You can influence this by a mouse click, or
@key{O}. Not all fields allow sorting.
@item Instead of showing the @t{From:} and @t{To:} fields separately, you
@ -757,7 +757,7 @@ u=@emph{unread}. The tooltip for this field also contains this information.
Jamie Zawinski's mail threading algorithm,
@url{http://www.jwz.org/doc/threading.html}}.
@item The headers view is @emph{automatically updated} if any changes are
found during the indexing process, and if there is not current
found during the indexing process, and if there is no current
user-interaction. If you do not want such automatic updates, set
@code{mu4e-headers-auto-update} to @code{nil}.
@end itemize
@ -1134,8 +1134,8 @@ is used for images.
@section Displaying rich-text messages
@t{mu4e} normally prefers the plain-text version for messages that consist of
both a plain-text and html (rich-text) versions of the body-text. You change
this by setting @code{mu4e-view-prefer-html} to @t{t}.
both a plain-text and html (rich-text) versions of the body-text. You can
change this by setting @code{mu4e-view-prefer-html} to @t{t}.
If there is only an html-version, or if the plain-text version is too short in
comparison with the html part@footnote{this is for the case where the
@ -1225,7 +1225,8 @@ For more information, see the @command{mu-verify} manual page.
@section Actions
You can perform custom functions (``actions'') on messages and their
attachments. For a general discussion on how to define your own, see see @ref{Actions}.
attachments. For a general discussion on how to define your own, see see
@ref{Actions}.
@subsection Message actions
@ -2403,16 +2404,17 @@ shortcut character @key{o} is due to the first character of
The @command{emacs}-package @t{sauron}@footnote{Sauron can be found at
@url{https://github.com/djcb/sauron}, or in the Marmalade package-repository
at @url{http://http://marmalade-repo.org/}} (by the same author) can be used
to get notifications about new mails. If you put something like the below
script in your @t{crontab} (or have some other way of having it execute every
@emph{n} minutes) you receive notifications in the sauron-buffer when new
messages arrive.
to get notifications about new mails. If you run something like the below
script from your @t{crontab} (or have some other way of having it execute
every @emph{n} minutes), you receive notifications in the @t{sauron}-buffer
when new messages arrive.
@verbatim
#!/bin/sh
# put the path to your Inbox folder here
# put the path to your Inbox folder here
CHECKDIR="/home/$LOGNAME/Maildir/Inbox"
sauron-msg () {
DBUS_COOKIE="/home/$LOGNAME/.sauron-dbus"
if test "x$DBUS_SESSION_BUS_ADDRESS" = "x"; then
@ -3011,7 +3013,7 @@ normal, synchronous fashion.
@section Known issues
Although they are not really @emph{questions}, we end this chapter with a list
of known issue and/or missing features in @t{mu4e}. Thus, users won't have to
of known issues and/or missing features in @t{mu4e}. Thus, users won't have to
search in vain for things that are not there (yet), and the author can use it
as a todo-list.
@ -3020,11 +3022,24 @@ as a todo-list.
utf-8}; so, if you problems with encodings, be sure to have
@code{(set-language-environment "UTF-8")} in your @file{~/.emacs}.
@item @emph{Thread handling is incomplete.} While threads are calculated and are
visible in the headers buffer, you can not collapse/open them.
visible in the headers buffer, you cannot collapse/open them.
@item @emph{The key-bindings are @emph{somewhat} hard-coded.} That is, the main
menu assumes the default key-bindings, as do the clicks-on-bookmarks.
@item @emph{The @t{emacs} front-end of the @t{notmuch} e-mail indexer
conflicts with @t{mu4e}}. @t{notmuch} running in parallel with
@t{mu4e} leads to
@verbatim
error in process filter: mu4e-error-handler: Error 70: cannot read
~/Maildir/...
@end verbatim
when sending a reply to a new e-mail. This seems to be caused by @t{notmuch}
changing the name of the original message file while @t{mu4e} is working in on
it. To prevent this, deactivate @t{notmuch} in your Emacs setup.
@end itemize
For a more complete list, please refer to the issues-list in the
github-repository.
@node Tips and Tricks
@appendix Tips and Tricks
@ -3195,7 +3210,8 @@ If you have multiple accounts, you can accommodate them as well:
((string-match "Account1" maildir)
(setq folder (or (catch 'found
(dolist (mailing-list my-mu4e-mailing-lists)
(if (mu4e-message-contact-field-matches msg :to (car mailing-list))
(if (mu4e-message-contact-field-matches
msg :to (car mailing-list))
(throw 'found (cdr mailing-list)))))
"/Account1/General")))
((string-match "Gmail" maildir)
@ -3203,7 +3219,8 @@ If you have multiple accounts, you can accommodate them as well:
((string-match "Account2" maildir)
(setq folder (or (cdar (member* subject my-mu4e-subject-alist
:test #'(lambda (x y)
(string-match (car y) x))))
(string-match
(car y) x))))
"/Account2/General"))))
folder))
@end lisp
@ -3216,10 +3233,12 @@ message based on the mailing list to which it was sent. This requires
another variable:
@lisp
(defvar my-mu4e-mailing-lists '(("mu-discuss@@googlegroups.com" . "/Account1/mu4e")
("pandoc-discuss@@googlegroups.com" . "/Account1/Pandoc")
("auctex@@gnu.org" . "/Account1/AUCTeX"))
"List of mailing list addresses and folders where their messages are saved.")
(defvar my-mu4e-mailing-lists
'(("mu-discuss@@googlegroups.com" . "/Account1/mu4e")
("pandoc-discuss@@googlegroups.com" . "/Account1/Pandoc")
("auctex@@gnu.org" . "/Account1/AUCTeX"))
"List of mailing list addresses and folders where
their messages are saved.")
@end lisp
@node Saving outgoing messages