* 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 @titlepage
@title @t{mu-guile} - extending @t{mu} with Guile Scheme @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 @author Dirk-Jan C. Binnema
@c The following two commands start the copyright page. @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 @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 your messages in many different ways, but sometimes that may not be
enough. If you have very specific queries, or want do generate some 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-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 @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:: * Attachments and other parts::
* Statistics:: * Statistics::
* Plotting data:: * Plotting data::
* Writing scripts::
Appendices Appendices
@ -94,21 +95,22 @@ Appendices
* Making sure it works:: * Making sure it works::
@end menu @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 @node Installation
@section Installation @section Installation
@t{mu-guile} is part of @t{mu} - by installing the latter, the former is @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 necessarily installed as well. At the time of writing, there are no
packaged versions of @t{mu-guile}; so for now, you need to follow the steps distribution-provided packaged versions of @t{mu-guile}; so for now, you need
below. to follow the steps below.
@subsection Guile 2.x @subsection Guile 2.x
@t{mu-guile} is built automatically when @t{mu} is built, if you have @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 @t{guile} version 2 or higher. (@t{mu} checks for this during
first step is to ensure you have @t{guile} installed. @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} On Debian/Ubuntu you can install @t{guile} 2.x using the @t{guile-2.0-dev}
package (and its dependencies): package (and its dependencies):
@ -116,7 +118,7 @@ package (and its dependencies):
$ sudo apt-get install guile-2.0-dev $ sudo apt-get install guile-2.0-dev
@end example @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 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 you are using Fedora or any other system that does not have packages, you need
to compile @t{guile} from to compile @t{guile} from
@ -139,9 +141,6 @@ $ sudo yum install gnuplot
@subsection mu @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 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 @t{configure}-stage, and creates @t{mu-guile}. Building @t{mu} follows the
normal steps -- please see the @t{mu} documentation for the details. normal steps -- please see the @t{mu} documentation for the details.
@ -227,7 +226,7 @@ Now, copy-paste the following after the prompt:
@noindent @noindent
After pressing @key{Enter}, you should get a list of all subjects of messages After pressing @key{Enter}, you should get a list of all subjects of messages
that match @t{hello}: that match @t{hello}:
@verbatim @verbatim
... ...
@ -240,7 +239,7 @@ Subject: When all is lost
@noindent @noindent
If all this works, congratulations! @t{mu-guile} is installed now, ready to 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 @node Initializing mu-guile
@chapter 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. under certain conditions; type `,show c' for details.
Enter `,help' for help. Enter `,help' for help.
scheme@(guile-user)> scheme@(guile-user)>
@end verbatim @end verbatim
@end cartouche @end cartouche
@ -293,7 +292,7 @@ scheme@(guile-user)> (mu:initialize)
This opens the database for reading, using the default location of 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 @file{~/.mu}@footnote{If you keep your @t{mu} database in a non-standard
place, use @code{(mu:initialize "/path/to/my/mu/")}} 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 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. 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 @node Finding messages
@section Finding messages @section Finding messages
Now we are ready to retrieve some messages from the system. There are two main Now we are ready to retrieve some messages from the system. There are two main
functions to do this: procedures to do this:
@itemize @itemize
@item @code{(mu:message-list [<search-expression>])} @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 @end itemize
@noindent @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 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} returns @emph{all} messages. For example, to get all messages with @t{coffee}
in the subject line: in the subject line:
@ -333,7 +332,7 @@ $1 = (#<<mu:message> 9040640> #<<mu:message> 9040630>
@noindent @noindent
Apparently, we have three messages matching @t{subject:coffee}, so we get a 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 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). 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 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 @end verbatim
@noindent @noindent
The second function we mentioned, @code{mu:for-each-message}, executes some The second procedure we mentioned, @code{mu:for-each-message}, executes some
function for each message matched by the search expression (or @emph{all} procedure for each message matched by the search expression (or @emph{all}
messages if the search expression is omitted): messages if the search expression is omitted):
@verbatim @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>} defines the following methods that all take a single
@code{<mu:message>} object as a parameter. We won't go into the exact meanings @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. properties, please refer to the @t{mu-find} man-page.
@itemize @itemize
@ -472,19 +471,19 @@ e-mail corpus.
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{mu:from}, @code{mu:to} etc.; @xref{Message methods}. These 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. is more useful to deal with recipients as separate objects.
@menu @menu
* Contact functions and objects:: * Contact procedures and objects::
* All contacts:: * All contacts::
* Utility functions:: * Utility procedures::
* Example - mutt export:: * Example - mutt export::
@end menu @end menu
@node Contact functions and objects @node Contact procedures and objects
@section Contact functions and objects @section Contact procedures and objects
Message objects (@pxref{Messages}) have a method @t{mu:contacts}: 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 @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. 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 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 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 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: expose the following additional methods:
@itemize @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 certain e-mail address occurs with different names, it uses the most recent
non-empty name. non-empty name.
@node Utility functions @node Utility procedures
@section Utility functions @section Utility procedures
To make dealing with contacts even easier, there are a number of utility 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 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 <mu:contact> format)}, which takes a contact and returns a text string with
@ -578,8 +577,8 @@ something like:
alias <nick> [<name>] "<" <email> ">" alias <nick> [<name>] "<" <email> ">"
@end verbatim @end verbatim
Anyway, there is the function @code{(mu:contact->string <mu:contact> format)} @t{mu guile} provides the procedure @code{(mu:contact->string <mu:contact>
that we can use to do the conversion. 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 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 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: Then, we may want to save the part to a file; this can be done using either:
@itemize @itemize
@item @code{(mu:save part <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 procedure of (user-id,
msg-path, part-index)} msg-path, part-index)}
@item @code{(mu: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
@ -703,7 +702,7 @@ probably be a bit more elegant.
@node Statistics @node Statistics
@chapter 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. about the messages in the database.
@menu @menu
@ -743,16 +742,16 @@ scheme@@(guile-user)> ;; subject length
scheme@@(guile-user)> (mu:correl mu:size (lambda (msg) scheme@@(guile-user)> (mu:correl mu:size (lambda (msg)
(string-length (mu:subject msg))) "subject:hello") (string-length (mu:subject msg))) "subject:hello")
$5 = -0.10804368622292 $5 = -0.10804368622292
scheme@@(guile-user)> scheme@@(guile-user)>
@end example @end example
@node Tabulating values @node Tabulating values
@section 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), 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 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 of values (such as address-fields), each of the values in the list is added
separately. separately.
@ -785,7 +784,7 @@ exec guile -s $0 $@
@end lisp @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: 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))
@ -826,10 +825,10 @@ something like this:
(sort (sort
(mu:tabulate mu:subject) (mu:tabulate mu:subject)
(lambda (a b) (> (cdr a) (cdr b)))) (lambda (a b) (> (cdr a) (cdr b))))
10) 10)
@end lisp @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 this: @code{mu:top-n-most-frequent}. For example, to get the top-10 people we
sent mail to most often: 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 @t{gnuplot}@footnote{@url{http://www.gnuplot.info/}} program to be installed
on your system. 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>])} @code{(mu:plot-histogram <data> <title> <x-label> <y-label> [<want-ascii>])}
@ -908,6 +907,57 @@ Frequency
@end verbatim @end verbatim
@end cartouche @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 @node GNU Free Documentation License
@appendix 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"))))) (else (error "Unsupported format")))))
;; message parts ;; message parts

