<<< Date Index >>>     <<< Thread Index >>>

Support for extension languages



Hi,

I have been trying to make Mutt extensible with a common extension
language since I found it might be useful for people who want more
control on Mutt's behavior or for people who would like to be able to
automate various tasks.

So far, I have modified Mutt 1.5.4i in order to make it extensible with
any extension language, currently the Scheme language (using Guile) --
note that this is not an attempt to rewrite GNU Emacs, just an attempt
to enhance a lightweight user-agent which I like. :)

The result can be downloaded from:
http://www.laas.fr/~lcourtes/software/mutt-guile-latest.tar.bz2
and a diff against Mutt 1.5.4i is available at:
http://www.laas.fr/~lcourtes/software/mutt-1.5.4i-guile.diff.bz2 .

Note that this is experimental-work-in-progress so there is a lot of
room for improvements.  However, I would be glad if people interested in
it could comment on it, try it, or help me improve it.

Below is a small documentation of how this all works (the
`README.Extensible' file of the tarball).

Thanks,
Ludovic.


Extensible version of Mutt
=-=-=-=-=-=-=-=-=-=-=-=-=-

This file describes an experimental integration of extension languages into
Mutt, why one might need that, and what have been the basic design ideas
behind that.

Currently, the only supported extension language is Scheme (Guile).  However,
Mutt may still be compiled without extension language support, and even when
compiled with extension language support it remains 100% compatible with the
regular Mutt.

This work is based on Mutt 1.5.4i.


1. Design approach
------------------

The idea was to allow Mutt to be extensible with _any_ extension language in a
way that makes it possible to use and customize internal mechanisms, without
being incompatible with the original Mutt.  However, we don't want Mutt to
become a heavy-weight program like GNU Emacs -- that's why we are not using
Gnus. :) 


1.1. Why adding an extension language?

Mutt already has some nice features that may be used to customize its
behavior, namely "hooks".  However, there are mainly three limitations to this
approach:

* hooks are pre-defined and even though there are already several of them in
  well-chosen places, one might some day want to add an hook to another event
  than those predefined;

* hooks are basic actions that can be executed either before or after a given
  operation and may not be used to change the behaviour of a given action;

* hooks can only execute what Mutt calls `commands' (e.g. "set from=ludo@net",
  "exec tag-pattern", "exec delete-message"); however functions that can be
  used are, again, pre-defined, and most of them are interactive which makes
  it impossible to automate some particular operation (for instance,
  tag-pattern would present a prompt and wait till the user entered a pattern;
  delete-message would not only mark a message as deleted but also jump to the
  next non-deleted message).

These limitations sometimes make the use of hooks quite frustrating.

Without the use of an extension language, it is almost impossible for the user
to:

  * customize a given feature (e.g. I don't like the default scoring method
    which rounds up negative scores to zero and I would like to have my own;
    I would like Mutt to warn me before displaying a message whose header
    contains 'X-Spam-Status: Yes'; etc.);

  * implement new features, new commands, which may be configured using new
    configuration variables (e.g. if `spam_learn' is "yes", then I would like
    Mutt to pipe messages through SpamAssassin once they have been displayed
    and recognized as spam/ham).

Using a simple interpreted language such as Scheme makes it possible to
quickly to this.


1.2. What should be extensible?  How should it be extended?

Ideally, any built-in feature, function or mechanism should be make
extensible via the extension language.  It should also be possible to
implement and use new functions programmed with the extension language.
Practically, this means that it should be possible to customize the way
messages are scored or the way messages are displayed.  This also means that
it should be possible to implement a function that counts the number of
messages coming from your best friend and call it through the standard Mutt
interface.

Muttrc files should still be available, but their use should become optional.
This means that built-in configuration variables, commands and functions have
to be made available to the extension language.  Executing functions or
commands written using the extension language requires that the
command/function execution mechanism be customisable itself.


1.3. Implementation details

Being able to change what a function does using the extension language
requires that Mutt calls functions written in that language rather than
built-in C functions.  Usually, extension languages (i.e. interpreted
languages) would allow to redefine a given function.  This way, any feature
can easily be modified.  For instance, instead of calling mutt_display_message
(), Mutt has to directly call the `display-message' Scheme function; in turn,
this Scheme function may choose to just the `builtin-display-message' Scheme
function which is just a wrapper for the original `mutt_display_message ()' C
function.

In the current implementation, the functions that can be extended in this way
are defined in the file extension.h and we call them `commands' (which is
different from what Mutt call `commands', functions that are called within a
muttrc file).  These commands declared in extension.h with the
`MUTT_DECLARE_COMMAND' macro are extensible features.  This macro declares
three functions: one with a leading `cmd_' which is the function to be used
throughout the code; one with a leading `smutt_cmd_' which is the Scheme
version of this command; one with a leading `builtin_' which is the original C
function for this feature.  The C code should only call the `cmd_' functions,
which in turn call the corresponding `smutt_cmd_' function if the extension
language is Guile (see extension.c).  The `builtin_' functions should be made
available to the extension language.  Here is what happens, by default, when
`cmd_display_message ()' is called:

  cmd_display_message (C) -> smutt_cmd_display_message (C) -------------.
                                                                        |
  .--- builtin-display-message (Scheme) <- display-message (Scheme) <---'
  |
  `---> builtin_display_message (C)

Sometimes, features can be customized at different levels of abstraction.
Consider the execution of a command:

  + source_rc_file (".muttrc")
  |
  +--+ parse_rc_line ("set realname='ludo' ; exec delete-message")
     |
     +--+ execute_command ("set", "realname='ludo'")
     |  |
     |  +--+ set_option ("realname", "ludo")
     |
     +--+ execute_command ("exec", "delete-message")
        |
        +--+ execute_function ("delete-message")

Each one of these features (commands) may be customized (well, almost).  For
example, allowing transparent query/modification of built-in and extension
language variables may be done by customizing the `set_option' command and
make it check whether the given variable name is the name of a built-in
variable or not.  Transparent execution of functions written in the execution
language can be done by customizing the `execute_command' and/or the
execute_function command.

Making it possible required to split the `parse_set ()' C function into two
distinct functions: `builtin_set_option ()' which is used to set the value of
a built-in variable, and `builtin_query_option ()' which is used to display
the value of a variable.  The latter one gets called for instance when typing
"set ?realname".  The `mutt_parse_rc_line ()' function also had to be splitted
in a similar way, leading to the creation of `builtin_execute_command ()'.

