\input texinfo.tex @c -*-texinfo-*- @c %**start of header @setfilename mu-scm.info @settitle Mu-SCM User Manual @c Use proper quote and backtick for code sections in PDF output @c Cf. Texinfo manual 14.2 @set txicodequoteundirected @set txicodequotebacktick @documentencoding UTF-8 @c %**end of header @include version.texi @copying Copyright @copyright{} 2025-@value{UPDATED-YEAR} Dirk-Jan C. Binnema @quotation Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License.'' @end quotation @end copying @titlepage @title Mu-SCM - extending @t{mu} with Guile Scheme @subtitle version @value{VERSION} @author Dirk-Jan C. Binnema @c The following two commands start the copyright page. @page @vskip 0pt plus 1filll @insertcopying @end titlepage @dircategory The Algorithmic Language Scheme @direntry * Mu-SCM: (mu-scm). Guile support for the mu e-mail search engine @end direntry @contents @ifnottex @node Top @top Mu-SCM Manual @end ifnottex @iftex @node Welcome to @t{mu-scm} @unnumbered Welcome to @t{mu-scm} @end iftex Welcome to @t{mu-scm}! @t{mu} is a program for indexing and searching your e-mails. It can do so in many different ways, but sometimes that may not be enough. @t{mu-scm} is made for such cases. It embeds the Guile programming language into @t{mu}. Guile is the @emph{GNU Ubiquitous Intelligent Language for Extensions} - a version of the @emph{Scheme} programming language, the official GNU extension language, and a member of the @emph{Lisp} family of programming languages -- like emacs-lisp, @emph{Racket}, Common Lisp. @t{mu-scm} is replacing the older @t{mu-guile} bindings; some notable differences are: @itemize @item No separate 'module', instead use mu itself: This greatly reduces the number of 'moving parts' and mysterious errors for users @item Automatically set up a reasonable environment: @t{mu scm} simply reuses the user's @t{mu} configuration, simplifying setup @item API improvements: @t{mu-scm} has learned from @t{mu-guile} to make its APIs nicer to use @item However, some parts are still missing: @t{mu-scm} does not yet support all that @t{mu-guile} did. It's just getting started. @end itemize If you're not familiar with Scheme or Lisp, @t{mu-scm} may be a fun way to learn a bit more! Note: @t{mu-scm} is brand new and rather @strong{experimental} for now, and APIs can still change without warning. @menu * Getting started:: * Shell:: * Scripts:: * API Reference with examples:: Appendices * GNU Free Documentation License:: The license of this manual. Indices * Procedure Index:: * Variable Index:: @end menu @node Getting started @chapter Getting started @menu * Using distributions:: * Building it yourself:: * Verifying support:: @end menu This chapter walks you through the installation and basic setup. @node Using distributions @section Using distributions At the time of writing, no distributions ship with an SCM-enabled @t{mu} yet, so for now you need to build it yourself. Of course, this is fully optional. @node Building it yourself @section Building it yourself To build @t{mu} with SCM support, first you need to ensure you have installed the required Guile development packages. The details of getting those vary across environments / distributions, e.g.: on Fedora (as root): @example # dnf install guile30-devel @end example or on Debian/Ubuntu: @example $ sudo apt install guile-3.0-dev @end example With those packages in place, you can (re)build @t{mu} and @t{mu-scm} should be built automatically if you did @emph{not} explicitly disable it. Parts of @t{mu-scm} depend on @t{mu} being @emph{installed}, not just built; however, you can still use it un-installed as well by setting an environment variable @t{MU_SCM_DIR} to the source-directory, e.g. @t{/home/user/sources/mu/scm}. @node Verifying support @section Verifying support After installing @t{mu}, you can check the output of @command{mu info}. If @t{mu-scm} is available, in the table you should find a line: @example | scm-support | yes | GNU Guile 3.x support (new)? | @end example @node Shell @chapter Shell This chapter discusses the @t{mu-scm}-powered shell. After installation (@xref{Getting started}), you can start the @t{mu-scm} shell by issuing: @example $ mu scm @end example This is the Guile REPL@footnote{@url{https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop}} customized for @t{mu}. In particular, it supports all the common @t{mu} command-line arguments for specifying where your @t{mu} stores its data and so on (see the @t{mu} and @t{mu scm} man-pages for details). @example $ mu scm [....] Welcome to the mu shell! GNU Guile 3.0.9 Copyright (C) 1995-2023 Free Software Foundation, Inc. Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. 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)> @end example This shell is set-up for use with @t{mu} with the module imported and the message database loaded. So we can try some simple queries, using the @code{mfind} function, which mimics @t{mu find}@footnote{It is called @t{mfind} instead of @t{find} to avoid clashing with the core Guile function by that name.} @example scheme@@(guile-user)> (mfind "rhinoceros") $1 = (#< 7f2aa671fce0>) @end example Which means we found one message matching @t{rhinoceros}. If you don't have such a message, try some different query. We can then inspect the message, using the @t{$1} temporary: @example scheme@@(guile-user)> (subject (car $1)) $2 = "Important message about African animals" @end example @node Scripts @chapter Scripts In the @ref{Shell} chapter, we saw how you can use @t{mu-scm} interactively. In this also possible to run @emph{scripts}. Generally, you can invoke a script by passing its name to @command{mu scm}, for example: @example mu scm path/to/script/myscript.scm @end example @t{mu-scm} expects scripts to have a procedure @code{main} of the form: @deffn {Scheme Procedure} main script #:rest args @end deffn @t{main} receives the script (path) as its first argument, and any other arguments in the list @t{args}. Thus, an example script might look like: @lisp (use-modules (mu)) (define* (main script #:rest args) (format #t "running script ~a and arguments ~s\n" script args) (for-each (lambda (msg) (format #t "~a ~a\n" (time-t->iso-date (date msg)) (or (subject msg) "No subject"))) (mfind "hello AND date:2015.." #:max-results 5))) @end lisp You can run it like this: @example $ mu scm ~/myscript.scm some args 123 running script /home/user/myscript.scm and arguments ("some" "args" "123") 2015-01-02T12:41:13 Happy 2015! It's Day 2 - How Are Those Resolutions Coming? 2015-01-06T12:29:21 Brunch Sunday 11.1. 2015-01-06T18:05:27 Moderator's spam report for mu-discuss@@googlegroups.com 2015-01-13T11:37:27 Upvoted: The Story Behind the Users Who Make Reddit's Front Page 2015-01-16T17:06:23 [mu] combining maildirs in query (#559) @end example Quite likely, your output will differ from the above. @node API Reference with examples @chapter API Reference with examples This chapter goes through the @t{mu-scm} API. For this, we need to understand a few key concepts, represented in some GOOP objects and other data-structures: @itemize @item the @t{} represents the mu database with information about messages @item from the store, you can find @t{} objects, each of which represent a specific message (similar to what you get from @code{mu find}) @item the store also exposes tha contacts in the store as alists (``association lists'') (similar to what you get from @code{mu cfind}) @end itemize @menu * Store:: the database of all information * Message:: inspecting individual messages * Miscellaneous:: other functions * Helpers:: some helper functions @end menu @node Store @section Store The store represents the @t{mu} database, i.e., the place where @t{mu index} stores information about messages and contacts. While you could theoretically have @emph{multiple} stores, for now @t{mu-scm} only supports a single one, which is the store you opened when you started @command{mu scm}. For completeness and possible future use, store-related methods do take a @t{#:store} parameter, but you can (in fact, @emph{must}) leave it out, and use its default value. Hence, in the API descriptions below, we leave out the @t{#:store} argument. The store currently only exposes a few methods, described below. @deffn {Scheme Procedure} mfind query [#:related? #f] [#:skip-dups? #f] [#:sort-field 'date] [#:reverse? #f] [#:max-results #f] @end deffn Perform a query for messages in the store, and return a list of message objects (@xref{Message}) for the matches. @itemize @item @var{query} is a Mu query; see the @t{mu-query} man-page for details @item @var{#:related?} whether @emph{related} messages should be included. This is similar to the @t{--include-related} parameter for @command{mu find} @item @var{#:skip-dups?} whether to exclude duplicate messages This is similar to the @t{--skip-dups} parameter for @command{mu find} @item @var{#:sort-field} a symbol, the message field to sort by You can sort by the fields (see @command{mu info fields} that have a @t{value=yes}) @item @var{#:reverse?} whether to reverse the sort-direction (make it descending) @item @var{#:max-results} the maximum number of results By default @emph{all} matches are returned @end itemize @t{mfind} mimics the @command{mu find} command-line command. Example usage: @lisp (mfind "capybara" #:skip-dups? #t #:sort-field 'subject) => (#< 7f3c8ac09c00> #< 7f3c8ac09be0>) @end lisp @deffn {Scheme Procedure} cfind pattern [#:personal? #f] [#:after #f] [#:max-results #f] @end deffn Search for contacts in the store, and return a list of contacts for the matches. Each contact is an association list with at least a key (symbol @t{email}) with e-mail address as its value, and possibly a @t{name} key with the contact's name. In the future, other fields may be added. @itemize @item @var{pattern} is a basic case-insensitive PCRE-compatible regular expression see the @t{pcre(3)} man-page for details @item @var{#:personal?} if true, only match @emph{personal} contacts A personal contact is a contact seen in message where ``you'' were an explicit sender or recipient, thus excluding mailing-list. Personal addresses are those that were specified at store creation time - see the @t{mu-init} man-page, in particular the @t{--personal-address} parameter @item @var{#:after} only include contacts last-seen after some time-point Specified as the number of seconds since epoch. Helper-function @code{iso-date->time-t} can be useful here. @item @var{#:max-results} (optional) the maximum number of results By default, @emph{all} matches are returned @end itemize @t{cfind} mimics the @command{mu cfind} command-line command. Example usage: @lisp (car (cfind "smith" #:personal? #t)) => ((name . "Hannibal Smith") (email . "jhs@@example.com")) @end lisp @deffn {Scheme Procedure} mcount @end deffn Returns the number of messages in the store. Example usage: @lisp (mcount) => 140728 @end lisp @node Message @section Message A message represents the information about an e-mail message. @t{mu} gets this information either from its database (the @t{mu} store), e.g., with @code{mfind} (see @xref{Store}) or by reading an email message from the file-systems with @code{make-message}. In many of the examples below we assume there is some @code{message} object, e.g. as retrieved through: @lisp (define msg (car (mfind "hello"))) @end lisp @anchor{full-message} Many of the procedures below use the internal representation of the message from the database; this re-uses the same information that @t{mu4e} uses. However, that is not sufficient for all: @code{body} and @code{header} need the full message. To get this, it needs to open the message file from the file-system. Much of this is internal to @t{mu-scm}, except that full-method-procedures are a bit slower relatively to the database-only ones. @subsection Basics @deffn {Scheme Procedure} make-message path @end deffn Create a new message object from a file-system path. This is a @emph{full message}, unlike the ones you get from a store-query (i.e., @code{mfind}). @deffn {Scheme Procedure} subject message @end deffn Get the message subject, or @t{#f} if there is none. For example: @lisp (subject msg) => "Hello!" @end lisp @deffn {Scheme Procedure} maildir message @end deffn Get the message subject, or @t{#f} if there is none. For example: @lisp (maildir msg) => "/inbox" @end lisp @deffn {Scheme Procedure} path message @end deffn Get the file-system path for the the message. For example: @lisp (path msg) => "/home/user/Maildir/archive/cur/1546942532.adb906ab91921e10.hyperion:2,DS" @end lisp @deffn {Scheme Procedure} message-id message @end deffn Get the message's @t{Message-ID} field, or @t{#f} if there is none. For example: @lisp (message-id msg) => "87a15477-dd66-43e5-a722-81c545d6af19@@gmail.com" @end lisp @deffn {Scheme Procedure} date message @end deffn Get the message's @t{Date} field (the sent-date), or @t{#f} if there is none. @t{date} expressed the data as the number of seconds since epoch, @t{time_t}. As a convenience, @t{iso-date} expresses the date as an ISO-8601-compatible string or an empty string of the same length. For example: @lisp (date msg) => 1750064431 (iso-date msg) => 2025-06-16T09:00:31 @end lisp @deffn {Scheme Procedure} body message [#:html? #f] @end deffn Get the message body as a string, or return @code{#f} if not found. If @var{#:html?} is non-@t{#f}, get the HTML-body instead. This requires the @ref{full-message,,full message}. @deffn {Scheme Procedure} message-id message @end deffn Get the message's @t{Message-ID} field, or @t{#f} if there is none. For example: @lisp (message-id msg) => "87a15477-dd66-43e5-a722-81c545d6af19@@gmail.com" @end lisp @subsection MIME-parts Messages consist of one or more MIME-parts, which include the body, attachments and other parts. To get the MIME-parts for a message, you can use the @code{mime-parts} method on a @code{message}. @deffn {Scheme Procedure} mime-parts message @end deffn Get the MIME-parts for this message, as a list of @code{} objects. A MIME-parts is an object with a few methods. @deffn {Scheme Procedure} mime-part->alist mime-part @end deffn Get an association list (alist) describing the MIME part. For example: @lisp ;; describe the second MIME-part of the first message with an attachment (mime-part->alist (cadr (mime-parts (car (mfind "flag:attach" #:max-results 1))))) => ((filename . "emacs.png") (size . 18188) (content-type . "image/png") (index . 1)) @end lisp Depending on the MIME-part, different fields can be present: @itemize @item @t{index} the index (number) of the part, 0-based @item @t{mime-type} the MIME-type of the part @item @t{size} the size of the part in bytes. For encoded parts, this is the @emph{encoded} size @item @t{filename} the filename (for attachments). This is as specified in the message, but with forward slashes and control-characters removed or substituted with @t{-}. @item @t{signed?} is this part (cryptographically) signed? @item @t{encrypted?} is this part encrypted? @end itemize @deffn {Scheme Procedure} make-port mime-part [#:content-only? #f] [#:decode? #t] @end deffn Get a read-port for the given MIME-part. Ports are the standard mechanism for dealing with I/O in Guile; see its documentation for further details. If @code{content-only?} is true, only include the contents, not headers. If @code{decode?} is true, decode the content (from e.g., Base-64); in that case, @code{content-only?} is implied to be #t. @deffn {Scheme Procedure} write-to-file [#:filename #f] [#:overwrite? #f] @end deffn Write MIME-part to file. Use @code{filename} is the file/path to use for writing; if this is @code{#f}, the name using the @code{filename} procedure. If @code{overwrite?} is true, overwrite existing files of the same name; otherwise, raise an error if the file already exists. @deffn {Scheme Procedure} filename mime-part @end deffn Determine a filename for the given MIME-part. This is either taken from the @t{filename} property of the MIME-part alist, or, If that does not exist, a generic name. @subsection Contacts Message fields @t{To:}, @t{From:}, @t{Cc:} and @t{Bcc:} contain @emph{contacts}. @t{mu-scm} represents those as list of contact-alists, or contacts for short. Each contact is an alist with at least an @code{email} and optionally a @code{name} field. For instance: @lisp (to msg) => (((name . "Hannibal Smith") (email . "jhs@@example.com")) ((email . "murdock@@example.com"))) @end lisp @deffn {Scheme Procedure} from message @end deffn Get the list of message senders (usually only one). Returns either a list of contacts or @t{#f}. @deffn {Scheme Procedure} to message @end deffn Get the message's intended @t{To:} recipients. Returns either a list of contacts or @t{#f} if not found. @deffn {Scheme Procedure} cc message @end deffn Get the message's intended carbon-copy @t{Cc:} recipients. Returns either a list of contacts or @t{#f} if not found. @deffn {Scheme Procedure} bcc message @end deffn Get the message's intended blind carbon-copy @t{Bcc:} recipients. Returns either a list of contacts or @t{#f} if not found. @subsection Flags Message can have a number of properties or @emph{flags}. @deffn {Scheme Procedure} flags message @end deffn Get the message's list of @emph{flags}. Flags are symbols, see @command{mu info fields} for the list of all flags. For example: @lisp (flags msg) => (draft seen personal) @end lisp There are some helpers to check for the presence of specific flags: @deffn {Scheme Procedure} flag? message flag @end deffn Does the message have the given flag? @t{#t} or @t{#f}. For example: @lisp (flags? msg 'personal) => #t (flags? msg 'calendar) => #f @end lisp @deffn {Scheme Procedure} draft? message @end deffn Does the message have the @t{draft} flag? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} flagged? message @end deffn Does the message have the @t{flagged} flag? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} passed? message @end deffn Does the message have the @t{passed} flag? I.e., has it been forwarded? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} replied? message @end deffn Does the message have the @t{replied} flag? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} seen? message @end deffn Does the message have the @t{seen} flag? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} trashed? message @end deffn Does the message have the @t{trashed} flag? I.e., has it been marked for removal? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} new? message @end deffn Is this a new message? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} signed? message @end deffn Is this a cryptographically signed message? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} encrypted? message @end deffn Is this an encrypted message? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} attach? message @end deffn Does the message have an attachment? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} unread? message @end deffn Is this message unread? I.e., either new or not seen? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} list? message @end deffn Is this a mailing-list message? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} personal? message @end deffn Is this a personal message? Returns @t{#t} or @t{#f}. @deffn {Scheme Procedure} calendar? message @end deffn Does this message include a calendar invitation? Returns @t{#t} or @t{#f}. @subsection Miscellaneous @deffn {Scheme Procedure} last-change message @end deffn Get the time of the message's last change (through @t{mu}), or @t{#f} if there is none. The time is expressed the data as the number of seconds since epoch, @t{time_t}. For example: @lisp (last-change msg) => 1703336567 @end lisp @deffn {Scheme Procedure} priority message @end deffn Get the message's priority. This is a symbol, either @t{high}, @t{normal} or @t{low}, or @t{#f} if not present. For example: @lisp (priority msg) => normal @end lisp @deffn {Scheme Procedure} size message @end deffn Get the message's size in bytes. For example: @lisp (size msg) => 2815 @end lisp @deffn {Scheme Procedure} language message @end deffn Get the ISO-639-1 language code for message's primary language or @t{#f} if not found. This is available only if @t{mu} was built with CLD2 support, see @command{mu info}. The language code is represented as a symbol, such as @t{en}, @t{nl} or @t{fi}. For example: @lisp (language msg) => en @end lisp @deffn {Scheme Procedure} header message @end deffn Get some arbitrary, raw header from the message. The @var{header} parameter is a case-insensitive string @emph{without} the colon (@t{:}). This requires the @ref{full-message,,full message}. For example: @lisp (header msg "subject") => "Re: Musical chairs" (header msg "From") => "\"Raul Endymion\" " (header msg "Something") => #f @end lisp @deffn {Scheme Procedure} references message @end deffn Get the list of references (message-ids of related messages) for this message. This combines the @t{References} and @t{In-Reply-To} fields, from oldest to the immediate parent. Returns @code{#f} if there are no references. For example: @lisp (references msg) => ("439C1136.90504@@euler.org" "4399DD94.5070309@@euler.org" "20051209233303.GA13812@@gauss.org" "439B41ED.2080402@@euler.org" 439A1E03.3090604@@euler.org" "20051211184308.GB13513@@gauss.org") @end lisp @deffn {Scheme Procedure} thread-id message @end deffn Get the oldest reference for the message or its message-id if there is none. This is useful to identify the thread some message lives in. For example: @lisp (thread-id msg) => "439C1136.90504@@euler.org" @end lisp For example: @lisp (references msg) => ("439C1136.90504@@euler.org" "4399DD94.5070309@@euler.org" "20051209233303.GA13812@@gauss.org" "439B41ED.2080402@@euler.org" 439A1E03.3090604@@euler.org" "20051211184308.GB13513@@gauss.org") @end lisp @deffn {Scheme Procedure} mailing-list message @end deffn Get the mailing-list id for this message (corresponding with the @t{List-Id:} field), or @code{#f} if there is none. For example: @lisp (mailing-list msg) => "gnu-emacs-sources.gnu.org" @end lisp @c @deffn {Scheme Procedure} sexp message @c @end deffn @c Get the message's s-expression. @c @t{mu} caches an s-expression for each message; this was designed as an @c optimization for @t{mu4e}, but @t{mu-scm} uses it as well. The details of this @c s-expression (a property-list) are internal to @t{mu} (so do not base your next @c billion-dollar startup on it), but it can be useful for development and @c debugging. @node Miscellaneous @section Miscellaneous @defvar %options @end defvar An association-list (alist) of general options passed to @command{mu scm} or their default values. @lisp %options => ((mu-home . #f) (quiet . #f) (debug . #f) (verbose . #f)) @end lisp @c @defvar %preferences @c @end defvar @c An association list (alist) of user-preferences that influence interactive use. @c E.g., the way how certain things are displayed. The alist maps symbols to values: @c @itemize @c @item @code{short-date} @c a @code{strftime}-compatible string for the display format of short dates. @c @item @code{utc?} @c boolean, whether to assume UTC for dates/times, such as for @code{string->time} and @code{time->string} @c @end itemize @c @lisp @c %preferences @c ((short-date-format . "%F %T") (input-utc? . #f) (output-utc? . #f)) @c @end lisp @node Helpers @section Helpers @deffn {Scheme Procedure} string->time timestr [#:utc? (assoc-ref %preferences 'utc?)] @end deffn Convert some ISO-8601-style time-string to a seconds-since-epoch @t{time_t} value. @var{timestr} is expected to be in the @t{strftime}-format @t{%F%T}, or a prefix thereof. Non-numerical characters are ignored. You can influence whether UTC is assumed using the optional @code{#:utc?} parameter. The input time/date format is fixed. @c which uses @code{%preferences} for its default. @deffn {Scheme Procedure} time->string [#:format (assoc-ref %preferences 'short-date)] [#:utc? (assoc-ref %preferences 'utc?)] @end deffn Convert a @t{time_t} value (``seconds-since-epoch'') to a string. The optional @code{#:format} parameter (an @code{strftime}-compatible string) determines the output format, while the @code{#:utc?} determines whether to use UTC. @c Defaults are determined by the @code{%preferences} variable. If @var{time_t} is @t{#f}, return @code{#f}. @node GNU Free Documentation License @appendix GNU Free Documentation License @include fdl.texi @page @node Procedure Index @unnumbered Procedure Index This is an alphabetical list of all the public procedures and macros in @t{mu-scm}. @printindex fn @page @node Variable Index @unnumbered Variables Index This is an alphabetical list of all the public variables @t{mu-scm}. @printindex vr @bye