View File

@ -22,7 +22,8 @@ EXTRA_DIST= \
msgs-per-hour.scm \ msgs-per-hour.scm \
msgs-per-month.scm \ msgs-per-month.scm \
msgs-per-day.scm \ msgs-per-day.scm \
msgs-per-year-month.scm msgs-per-year-month.scm \
find-dups.scm
muguiledistscriptdir = $(pkgdatadir)/scripts/ muguiledistscriptdir = $(pkgdatadir)/scripts/
muguiledistscript_SCRIPTS = $(EXTRA_DIST) 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* 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) gboolean empty_parent, gboolean has_child, gboolean is_dup)
{ {
MuMsgIterThreadInfo *ti; MuMsgIterThreadInfo *ti;
@ -529,9 +529,9 @@ thread_info_new (gchar *threadpath, gboolean root, gboolean child,
ti->threadpath = threadpath; ti->threadpath = threadpath;
ti->level = count_colons (threadpath); /* hacky... */ 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 |= 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 |= empty_parent ? MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT : 0;
ti->prop |= is_dup ? MU_MSG_ITER_THREAD_PROP_DUP : 0; ti->prop |= is_dup ? MU_MSG_ITER_THREAD_PROP_DUP : 0;
ti->prop |= has_child ? MU_MSG_ITER_THREAD_PROP_HAS_CHILD : 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 { enum _MuMsgIterThreadProp {
MU_MSG_ITER_THREAD_PROP_NONE = 0 << 0,
MU_MSG_ITER_THREAD_PROP_ROOT = 1 << 0, MU_MSG_ITER_THREAD_PROP_ROOT = 1 << 0,
MU_MSG_ITER_THREAD_PROP_FIRST_CHILD = 1 << 1, MU_MSG_ITER_THREAD_PROP_FIRST_CHILD = 1 << 1,
MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT = 1 << 2, MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT = 1 << 2,

View File

@ -438,7 +438,6 @@ mu_util_locale_is_utf8 (void)
gboolean gboolean
mu_util_fputs_encoded (const char *str, FILE *stream) mu_util_fputs_encoded (const char *str, FILE *stream)
{ {
char *conv;
int rv; int rv;
g_return_val_if_fail (str, FALSE); g_return_val_if_fail (str, FALSE);
@ -449,10 +448,14 @@ mu_util_fputs_encoded (const char *str, FILE *stream)
rv = fputs (str, stream); rv = fputs (str, stream);
else { /* charset is _not_ utf8, so we actually have to else { /* charset is _not_ utf8, so we actually have to
* convert it..*/ * convert it..*/
GError *err;
unsigned bytes; GError *err;
err = NULL; unsigned bytes;
char *conv;
err = NULL;
conv = g_locale_from_utf8 (str, -1, (gsize*)&bytes, NULL, &err); conv = g_locale_from_utf8 (str, -1, (gsize*)&bytes, NULL, &err);
if (!conv || err) { if (!conv || err) {
/* conversion failed; this happens because is /* conversion failed; this happens because is
* some cases GMime may gives us non-UTF-8 * 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", g_warning ("%s: g_locale_from_utf8 failed: %s",
__FUNCTION__, __FUNCTION__,
err ? err->message : "conversion failed"); err ? err->message : "conversion failed");
g_clear_error (&err);
g_free (conv); g_free (conv);
conv = g_strescape (str, NULL); conv = g_strescape (str, NULL);
} }
g_clear_error (&err);
rv = fputs (conv, stream); rv = fputs (conv, stream);
g_free (conv); g_free (conv);
}
}
return (rv == EOF) ? FALSE : TRUE; 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 .SH NAME
@ -53,9 +53,9 @@ messages.
A wildcard search is a search where a \fB*\fR matches the last \fIn\fR 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 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 characters before the wildcard. \fBmu\fR supports wildcard searches for all
wildcard searches for all fields except maildirs and paths. So, to get all fields except maildirs and paths. To get all mails with a subject containing a
mails with a subject containing a word starting with \fBcom\fR, you can use: word starting with \fBcom\fR, you can use:
.nf .nf
$ mu find 'subject:com*' $ 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 rightmost character of a search term. Furthermore, it is \fBnot\fR a regular
expression. 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 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, 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: Please report bugs if you find them:
.BR http://code.google.com/p/mu0/issues/list .BR http://code.google.com/p/mu0/issues/list
If you have specific messages which are not matched correctly, please attach If you have specific messages which are not matched correctly, please attach
them (appropriately censored of course). them (appropriately censored if needed).
.SH AUTHOR .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 .SH NAME
mu script\- run a mu script mu script\- show the available mu scripts, and run them.
.SH SYNOPSIS .SH SYNOPSIS
.B mu script [options] [--script=<script>] [<pattern>] [-- [script-options]] .B mu script [options] [<pattern>]
.B mu <script-name> [<script-options>]
.SH DESCRIPTION .SH DESCRIPTION
\fBmu script\fR is the \fBmu\fR command to list available \fBmu\fR scripts, \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, The scripts are to be implemented in the Guile programming language, and
and thus only work if your \fBmu\fR is built with support for Guile. In therefore only work if your \fBmu\fR is built with support for Guile. In
addition, many scripts require you to have \fBgnuplot\fR installed. addition, many scripts require you to have \fBgnuplot\fR installed.
Without any parameters, \fBmu script\fR lists the available scripts. If you Without any parameters, \fBmu script\fR lists the available scripts. If you
provide a pattern (regular expression), only the scripts whose name or provide a pattern (a regular expression), only the scripts whose name or
one-line description match this pattern, are listed. See the examples below. one-line description match this pattern are listed. See the examples below.
\fBmu\fR ships with a number of scripts. \fBmu\fR ships with a number of scripts.
.SH OPTIONS .SH OPTIONS
.TP .TP
\fB\-\-script=\fR\fI<script>\fR
run the given script.
\fB\-\-verbose\fR,\fB\-v\fR \fB\-\-verbose\fR,\fB\-v\fR
when listing the available scripts, show the long descriptions. 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 $ mu script -v month
.fi .fi
Run the \fImsgs-per-month\fR script, and pass it the \fI--textonly\fR Run the \fImsgs-per-month\fR script for messages matching 'hello', and pass it
parameter: the \fI--textonly\fR parameter:
.nf .nf
$ mu script --script=msgs-per-month -- --textonly $ mu msgs-per-month --query=hello --textonly
.fi .fi
.SH RETURN VALUE .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 You can make your own Scheme scripts accessible through \fBmu script\fR by
putting them in \fI<muhome>/scripts\fR (which is typically 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 \fI~/.mu/scripts\fR). It is a good idea to document the scripts by using some
comments in the source code: special comments in the source code:
.nf .nf
;; INFO: this is my script -- one-line description ;; INFO: this is my script -- one-line description
;; INFO: (longer description) ;; INFO: (longer description)

View File

@ -177,9 +177,9 @@ mu_cmd_script (MuConfig *opts, GError **err)
if (err && *err) if (err && *err)
goto leave; goto leave;
if (!opts->script) { if (g_strcmp0 (opts->cmdstr, "script") == 0) {
print_scripts (scripts, !opts->nocolor, opts->verbose, print_scripts (scripts, !opts->nocolor, opts->verbose,
opts->params[1], err); opts->script_params[0], err);
goto leave; goto leave;
} }
@ -191,8 +191,7 @@ mu_cmd_script (MuConfig *opts, GError **err)
} }
/* do it! */ /* do it! */
mu_script_guile_run (msi, opts->muhome, mu_script_guile_run (msi, opts->muhome, opts->script_params, err);
(const gchar**)&opts->params[1], err);
leave: leave:
/* this won't be reached, unless there is some error */ /* this won't be reached, unless there is some error */
mu_script_info_list_destroy (scripts); 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, g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_REMOVE,
MU_ERROR_INTERNAL); MU_ERROR_INTERNAL);
/* note: params[0] will be 'add' */ /* note: params[0] will be 'remove' */
if (!opts->params[0] || !opts->params[1]) { if (!opts->params[0] || !opts->params[1]) {
g_warning ("usage: mu remove <file> [<files>]"); g_warning ("usage: mu remove <file> [<files>]");
mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS,
@ -593,6 +593,8 @@ set_log_options (MuConfig *opts)
logopts |= MU_LOG_OPTIONS_DEBUG; logopts |= MU_LOG_OPTIONS_DEBUG;
} }
MuError MuError
mu_cmd_execute (MuConfig *opts, GError **err) mu_cmd_execute (MuConfig *opts, GError **err)
{ {
@ -627,10 +629,7 @@ mu_cmd_execute (MuConfig *opts, GError **err)
case MU_CONFIG_CMD_SERVER: case MU_CONFIG_CMD_SERVER:
merr = with_store (mu_cmd_server, opts, FALSE, err); break; merr = with_store (mu_cmd_server, opts, FALSE, err); break;
default: default:
show_usage (); merr = MU_ERROR_IN_PARAMETERS; break;
mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS,
"unknown command '%s'", opts->cmdstr);
return MU_ERROR_IN_PARAMETERS;
} }
return merr; return merr;

View File

@ -300,13 +300,14 @@ config_options_group_script (void)
{ {
GOptionGroup *og; GOptionGroup *og;
GOptionEntry entries[] = { GOptionEntry entries[] = {
{"script", 0, 0, G_OPTION_ARG_STRING, &MU_CONFIG.script, {G_OPTION_REMAINING, 0,0, G_OPTION_ARG_STRING_ARRAY,
"script to run (see `mu help script')", "<script>"}, &MU_CONFIG.params, "script parameters", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL} {NULL, 0, 0, 0, NULL, NULL, NULL}
}; };
og = g_option_group_new("script", "Options for the 'script' command", og = g_option_group_new("script", "Options for the 'script' command",
"", NULL, NULL); "", NULL, NULL);
g_option_group_add_entries(og, entries); g_option_group_add_entries(og, entries);
return og; return og;
@ -461,7 +462,7 @@ cmd_from_string (const char *str)
{ "index", MU_CONFIG_CMD_INDEX }, { "index", MU_CONFIG_CMD_INDEX },
{ "mkdir", MU_CONFIG_CMD_MKDIR }, { "mkdir", MU_CONFIG_CMD_MKDIR },
{ "remove", MU_CONFIG_CMD_REMOVE }, { "remove", MU_CONFIG_CMD_REMOVE },
{ "script", MU_CONFIG_CMD_SCRIPT }, { "script", MU_CONFIG_CMD_SCRIPT },
{ "server", MU_CONFIG_CMD_SERVER }, { "server", MU_CONFIG_CMD_SERVER },
{ "verify", MU_CONFIG_CMD_VERIFY }, { "verify", MU_CONFIG_CMD_VERIFY },
{ "view", MU_CONFIG_CMD_VIEW } { "view", MU_CONFIG_CMD_VIEW }
@ -474,7 +475,8 @@ cmd_from_string (const char *str)
if (strcmp (str, cmd_map[i].name) == 0) if (strcmp (str, cmd_map[i].name) == 0)
return cmd_map[i].cmd; 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) && rv = g_option_context_parse (context, argcp, argvp, err) &&
cmd_help (); cmd_help ();
break; 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: default:
group = get_option_group (MU_CONFIG.cmd); group = get_option_group (MU_CONFIG.cmd);
if (group) if (group)

View File

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

View File

@ -142,25 +142,21 @@ mu4e e-mail client.
#BEGIN MU_CONFIG_CMD_SCRIPT #BEGIN MU_CONFIG_CMD_SCRIPT
#STRING #STRING
mu script [--script=<script>] [<pattern>] [-v] -- [script-options] mu script [<pattern>] [-v]
mu <script-name> [<script options>]
#STRING #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 List the available scripts and/or run them (if mu was built with support for
any arguments to the script after the '--' double-dash. 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: Some examples:
List all available scripts (one-line descriptions):
$ mu script
List all available scripts matching 'month' (long descriptions): List all available scripts matching 'month' (long descriptions):
$ mu script -v month $ mu script -v month
Run the 'msgs-per-month' script, and pass it the '--textonly' parameter: Run the 'msgs-per-month' script, and pass it the '--textonly' parameter:
$ mu script --script=msgs-per-month -- --textonly $ mu msgs-per-month --textonly
(as mentioned, parameters to the script follow the '--')
#END #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:" `("^References:" "^Face:" "^X-Face:"
"^X-Draft-From:" "^User-agent:") "^X-Draft-From:" "^User-agent:")
"Hidden headers when composing.") "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 (defconst mu4e~compose-address-fields-regexp
"^\\(To\\|B?Cc\\|Reply-To\\|From\\):") "^\\(To\\|B?Cc\\|Reply-To\\|From\\):")
@ -211,8 +216,7 @@ appear on disk."
(mu4e~compose-set-friendly-buffer-name) (mu4e~compose-set-friendly-buffer-name)
(mu4e~draft-insert-mail-header-separator) (mu4e~draft-insert-mail-header-separator)
;; hide some headers again ;; hide some headers again
(let ((message-hidden-headers mu4e~compose-hidden-headers)) (mu4e~compose-hide-headers)
(message-hide-headers))
(set-buffer-modified-p nil) (set-buffer-modified-p nil)
;; update the file on disk -- ie., without the separator ;; update the file on disk -- ie., without the separator
(mu4e~proc-add (buffer-file-name) mu4e~draft-drafts-folder)) nil t)) (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" (define-derived-mode mu4e-compose-mode message-mode "mu4e:compose"
"Major mode for the mu4e message composition, derived from `message-mode'. "Major mode for the mu4e message composition, derived from `message-mode'.
\\{message-mode-map}." \\{message-mode-map}."
(let ((message-hidden-headers mu4e~compose-hidden-headers)) (progn
(use-local-map mu4e-compose-mode-map) (use-local-map mu4e-compose-mode-map)
(make-local-variable 'message-default-charset) (make-local-variable 'message-default-charset)
;; if the default charset is not set, use UTF-8 ;; 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-S-u") 'mu4e-update-mail-and-index)
(define-key mu4e-compose-mode-map (kbd "C-c C-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 ;; setup the fcc-stuff, if needed
(add-hook 'message-send-hook (add-hook 'message-send-hook
(defun mu4e~compose-save-before-sending () (defun mu4e~compose-save-before-sending ()
@ -293,7 +297,7 @@ appear on disk."
(setq mu4e-sent-func 'mu4e-sent-handler) (setq mu4e-sent-func 'mu4e-sent-handler)
(mu4e~proc-sent (buffer-file-name) mu4e~draft-drafts-folder)) nil)) (mu4e~proc-sent (buffer-file-name) mu4e~draft-drafts-folder)) nil))
;; mark these two hooks as permanent-local, so they'll survive mode-changes ;; 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)) (put 'mu4e~compose-mark-after-sending 'permanent-local-hook t))
(defconst mu4e~compose-buffer-max-name-length 30 (defconst mu4e~compose-buffer-max-name-length 30
@ -312,12 +316,7 @@ appear on disk."
(truncate-string-to-width str (truncate-string-to-width str
mu4e~compose-buffer-max-name-length mu4e~compose-buffer-max-name-length
nil nil t))))) 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) (defun mu4e~compose-handler (compose-type &optional original-msg includes)
"Create a new draft message, or open an existing one. "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) (set (make-local-variable 'mu4e-compose-parent-message) original-msg)
(put 'mu4e-compose-parent-message 'permanent-local t) (put 'mu4e-compose-parent-message 'permanent-local t)
;; hide some headers ;; hide some headers
(let ((message-hidden-headers mu4e~compose-hidden-headers)) (mu4e~compose-hide-headers)
(message-hide-headers))
;; switch on the mode ;; switch on the mode
(mu4e-compose-mode)) (mu4e-compose-mode))

View File

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

View File

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

View File

@ -553,13 +553,14 @@ process."
(defun mu4e~rfc822-phrase-type (ph) (defun mu4e~rfc822-phrase-type (ph)
"Return either atom, quoted-string, a corner-case or nil. This "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 (returning 'rfc822-quoted-string). Then whether there is a quote
inside the phrase (returning 'rfc822-containing-quote). inside the phrase (returning 'rfc822-containing-quote).
The reverse of the RFC atext definition is then tested. The reverse of the RFC atext definition is then tested.
If it matches, nil is returned, if not, it is an 'rfc822-atom, which If it matches, nil is returned, if not, it is an 'rfc822-atom, which
is returned." is returned."
(cond (cond
((= (length ph) 0) 'rfc822-empty)
((= (aref ph 0) ?\") ((= (aref ph 0) ?\")
(if (string-match "\"\\([^\"\\\n]\\|\\\\.\\|\\\\\n\\)*\"" ph) (if (string-match "\"\\([^\"\\\n]\\|\\\\.\\|\\\\\n\\)*\"" ph)
'rfc822-quoted-string '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) (defmacro mu4e~view-in-headers-context (&rest body)
"Evaluate BODY in the current headers buffer, with moved to the "Evaluate BODY in the context of the headers buffer connected to
current message." this view."
`(progn `(progn
(unless '(buffer-live-p mu4e~view-headers-buffer) (unless (buffer-live-p mu4e~view-headers-buffer)
(mu4e-error "no headers-buffer connected")) (mu4e-error "no headers-buffer connected"))
(let* ((msg (mu4e-message-at-point)) (let* ((msg (mu4e-message-at-point))
(docid (mu4e-message-field msg :docid))) (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. time when the message was sent today, and the date otherwise.
@item The header field used for sorting is indicated by ``@t{V}'' or @item The header field used for sorting is indicated by ``@t{V}'' or
``@t{^}''@footnote{or you can use little graphical triangles; see variable ``@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 ascending, respectively). You can influence this by a mouse click, or
@key{O}. Not all fields allow sorting. @key{O}. Not all fields allow sorting.
@item Instead of showing the @t{From:} and @t{To:} fields separately, you @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, Jamie Zawinski's mail threading algorithm,
@url{http://www.jwz.org/doc/threading.html}}. @url{http://www.jwz.org/doc/threading.html}}.
@item The headers view is @emph{automatically updated} if any changes are @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 user-interaction. If you do not want such automatic updates, set
@code{mu4e-headers-auto-update} to @code{nil}. @code{mu4e-headers-auto-update} to @code{nil}.
@end itemize @end itemize
@ -1134,8 +1134,8 @@ is used for images.
@section Displaying rich-text messages @section Displaying rich-text messages
@t{mu4e} normally prefers the plain-text version for messages that consist of @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 both a plain-text and html (rich-text) versions of the body-text. You can
this by setting @code{mu4e-view-prefer-html} to @t{t}. 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 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 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 @section Actions
You can perform custom functions (``actions'') on messages and their 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 @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 The @command{emacs}-package @t{sauron}@footnote{Sauron can be found at
@url{https://github.com/djcb/sauron}, or in the Marmalade package-repository @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 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 to get notifications about new mails. If you run something like the below
script in your @t{crontab} (or have some other way of having it execute every script from your @t{crontab} (or have some other way of having it execute
@emph{n} minutes) you receive notifications in the sauron-buffer when new every @emph{n} minutes), you receive notifications in the @t{sauron}-buffer
messages arrive. when new messages arrive.
@verbatim @verbatim
#!/bin/sh #!/bin/sh
# put the path to your Inbox folder here
# put the path to your Inbox folder here
CHECKDIR="/home/$LOGNAME/Maildir/Inbox" CHECKDIR="/home/$LOGNAME/Maildir/Inbox"
sauron-msg () { sauron-msg () {
DBUS_COOKIE="/home/$LOGNAME/.sauron-dbus" DBUS_COOKIE="/home/$LOGNAME/.sauron-dbus"
if test "x$DBUS_SESSION_BUS_ADDRESS" = "x"; then if test "x$DBUS_SESSION_BUS_ADDRESS" = "x"; then
@ -3011,7 +3013,7 @@ normal, synchronous fashion.
@section Known issues @section Known issues
Although they are not really @emph{questions}, we end this chapter with a list 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 search in vain for things that are not there (yet), and the author can use it
as a todo-list. as a todo-list.
@ -3020,11 +3022,24 @@ as a todo-list.
utf-8}; so, if you problems with encodings, be sure to have utf-8}; so, if you problems with encodings, be sure to have
@code{(set-language-environment "UTF-8")} in your @file{~/.emacs}. @code{(set-language-environment "UTF-8")} in your @file{~/.emacs}.
@item @emph{Thread handling is incomplete.} While threads are calculated and are @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 @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. 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 @end itemize
For a more complete list, please refer to the issues-list in the
github-repository.
@node Tips and Tricks @node Tips and Tricks
@appendix 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) ((string-match "Account1" maildir)
(setq folder (or (catch 'found (setq folder (or (catch 'found
(dolist (mailing-list my-mu4e-mailing-lists) (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))))) (throw 'found (cdr mailing-list)))))
"/Account1/General"))) "/Account1/General")))
((string-match "Gmail" maildir) ((string-match "Gmail" maildir)
@ -3203,7 +3219,8 @@ If you have multiple accounts, you can accommodate them as well:
((string-match "Account2" maildir) ((string-match "Account2" maildir)
(setq folder (or (cdar (member* subject my-mu4e-subject-alist (setq folder (or (cdar (member* subject my-mu4e-subject-alist
:test #'(lambda (x y) :test #'(lambda (x y)
(string-match (car y) x)))) (string-match
(car y) x))))
"/Account2/General")))) "/Account2/General"))))
folder)) folder))
@end lisp @end lisp
@ -3216,10 +3233,12 @@ message based on the mailing list to which it was sent. This requires
another variable: another variable:
@lisp @lisp
(defvar my-mu4e-mailing-lists '(("mu-discuss@@googlegroups.com" . "/Account1/mu4e") (defvar my-mu4e-mailing-lists
("pandoc-discuss@@googlegroups.com" . "/Account1/Pandoc") '(("mu-discuss@@googlegroups.com" . "/Account1/mu4e")
("auctex@@gnu.org" . "/Account1/AUCTeX")) ("pandoc-discuss@@googlegroups.com" . "/Account1/Pandoc")
"List of mailing list addresses and folders where their messages are saved.") ("auctex@@gnu.org" . "/Account1/AUCTeX"))
"List of mailing list addresses and folders where
their messages are saved.")
@end lisp @end lisp
@node Saving outgoing messages @node Saving outgoing messages