As one might guess, integrating features one by one is not very scalable but
seems to be the only solution.  However, any built-in Mutt command may be
customized since command execution is customizable.


1.4. Things left to do

A lot of things are left to be done, mostly making more features extensible.
The key binding mechanism might need some rewrite in order to be extensible
(currenly, a key is bound to an `op' which is basically the ID of a function
to be called when the key is pressed).  It should be possible to create and
handle menus (shouldn't be too hard).

Also, currently only a few features may be customized (see `extension.h' for a
complete list of what may be customized) and it would be nice to have some
more.  However, some features are not easy to integrate because the manipulate
internal things which we do not want the extension language to know: for
instance mx_sync_mailbox () sets its `index_hint' argument to something that
may be used by the index; send_message () from send.c may not only return
`true' of `false' but also a third value in case a message needs to be
postponed.

The current execute-command function is just passed a command name and an
unparsed argument string.  It would be much cleaner if argument parsing could
be done *before* execute-command is called and then commands would just
receive and argc/argv list of arguments.  However, this would require
rewriting of all Mutt commands and it may not work for all of them (for
instance, would '=' be an argument for command 'set' when typing "set
realname=myself"?).

Try to add support for your favorite language (Python?).


2. Extending Mutt with Guile
----------------------------

2.1. Features

All the features described here are programmed in Scheme in the system-wide
`mutt.scm' file which should get installed in /etc.

Transparent call of Scheme functions through Mutt's command interface (i.e.
either in a Muttrc file or by typing `:' in the index menu) or through the
function interface (i.e. "exec <function>").

The following Scheme functions

  (define (my-command argstring)
    (ui-message argstring))

  (define (my-function)
    (ui-message "This is my function"))

can be called within a muttrc file (or using Mutt's `enter-command' function):

  my-command and its arguments
  exec my-function

Transparent access to Scheme and built-in variables, which means that the
following lines should work:

  (define my-own-variable 2)
  (set-option "spoolfile" "/var/mail/ludo")
  (set-option "my-own-variable" "Hello world!")

This can also be done using the standard muttrc syntax:

  set my-own-variable="Hello world!"

The default `save-alias' function has been rewritten so that aliases can be
saved in a schemey way.  For instance, while the standard way of saving
aliases is:

  alias ludo Ludovic Courtès <ludo@xxxxxxxx>

the schemey way to do that is:

  (create-alias "ludo" (create-address "Ludovic Courtès <ludo@xxxxxxxx>"))

This is not very useful and may be overriden in the user's `$HOME/.mutt.scm'
by simply adding the following line:

  (set! save-alias builtin-save-alias)

Note that Scheme things may be debugged by setting the Scheme variable `debug'
to "yes" (which can be done by typing `:set debug="yes"'); the `debug-time'
variable may be set to the delay of debug messages.

Finally, one can also execute Scheme code directly from within Mutt:

  :scheme (ui-message (format \#f "~a unread messages" 
(context-unread-messages)))

(note that there are no quotes).  Mutt will always ask before evaluating
Scheme code to prevent from malicious use of this feature.

Looking at the files `$SYSCONFIGDIR/mutt.scm' (probably `/etc/mutt.scm') and
`dotmutt.scm' (my own `.mutt.scm' configuration file) is probably the best way
to get started.


2.2. Data types

The available data types available are:

* `context', the equivalent of Mutt's CONTEXT, which may be used to get the
  number of messages (`context-messages context', etc.) and to traverse them
  (`context-select-headers proc context');

* `alias', alias objects that may be created using
   `create-alias name address';

* `address', address objects that may be created using
   `create-address realname email';

* `header' which represents messages and may be observed and modified using
  `header-delete!', `header-score', `header-envelope', etc.;

* `envelope' which represents a header's envelope and may be observed and
  modified using `envelope-from', `envelope-subject', etc.;

* `pattern' which represent Mutt limitation patterns and may be used using
   `compile-pattern string', `execute-pattern header pattern';

* `menu' which is not really useful yet.

* `score', the equivalent of Mutt's SCORE which represents a message scoring
  rule (not very useful).

All these data types come with a number of accessor functions allowing to
access (and modify) their contents.  For example, given HEADER, a header
object, the following expression

  (address-mailbox (envelope-to (header-envelope header)))

returns the email address of HEADER's recipient.  Some accessors are
procedures with setter, ie. procedures that may be used also when modifying an
element with `set!':

  (set! (header-score header) (+ 3 (header-score header)))

There are also type predicates: for instance, `header?' returns true when its
argument is a header object.


2.3. Things left to do

Better exception handling (currently only functions executed as Mutt commands
or Mutt functions are safe because the `safe-exec' macro catches any exception
when executing these Scheme functions).


Sept. 2003.
Please send comments to Ludovic Courtès <ludovic.courtes@xxxxxxx>.
$Id$

Attachment: pgpv744IPDapg.pgp
Description: PGP signature