From: Carl Worth Date: Mon, 8 Nov 2010 18:08:27 +0000 (-0800) Subject: Merge in ruby bindings. X-Git-Tag: 0.5~50 X-Git-Url: https://git.notmuchmail.org/git?a=commitdiff_plain;h=44ea57a0d10ddab514abea319c4d25ec4e36b51e;hp=5c9e385591b66fa20cbb186393c48c52831a23b7;p=notmuch Merge in ruby bindings. Thanks to Ali Polatel for these bindings. This code was fetched from the ruby branch of: git://github.com/alip/notmuch.git --- diff --git a/Makefile b/Makefile index 619392d3..7549b40d 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ all: # List all subdirectories here. Each contains its own Makefile.local -subdirs = compat completion emacs lib +subdirs = compat completion emacs lib test # We make all targets depend on the Makefiles themselves. global_deps = Makefile Makefile.config Makefile.local \ diff --git a/Makefile.local b/Makefile.local index bc61a3c1..f9b5a9b3 100644 --- a/Makefile.local +++ b/Makefile.local @@ -31,18 +31,16 @@ GPG_FILE=$(SHA1_FILE).asc # Smash together user's values with our extra values FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CFLAGS) $(WARN_CFLAGS) $(CONFIGURE_CFLAGS) $(extra_cflags) FINAL_CXXFLAGS = $(CXXFLAGS) $(WARN_CXXFLAGS) $(CONFIGURE_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) -FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Llib -lnotmuch +FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Llib -lnotmuch $(AS_NEEDED_LDFLAGS) $(GMIME_LDFLAGS) $(TALLOC_LDFLAGS) FINAL_NOTMUCH_LINKER = CC ifneq ($(LINKER_RESOLVES_LIBRARY_DEPENDENCIES),1) FINAL_NOTMUCH_LDFLAGS += $(CONFIGURE_LDFLAGS) FINAL_NOTMUCH_LINKER = CXX endif -ifeq ($(PLATFORM),LINUX) ifeq ($(LIBDIR_IN_LDCONFIG),0) -FINAL_NOTMUCH_LDFLAGS += -Wl,--enable-new-dtags -Wl,-rpath,$(libdir) +FINAL_NOTMUCH_LDFLAGS += $(RPATH_LDFLAGS) endif -endif -FINAL_LIBNOTMUCH_LDFLAGS = $(LDFLAGS) $(CONFIGURE_LDFLAGS) +FINAL_LIBNOTMUCH_LDFLAGS = $(LDFLAGS) $(AS_NEEDED_LDFLAGS) $(CONFIGURE_LDFLAGS) .PHONY: all all: notmuch notmuch-shared notmuch.1.gz @@ -61,10 +59,6 @@ ifeq ($(shell cat .first-build-message 2>/dev/null),) endif endif -.PHONY: test -test: all - @./test/notmuch-test - $(TAR_FILE): git archive --format=tar --prefix=$(PACKAGE)-$(VERSION)/ HEAD > $(TAR_FILE).tmp echo $(VERSION) > version.tmp @@ -255,7 +249,8 @@ notmuch_client_srcs = \ notmuch-time.c \ query-string.c \ show-message.c \ - json.c + json.c \ + xutil.c notmuch_client_modules = $(notmuch_client_srcs:.c=.o) diff --git a/NEWS b/NEWS index 2d5e398f..bf98f462 100644 --- a/NEWS +++ b/NEWS @@ -1,32 +1,259 @@ +New command-line features +------------------------- +New "notmuch show --format=raw" for getting at original email contents + + This new feature allows for a fully-functional email client to be + built on top of the notmuch command-line without needing any direct + access to the mail store itself. + + For example, it's now possible to run "emacs -f notmuch" on a local + machine with only ssh access to the mail store/notmuch database. To + do this, simply set the notmuch-command variable in emacs to the + name of a script containing: + + ssh user@host notmuch "$@" + + If the ssh client has enabled connection sharing (ControlMaster + option in OpenSSH), the emacs interface can be quite responsive this + way. + +General bug fixes +----------------- +Fix "notmuch search" to print nothing when nothing matches + + The 0.4 release had a bug in which: + + notmuch search + + would produce a single blank line of output, (where previous + versions would produce no output. This fix also causes a change in + the --format=json output, (which would previously produce "[]" and + now produces nothing). + +Notmuch 0.4 (2010-11-01) +======================== +New command-line features +------------------------- +notmuch search --output=(summary|threads|messages|tags|files) + + This new option allows for particular items to be returned from + notmuch searches. The "summary" option is the default and behaves + just as "notmuch search" has historically behaved. + + The new option values allow for thread IDs, message IDs, lists of + tags, and lists of filenames to be returned from searches. It is + expected that this new option will be very useful in shell + scripts. For example: + + for file in $(notmuch search --output=files ); do + "$file" + done + +notmuch show --format=mbox + + This new option allows for the messages matching a search + specification to be presented as an mbox. Specifically the "mboxrd" + format is used which allows for reversible quoting of lines + beginning with "From ". A reader should remove a single '>' from the + beginning of all lines beginning with one or more '>' characters + followed by the 5 characters "From ". + +notmuch config [get|set]
. [value ...] + + The new top-level "config" command allows for any value in the + notmuch configuration file to be queried or set to a new value. Both + single-valued and multi-valued items are supported, as our any + custom items stored in the configuration file. + +Avoid setting Bcc header in "notmuch reply" + + We decided that this was a bit heavy-handed as the actual mail + user-agent should be responsible for setting any Bcc option. Also, + see below for the notmuch/emacs user-agent now setting an Fcc by + default rather than Bcc. + +New library features +-------------------- +Add notmuch_query_get_query_string and notmuch_query_get_sort + + These are simply functions for querying properties of a + notmuch_query_t object. + New emacs features ------------------ -Add a new, optional hook for detecting inline patches +Enable Fcc of all sent messages by default (to "sent" directory) + + All messages sent from the emacs interface will now be saved to the + notmuch mail store where they will be incorporated to the database + by the next "notmuch new". By default, messages are saved to the + "sent" directory at the top-level of the mail store. This directory + can be customized by means of the "Notmuch Fcc Dirs" option in the + notmuch customize interface. + +Ability to all open messages in a thread to a pipe + + Historically, the '|' keybinding allows for piping a single message + to an external command. Now, by prefixing this key with a prefix + argument, (for example, by pressing "Control-U |"), all open + messages in the current thread will be sent to the external command. + +Optional support for detecting inline patches This hook is disabled by default but can be enabled with a checkbox - under ""Notmuch Show Insert Text/Plain Hook" in the notmuch - customize interface. It allows for inline patches to be detected and - treated as if they were attachments, (with context-sensitive - highlighting). + under "Notmuch Show Insert Text/Plain Hook" in the notmuch customize + interface. It allows for inline patches to be detected and treated + as if they were attachments, (with context-sensitive highlighting). Automatically tag messages as "replied" when sending a reply - This feature adds a "replied" tag by default, but can easily be - customized to add or remove other tags as well. For example, a user - might use a tag of "needs-reply" and can configure this feature to - automatically remove that tag when replying. See "Notmuch Message - Mark Replied" in the notmuch customize interface. + Messages replied to within the emacs interface will now be tagged as + "replied". This feature can easily be customized to add or remove + other tags as well. For example, a user might use a tag of + "needs-reply" and can configure this feature to automatically remove + that tag when replying. See "Notmuch Message Mark Replied" in the + notmuch customize interface. + +Allow search-result color specifications to overlay each other + + For example, one tag can specify the background color of matching + lines, while another can specify the foreground. With this change, + both settings will now be visible simultaneously, (which was not the + case in previous releases). See "Notmuch Search Line Faces" in the + notmuch customize interface. + +Make hidden author names still available for incremental search. + + When there is insufficient space to display all authors of a thread + in search results, the names of hidden authors are now still made + available to emacs' incremental search commands. As the user + searches, matching lines will temporarily expand to show the hidden + names. -Emacs mail improvements +New binding of Control-TAB (works like TAB in reverse) + + Many notmuch nodes already use TAB to navigate forward through + various items allowing actions, (message headers, email attachments, + etc.). The new Control-TAB binding operates similarly but in the + opposite direction. + +New build-system features +------------------------- +Various portability fixes have been applied + + These include fixes for build failures on at least Solaris, FreeBSD, + and Fedora systems. We're hopeful that the notmuch code base is now + more portable than ever before. + +Arrange for libnotmuch to be found automatically after make install + + The notmuch build system is now careful to help the user avoid + errors of the form "libnotmuch.so could not be found" immediately + after installing. This support takes two forms: + + 1. If the library is installed to a system directory, + (configured in /etc/ld.so.conf), then "make install" will + automatically run ldconfig. + + 2. If the library is installed to a non-system directory, the + build system adds a DR_RUNPATH entry to the final binary + pointing to the directory to which the library is installed. + + When this support works, the user should be able to run notmuch + immediately after "make install", without any errors trying to find + the notmuch library, and without having to manually set environment + variables such as LD_LIBRARY_PATH. + +Check compiler/linker options before using them + + The configure script now carefully checks that any desired + compilation options, (whether for enabling compiler warnings, or for + embedding rpath, etc.), are supported. Only supported options are + used in the resulting Makefile. + +New test-suite features ----------------------- +New modularization of test suite. + + Thanks to a gracious relicensing of the test-suite infrastructure + from the git project, notmuch now has a modular test suite. This + provides the ability to run individual sections of the test suite + rather than the whole things. It also provides better summary of + test results, with support for tests that are expected to fail + (BROKEN and FIXED) in addition to PASS and FAIL. Finally, it makes + it easy to run the test suite within valgrind (pass --valgrind to + notmuch-test or to any sub-script) which has been very useful. + +New testing of emacs interface. + + The test suite has been augmented to allow automated testing of the + emacs interfaces. So far, this includes basic searches, display of + threads, and tag manipulation. This also includes a test that a new + message can successfully be sent out through a (dummy) SMTP server + and that said message is successfully integrated into the notmuch + database via the FCC setting. + +General bug fixes +----------------- +Fix potential corruption of database when "notmuch new " is interrupted. + + Previously, an interruption of "notmuch new" would (rarely) result + in a corrupt database. The corruption would manifest itself by a + persistent error of the form: + + document ID of 1234 has no thread ID + + The message-adding code has been carefully audited and reworked to + avoid this sort of corruption regardless of when it is interrupted. + +Fix failure with extremely long message ID headers. + + Previously, a message with an extremely long message ID, (say, more + than 300 characters), would fail to be added to notmuch, (triggering + Xapian exceptions). This has now been fixed. + +Fix for messages with "charset=unknown-8bit" + + Previously, messages with this charset would cause notmuch to emit a + GMime warning, (which would then trip up emacs or other interfaces + parsing the notmuch results). + +Fix notmuch_query_search_threads function to return NULL on any exception + +Fix "notmuch search" to return non-zero if notmuch_query_search_threads fails + + Previously, this command could confusingly report a Xapian + exception, yet still return an error code of 0. It now correctly + returns a failing error code of 1 in this case. + +Emacs bug fixes +--------------- +Fix to handle a message with a subject containing, for example "[1234]" + + Previously, a message subject containing a sequence of digits within + square brackets would cause the emacs interface to mis-parse the + output of "notmuch search". This would result in the message being + mis-displayed and prevent the user from manipulating the message in + the emacs interface. + +Fix to correctly handle message IDs containing ".." + + The emacs interface now properly quotes message IDs to avoid a + Xapian bug in which the ".." within a message ID would be + misinterpreted as a numeric range specification. + +Python-binding fixes +-------------------- +The python bindings for notmuch have been updated to work with python3. + +Debian-specific fixes +--------------------- +Fix emacs initialization so "M-x notmuch" works for users by default. + + Now, a new Debian user can immediately run "emacs -f notmuch" after + "apt-get install notmuch". Previously, the user would have had to + edit the ~/.emacs file to add "(require 'notmuch)" before this would + work. -Easier way to define a fcc directory - - In the common case that a user only has one FCC (save outgoing mail - in the Mail directory, it is now possible to simply configure a - string such as "Sent" in the notmuch-fcc-dirs variable. More complex - options, depending on a users email address, are possible and - described in the variable customization help text. - Notmuch 0.3.1 (2010-04-27) ========================== General bug fixes diff --git a/RELEASING b/RELEASING index e9cb3d0b..6c714f8e 100644 --- a/RELEASING +++ b/RELEASING @@ -48,10 +48,10 @@ repository. From here, there are just a few steps to release: as "1.1" or "1.2"). Finally, releases that do not change "features" but are merely - bug fixes either add increase the micro number or add it - (starting at ".1" if not present). So a bug-fix release from - "1.0" would be "1.0.1" and a subsequent bug-fix release would - be "1.0.2" etc. + bug fixes either increase the micro number or add it (starting + at ".1" if not present). So a bug-fix release from "1.0" would + be "1.0.1" and a subsequent bug-fix release would be "1.0.2" + etc. Commit this change. diff --git a/TODO b/TODO index f65e59ab..9834d73a 100644 --- a/TODO +++ b/TODO @@ -6,8 +6,15 @@ Fix the things that are causing the most pain to new users Emacs interface (notmuch.el) ---------------------------- -Enhance '+' and '-' in the search view to operate on an entire region -if set. +Add notmuch-bcc and notmuch-cc for setting default Bcc and Cc values, +(should affect the message-setup-hook). + +Switch the notmuch-search view to use "notmuch search --format=json" +to fix large classes of bugs regarding poorly-escaped output and lame +regular expressions. (The most recently found, unfixed example is the +sender's name containing ';' which causes emacs to drop a search +result.) This may require removing the outer array from the current +"notmuch search --format=json" results. Fix '*' to work by simply calling '+' or '-' on a region consisting of the entire buffer. @@ -31,8 +38,6 @@ Add support to "mute" a thread (add a "muted" tag and then don't display threads in searches by default where any message of the thread has the "muted" tag). -Fix i-search to open up invisible citations as necessary. - Make '=' count from the end rather than from the beginning if more than half-way through the buffer. @@ -41,10 +46,6 @@ sending. This should probably just be fixed in message-mode itself, (but perhaps we can have a notmuch-message-mode that layers this on top). -Implement Fcc and use it for all messages, (whether a new composition, -a reply, or a forward). This again may require a notmuch-message-mode -that extends message-mode. - Stop hiding the headers so much in the thread-view mode. Allow opening a message in thread-view mode by clicking on either @@ -56,65 +57,9 @@ Change 'a' command in thread-view mode to only archive open messages. Add a binding to open all closed messages. -Make all authors and subjects available to isearch, (hidden by default -but with magic expansion while isearching). - -Fix notmuch-hello as follows: - - 1. Change the "notmuch" and message count in the welcome sentence to - not be buttons. - - 2. Put the saved searches (if any) before the search bar. - - 3. When `notmuch-hello' (or even `notmuch' when it gets its new name) - is invoked directly, move to the first button, (go to point-min and - then call widget-forward). That is, if the user has any saved - searches, then point will be on the first one. If the user has no - saved searches, then point will be on the search bar. - - 4. Fix refresh of notmuch-hello to leave point in the same logical - place, (the same saved-search widget at least). - Change the 'a'rchive command in the thread view to only archive open messages. -Emacs saved-search interface ----------------------------- -Here's a proposal Carl wrote (id:87einafy4u.fsf@yoom.home.cworth.org): - - So what I'm imagining for the default notmuch view is something like - this: - - Welcome to notmuch. - - Notmuch search: _________________________________________ - - Saved searches: - - 55,342 All messages - 22 Inbox - - Recent searches: - - 1 from:"someone special" and tag:unread - 34 tag:notmuch and tag:todo - - Click (or press Enter) on any search to see the results. - Right-click (or press Space) on any recent search to save it. - - So the "saved searches" portion of the view is basically just what - notmuch-folder displays now. Above that there's an obvious place to - start a new search, (in a slightly more "web-browser-like" way than the - typical mini-buffer approach). - - All recent searches appear in the list at the bottom automatically, and - there's the documented mechanism for saving a search, (giving it a name - and having it appear above). - -Portability ------------ -Fix configure script to test each compiler warning we want to use. - Completion ---------- Fix bash completion to complete multiple search options (both --first @@ -123,6 +68,13 @@ and *then* --max-threads), and also complete value for --sort= notmuch command-line tool ------------------------- +Replace "notmuch reply" with "notmuch compose --reply ". +This would enable a plain "notmuch compose" to be used to construct an +initial message, (which would then have the properly configured name +and email address in the From: line. We could also then easily support +"notmuch compose --from " to support getting at alternate +email addresses. + Fix the --format=json option to not imply --entire-thread. Implement "notmuch search --exclude-threads=" to allow @@ -136,21 +88,9 @@ option (or similar) to "notmuch show".) For now, this is being worked around in the emacs interface by noticing that "notmuch show" returns nothing and re-rerunning the command without the extra arguments. -Teach "notmuch search" to return many different kinds of results. Some -ideas: - - notmuch search --output=threads # Default if no --output is given - notmuch search --output=messages - notmuch search --output=tags - notmuch search --output=addresses - notmuch search --output=terms - Add a "--format" option to "notmuch search", (something printf-like for selecting what gets printed). -Add a "--count-only" (or so?) option to "notmuch search" for returning -the count of search results. - Give "notmuch restore" some progress indicator. Fix "notmuch restore" to operate in a single pass much like "notmuch @@ -202,8 +142,6 @@ Make failure to read a file (such as a permissions problem) a warning rather than an error (should be similar to the existing warning for a non-mail file). -Actually compile and install a libnotmuch shared library. - Fix to use the *last* Message-ID header if multiple such headers are encountered, (I noticed this is one thing that kept me from seeing the same message-ID values as sup). @@ -288,7 +226,7 @@ fix old messages to be consistent. Start indexing the List-Id header, (and re-index this header for existing messages at the next database upgrade). -Start indexing the message file's directory ana make it available for +Start indexing the message file's directory and make it available for search as "folder:" (and re-index this value for existing messages at the next database upgrade). @@ -297,24 +235,14 @@ re-index these for existing messages at the next database upgrade). Test suite ---------- -Start testing --format=json. - Achieve 100% test coverage with the test suite. -Modularize test suite (to be able to run individual tests). - -Summarize test results at the end. - -Fix the insane quoting nightmare of the test suite, (and once we do -that we can actually test the implicit-phrase search feature such as -"notmuch search 'body search (phrase)'" - -Test "notmuch reply" choosing the correct email address from the -Received header when no configured email address appears in To or Cc. - General ------- Audit everything for dealing with out-of-memory (and drop xutil.c). Investigate why the notmuch database is slightly larger than the sup database for the same corpus of email. + +Makefile should print message teaching user about LD_LIBRARY_PATH (or +similar) if libdir is not set to a directory examined by ldconfig. diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py index 56a4f2a4..5ba1337f 100644 --- a/bindings/python/notmuch/__init__.py +++ b/bindings/python/notmuch/__init__.py @@ -51,10 +51,10 @@ along with notmuch. If not, see . Copyright 2010 Sebastian Spaeth ' """ -from database import Database, Query -from message import Messages, Message -from thread import Threads, Thread -from tag import Tags +from notmuch.database import Database, Query +from notmuch.message import Messages, Message +from notmuch.thread import Threads, Thread +from notmuch.tag import Tags from notmuch.globals import nmlib, STATUS, NotmuchError __LICENSE__="GPL v3+" __VERSION__='0.2.2' diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index 613cc4ab..ac85cbb4 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -748,7 +748,7 @@ class Message(object): output += "\n\fbody{" parts = format["body"] - parts.sort(key=lambda(p): p["id"]) + parts.sort(key=lambda x: x['id']) for p in parts: if not p.has_key("filename"): output += "\n\fpart{ " diff --git a/configure b/configure index c86a227c..bab25016 100755 --- a/configure +++ b/configure @@ -15,7 +15,7 @@ CXX=${CXX:-g++} CFLAGS=${CFLAGS:--O2} CXXFLAGS=${CXXFLAGS:-\$(CFLAGS)} LDFLAGS=${LDFLAGS:-} -XAPIAN_CONFIG=${XAPIAN_CONFIG:-xapian-config-1.1${tab}xapian-config} +XAPIAN_CONFIG=${XAPIAN_CONFIG:-xapian-config} # We don't allow the EMACS or GZIP Makefile variables inherit values # from the environment as we do with CC and CXX above. The reason is @@ -275,21 +275,21 @@ fi libdir_in_ldconfig=0 printf "Checking which platform we are on... " -if [ `uname` = "Darwin" ] ; then +uname=`uname` +if [ $uname = "Darwin" ] ; then printf "Mac OS X.\n" platform=MACOSX linker_resolves_library_dependencies=0 -elif [ `uname` = "SunOS" ] ; then +elif [ $uname = "SunOS" ] ; then printf "Solaris.\n" platform=SOLARIS linker_resolves_library_dependencies=0 -elif [ `uname` = "Linux" ] ; then +elif [ $uname = "Linux" ] ; then printf "Linux\n" platform=LINUX linker_resolves_library_dependencies=1 ldconfig_paths=$(/sbin/ldconfig -N -X -v 2>/dev/null | sed -n -e 's,^\(/.*\):\( (.*)\)\?$,\1,p') for path in $ldconfig_paths; do - echo "Checking $path compared to $libdir_expanded" if [ "$path" = "$libdir_expanded" ]; then libdir_in_ldconfig=1 fi @@ -390,6 +390,50 @@ else fi rm -f compat/have_strcasestr +printf "int main(void){return 0;}\n" > minimal.c + +printf "Checking for rpath support... " +if ${CC} -Wl,--enable-new-dtags -Wl,-rpath,/tmp/ -o minimal minimal.c >/dev/null 2>&1 +then + printf "Yes.\n" + rpath_ldflags="-Wl,--enable-new-dtags -Wl,-rpath,\$(libdir)" +else + printf "No (nothing to worry about).\n" + rpath_ldflags="" +fi + +printf "Checking for -Wl,--as-needed... " +if ${CC} -Wl,--as-needed -o minimal minimal.c >/dev/null 2>&1 +then + printf "Yes.\n" + as_needed_ldflags="-Wl,--as-needed" +else + printf "No (nothing to worry about).\n" + as_needed_ldflags="" +fi + +WARN_CXXFLAGS="" +printf "Checking for available C++ compiler warning flags... " +for flag in -Wall -Wextra -Wwrite-strings -Wswitch-enum; do + if ${CC} $flag -o minimal minimal.c > /dev/null 2>&1 + then + WARN_CXXFLAGS="${WARN_CXXFLAGS}${WARN_CXXFLAGS:+ }${flag}" + fi +done +printf "\n\t${WARN_CXXFLAGS}\n" + +WARN_CFLAGS="${WARN_CXXFLAGS}" +printf "Checking for available C compiler warning flags... " +for flag in -Wmissing-declarations; do + if ${CC} $flag -o minimal minimal.c > /dev/null 2>&1 + then + WARN_CFLAGS="${WARN_CFLAGS}${WARN_CFLAGS:+ }${flag}" + fi +done +printf "\n\t${WARN_CFLAGS}\n" + +rm -f minimal minimal.c + cat < + * new: notmuch config [get|set]
. [value ...] + * lib: Add notmuch_query_get_query_string and notmuch_query_get_sort + * emacs: Enable Fcc of all sent messages by default (to "sent" directory) + * emacs: Ability to all open messages in a thread to a pipe + * emacs: Optional support for detecting inline patches + * emacs: Automatically tag messages as "replied" when sending a reply + * emacs: Allow search-result color specifications to overlay each other + * emacs: Make hidden author names still available for incremental search. + * emacs: New binding of Control-TAB (works like TAB in reverse) + * test: New modularization of test suite. + * test: New testing of emacs interface. + * bugfix: Avoid setting Bcc header in "notmuch reply" + * bugfix: Avoid corruption of database when "notmuch new " is interrupted. + * bugfix: Fix failure with extremely long message ID headers. + * bugfix: Fix for messages with "charset=unknown-8bit" + * bugfix: Fix notmuch_query_search_threads to return NULL on any exception + * bugfix: Fix "notmuch search" to return non-zero on any exception + * emacs bugfix: Fix for message with a subject containing, "[1234]" + * emacs bugfix: Fix to correctly handle message IDs containing ".." + * emacs bugfix: Fix initialization so "M-x notmuch" works by default. + + -- Carl Worth Mon, 01 Nov 2010 16:23:47 -0700 + notmuch (0.3.1) unstable; urgency=low * Fix an infinite loop in "notmuch reply" diff --git a/debian/control b/debian/control index e4c61ab3..3491e9a4 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,7 @@ Priority: extra Maintainer: Carl Worth Uploaders: Jameson Graef Rollins , martin f. krafft Build-Depends: debhelper (>= 7.0.50~), pkg-config, libxapian-dev, libgmime-2.4-dev, libtalloc-dev, libz-dev, emacs (>= 23~) -Standards-Version: 3.8.4 +Standards-Version: 3.9.1.0 Homepage: http://notmuchmail.org/ Vcs-Git: git://notmuchmail.org/git/notmuch Vcs-Browser: http://git.notmuchmail.org/git/notmuch diff --git a/debian/libnotmuch1.symbols b/debian/libnotmuch1.symbols new file mode 100644 index 00000000..88ed761b --- /dev/null +++ b/debian/libnotmuch1.symbols @@ -0,0 +1,79 @@ +libnotmuch.so.1 libnotmuch1 #MINVER# + _ZTIN6Xapian10LogicErrorE@Base 0.3 + _ZTIN6Xapian12RuntimeErrorE@Base 0.3 + _ZTIN6Xapian16DocNotFoundErrorE@Base 0.3 + _ZTIN6Xapian20InvalidArgumentErrorE@Base 0.3 + _ZTIN6Xapian5ErrorE@Base 0.3 + _ZTSN6Xapian10LogicErrorE@Base 0.3 + _ZTSN6Xapian12RuntimeErrorE@Base 0.3 + _ZTSN6Xapian16DocNotFoundErrorE@Base 0.3 + _ZTSN6Xapian20InvalidArgumentErrorE@Base 0.3 + _ZTSN6Xapian5ErrorE@Base 0.3 + notmuch_database_add_message@Base 0.3 + notmuch_database_close@Base 0.3 + notmuch_database_create@Base 0.3 + notmuch_database_find_message@Base 0.3 + notmuch_database_get_all_tags@Base 0.3 + notmuch_database_get_directory@Base 0.3 + notmuch_database_get_path@Base 0.3 + notmuch_database_get_version@Base 0.3 + notmuch_database_needs_upgrade@Base 0.3 + notmuch_database_open@Base 0.3 + notmuch_database_remove_message@Base 0.3 + notmuch_database_upgrade@Base 0.3 + notmuch_directory_destroy@Base 0.3 + notmuch_directory_get_child_directories@Base 0.3 + notmuch_directory_get_child_files@Base 0.3 + notmuch_directory_get_mtime@Base 0.3 + notmuch_directory_set_mtime@Base 0.3 + notmuch_filenames_destroy@Base 0.3 + notmuch_filenames_get@Base 0.3 + notmuch_filenames_move_to_next@Base 0.3 + notmuch_filenames_valid@Base 0.3 + notmuch_message_add_tag@Base 0.3 + notmuch_message_destroy@Base 0.3 + notmuch_message_freeze@Base 0.3 + notmuch_message_get_date@Base 0.3 + notmuch_message_get_filename@Base 0.3 + notmuch_message_get_flag@Base 0.3 + notmuch_message_get_header@Base 0.3 + notmuch_message_get_message_id@Base 0.3 + notmuch_message_get_replies@Base 0.3 + notmuch_message_get_tags@Base 0.3 + notmuch_message_get_thread_id@Base 0.3 + notmuch_message_remove_all_tags@Base 0.3 + notmuch_message_remove_tag@Base 0.3 + notmuch_message_set_flag@Base 0.3 + notmuch_message_thaw@Base 0.3 + notmuch_messages_collect_tags@Base 0.3 + notmuch_messages_destroy@Base 0.3 + notmuch_messages_get@Base 0.3 + notmuch_messages_move_to_next@Base 0.3 + notmuch_messages_valid@Base 0.3 + notmuch_query_count_messages@Base 0.3 + notmuch_query_create@Base 0.3 + notmuch_query_destroy@Base 0.3 + notmuch_query_get_query_string@Base 0.4 + notmuch_query_get_sort@Base 0.4 + notmuch_query_search_messages@Base 0.3 + notmuch_query_search_threads@Base 0.3 + notmuch_query_set_sort@Base 0.3 + notmuch_status_to_string@Base 0.3 + notmuch_tags_destroy@Base 0.3 + notmuch_tags_get@Base 0.3 + notmuch_tags_move_to_next@Base 0.3 + notmuch_tags_valid@Base 0.3 + notmuch_thread_destroy@Base 0.3 + notmuch_thread_get_authors@Base 0.3 + notmuch_thread_get_matched_messages@Base 0.3 + notmuch_thread_get_newest_date@Base 0.3 + notmuch_thread_get_oldest_date@Base 0.3 + notmuch_thread_get_subject@Base 0.3 + notmuch_thread_get_tags@Base 0.3 + notmuch_thread_get_thread_id@Base 0.3 + notmuch_thread_get_toplevel_messages@Base 0.3 + notmuch_thread_get_total_messages@Base 0.3 + notmuch_threads_destroy@Base 0.3 + notmuch_threads_get@Base 0.3 + notmuch_threads_move_to_next@Base 0.3 + notmuch_threads_valid@Base 0.3 diff --git a/debian/notmuch.emacsen-startup b/debian/notmuch.emacsen-startup index 25a527f5..60bf9968 100644 --- a/debian/notmuch.emacsen-startup +++ b/debian/notmuch.emacsen-startup @@ -20,4 +20,9 @@ (concat "/usr/share/" (symbol-name debian-emacs-flavor) "/site-lisp/notmuch")) + (autoload 'notmuch "notmuch" "Run notmuch and display saved searches, known tags, etc." t) + (autoload 'notmuch-hello "notmuch" "Run notmuch and display saved searches, known tags, etc." t) + (autoload 'notmuch-search "notmuch" "Run \"notmuch search\" with the given query string and display results." t) + (autoload 'notmuch-show "notmuch" "Run \"notmuch show\" with the given thread ID and display results." t) + )) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index f8ae332c..e58dd24e 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -19,9 +19,9 @@ ;; ;; Authors: David Edmondson +(eval-when-compile (require 'cl)) (require 'widget) (require 'wid-edit) ; For `widget-forward'. -(require 'cl) (require 'notmuch-lib) (require 'notmuch-mua) @@ -115,6 +115,7 @@ Typically \",\" in the US and UK and \".\" in Europe." (while (> n 0) (push (% n 1000) result) (setq n (/ n 1000))) + (setq result (or result '(0))) (apply #'concat (number-to-string (car result)) (mapcar (lambda (elem) @@ -209,11 +210,12 @@ should be. Returns a cons cell `(tags-per-line width)'." ;; after the name. (+ 9 1 widest))))))) - (cons tags-per-line (/ (- (window-width) notmuch-hello-indent - ;; Count is 9 wide (8 digits plus - ;; space), 1 for the space after the - ;; name. - (* tags-per-line (+ 9 1))) + (cons tags-per-line (/ (max 1 + (- (window-width) notmuch-hello-indent + ;; Count is 9 wide (8 digits plus + ;; space), 1 for the space after the + ;; name. + (* tags-per-line (+ 9 1)))) tags-per-line)))) (defun notmuch-hello-insert-tags (tag-alist widest target) @@ -249,7 +251,9 @@ should be. Returns a cons cell `(tags-per-line width)'." ;; can just insert `(- widest (length name))' spaces - ;; the column separator is included in the button if ;; `(equal widest (length name)'. - (widget-insert (make-string (- widest (length name)) ? )))) + (widget-insert (make-string (max 1 + (- widest (length name))) + ? )))) (setq count (1+ count)) (if (eq (% count tags-per-line) 0) (widget-insert "\n"))) @@ -290,7 +294,7 @@ should be. Returns a cons cell `(tags-per-line width)'." (define-key map "v" '(lambda () "Display the notmuch version" (interactive) (message "notmuch version %s" (notmuch-version)))) (define-key map "?" 'notmuch-help) - (define-key map "q" 'kill-this-buffer) + (define-key map "q" 'notmuch-kill-this-buffer) (define-key map "=" 'notmuch-hello-update) (define-key map "G" 'notmuch-hello-poll-and-update) (define-key map (kbd "") 'widget-backward) @@ -314,7 +318,9 @@ Complete list of currently available key bindings: ;;(setq buffer-read-only t) ) +;;;###autoload (defun notmuch-hello (&optional no-display) + "Run notmuch and display saved searches, known tags, etc." (interactive) ; Jump through a hoop to get this value from the deprecated variable @@ -378,7 +384,7 @@ Complete list of currently available key bindings: :help-echo "Refresh" (notmuch-hello-nice-number (string-to-number (car (process-lines notmuch-command "count"))))) - (widget-insert " messages (that's not much mail).\n")) + (widget-insert " messages.\n")) (let ((found-target-pos nil) (final-target-pos nil)) @@ -510,7 +516,6 @@ Complete list of currently available key bindings: (unless (widget-at) (notmuch-hello-goto-search))))) -;;;###autoload (defun notmuch-folder () "Deprecated function for invoking notmuch---calling `notmuch' is preferred now." (interactive) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index f30bcb42..dfdcd052 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -68,6 +68,30 @@ the user hasn't set this variable with the old or new value." (match-string 2 long-string) "unknown"))) +(defun notmuch-config-get (item) + "Return a value from the notmuch configuration." + ;; Trim off the trailing newline + (substring (shell-command-to-string + (concat notmuch-command " config get " item)) + 0 -1)) + +(defun notmuch-database-path () + "Return the database.path value from the notmuch configuration." + (notmuch-config-get "database.path")) + +(defun notmuch-user-name () + "Return the user.name value from the notmuch configuration." + (notmuch-config-get "user.name")) + +(defun notmuch-user-primary-email () + "Return the user.primary_email value from the notmuch configuration." + (notmuch-config-get "user.primary_email")) + +(defun notmuch-kill-this-buffer () + "Kill the current buffer." + (interactive) + (kill-buffer (current-buffer))) + ;; ;; XXX: This should be a generic function in emacs somewhere, not diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index 8bb41a89..693d8d42 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -18,9 +18,11 @@ (require 'message) +(require 'notmuch-lib) + (defvar notmuch-maildir-fcc-count 0) -(defcustom notmuch-fcc-dirs nil +(defcustom notmuch-fcc-dirs "sent" "Determines the maildir directory to save outgoing mails in. If set to non-nil, this will cause message mode to file your @@ -43,10 +45,9 @@ used. The first entry is used as a default fallback when nothing else matches. - In all cases, the complete FCC directory will be constructed by - concatenating the content of the variable 'message-directory' - ('~/Mail/' by default and customizable via M-x - customize-variablemessage-directory) and this value. + In all cases, a relative FCC directory will be understood to + specify a directory within the notmuch mail store, (as set by + the database.path option in the notmuch configuration file). You will be prompted to create the directory if it does not exist yet when sending a mail. @@ -67,7 +68,7 @@ '(lambda (destdir) (notmuch-maildir-fcc-write-buffer-to-maildir destdir t))) ;;add a hook to actually insert the Fcc header when sending - (add-hook 'message-send-hook 'notmuch-fcc-header-setup)) + (add-hook 'message-header-setup-hook 'notmuch-fcc-header-setup)) (defun notmuch-fcc-header-setup () "Adds an appropriate fcc header to the current mail buffer @@ -90,8 +91,9 @@ ;; if there is no fcc header yet, add ours (unless (message-fetch-field "fcc") (message-add-header (concat "Fcc: " - (file-name-as-directory message-directory) - subdir))) + (if (= (elt subdir 0) ?/) + subdir + (concat (notmuch-database-path) "/" subdir))))) ;; finally test if fcc points to a valid maildir (let ((fcc-header (message-fetch-field "fcc"))) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 0ad079ff..dc7b386f 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -126,6 +126,10 @@ list." (when (not (string= "" user-agent)) (push (cons "User-Agent" user-agent) other-headers)))) + (unless (mail-header 'from other-headers) + (push (cons "From" (concat + (notmuch-user-name) " <" (notmuch-user-primary-email) ">")) other-headers)) + (message-mail to subject other-headers continue switch-function yank-action send-actions) (message-sort-headers) diff --git a/emacs/notmuch-query.el b/emacs/notmuch-query.el index 0d6e7759..26f95447 100644 --- a/emacs/notmuch-query.el +++ b/emacs/notmuch-query.el @@ -47,7 +47,7 @@ is a possibly empty forest of replies. (apply 'append (mapcar (lambda (tree) - (funcall mapper fn tree)) + (funcall mapper function tree)) seq))) (defun notmuch-query-map-threads (fn threads) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index f872cdfe..70121254 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -21,7 +21,7 @@ ;; Authors: Carl Worth ;; David Edmondson -(require 'cl) +(eval-when-compile (require 'cl)) (require 'mm-view) (require 'message) (require 'mm-decode) @@ -85,10 +85,10 @@ any given message." (defmacro with-current-notmuch-show-message (&rest body) "Evaluate body with current buffer set to the text of current message" `(save-excursion - (let ((filename (notmuch-show-get-filename))) - (let ((buf (generate-new-buffer (concat "*notmuch-msg-" filename "*")))) + (let ((id (notmuch-show-get-message-id))) + (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*")))) (with-current-buffer buf - (insert-file-contents filename nil nil nil t) + (call-process notmuch-command nil t nil "show" "--format=raw" id) ,@body) (kill-buffer buf))))) @@ -555,7 +555,7 @@ function is used. " (defvar notmuch-show-mode-map (let ((map (make-sparse-keymap))) (define-key map "?" 'notmuch-help) - (define-key map "q" 'kill-this-buffer) + (define-key map "q" 'notmuch-kill-this-buffer) (define-key map (kbd "") 'widget-backward) (define-key map (kbd "M-TAB") 'notmuch-show-previous-button) (define-key map (kbd "") 'notmuch-show-previous-button) @@ -586,7 +586,6 @@ function is used. " "Keymap for \"notmuch show\" buffers.") (fset 'notmuch-show-mode-map notmuch-show-mode-map) -;;;###autoload (defun notmuch-show-mode () "Major mode for viewing a thread with notmuch. @@ -597,21 +596,17 @@ By default, various components of email messages, (citations, signatures, already-read messages), are hidden. You can make these parts visible by clicking with the mouse button or by pressing RET after positioning the cursor on a hidden part, (for -which \\[notmuch-show-next-button] and -\\[notmuch-show-previous-button] are helpful). +which \\[notmuch-show-next-button] and \\[notmuch-show-previous-button] are helpful). Reading the thread sequentially is well-supported by pressing -\\[notmuch-show-advance-and-archive]. This will scroll the -current message (if necessary), advance to the next message, or -advance to the next thread (if already on the last message of a -thread). +\\[notmuch-show-advance-and-archive]. This will scroll the current message (if necessary), advance +to the next message, or advance to the next thread (if already on +the last message of a thread). Other commands are available to read or manipulate the thread -more selectively, (such as '\\[notmuch-show-next-message]' and -'\\[notmuch-show-previous-message]' to advance to messages -without removing any tags, and '\\[notmuch-show-archive-thread]' -to archive an entire thread without scrolling through with -\\[notmuch-show-advance-and-archive]). +more selectively, (such as '\\[notmuch-show-next-message]' and '\\[notmuch-show-previous-message]' to advance to messages +without removing any tags, and '\\[notmuch-show-archive-thread]' to archive an entire thread +without scrolling through with \\[notmuch-show-advance-and-archive]). You can add or remove arbitary tags from the current message with '\\[notmuch-show-add-tag]' or '\\[notmuch-show-remove-tag]'. @@ -730,7 +725,7 @@ All currently available key bindings: (defun notmuch-show-get-message-id () "Return the message id of the current message." - (concat "id:" (notmuch-show-get-prop :id))) + (concat "id:\"" (notmuch-show-get-prop :id) "\"")) ;; dme: Would it make sense to use a macro for many of these? @@ -778,6 +773,22 @@ All currently available key bindings: "Mark the current message as read." (notmuch-show-remove-tag "unread")) +;; Functions for getting attributes of several messages in the current +;; thread. + +(defun notmuch-show-get-message-ids-for-open-messages () + "Return a list of all message IDs for open messages in the current thread." + (save-excursion + (let (message-ids done) + (goto-char (point-min)) + (while (not done) + (if (notmuch-show-message-visible-p) + (setq message-ids (append message-ids (list (notmuch-show-get-message-id))))) + (setq done (not (notmuch-show-goto-message-next))) + ) + message-ids + ))) + ;; Commands typically bound to keys. (defun notmuch-show-advance-and-archive () @@ -906,42 +917,84 @@ any effects from previous calls to (defun notmuch-show-view-raw-message () "View the file holding the current message." (interactive) - (view-file (notmuch-show-get-filename))) + (let ((id (notmuch-show-get-message-id))) + (let ((buf (get-buffer-create (concat "*notmuch-raw-" id "*")))) + (switch-to-buffer buf) + (save-excursion + (call-process notmuch-command nil t nil "show" "--format=raw" id))))) -(defun notmuch-show-pipe-message (command) - "Pipe the contents of the current message to the given command. +(defun notmuch-show-pipe-message (entire-thread command) + "Pipe the contents of the current message (or thread) to the given command. The given command will be executed with the raw contents of the current email message as stdin. Anything printed by the command -to stdout or stderr will appear in the *Messages* buffer." - (interactive "sPipe message to command: ") - (apply 'start-process-shell-command "notmuch-pipe-command" "*notmuch-pipe*" - (list command " < " - (shell-quote-argument (notmuch-show-get-filename))))) +to stdout or stderr will appear in the *Messages* buffer. + +When invoked with a prefix argument, the command will receive all +open messages in the current thread (formatted as an mbox) rather +than only the current message." + (interactive "P\nsPipe message to command: ") + (let (shell-command) + (if entire-thread + (setq shell-command + (concat notmuch-command " show --format=mbox " + (shell-quote-argument + (mapconcat 'identity (notmuch-show-get-message-ids-for-open-messages) " OR ")) + " | " command)) + (setq shell-command + (concat notmuch-command " show --format=raw " + (shell-quote-argument (notmuch-show-get-message-id)) " | " command))) + (start-process-shell-command "notmuch-pipe-command" "*notmuch-pipe*" shell-command))) + +(defun notmuch-show-add-tags-worker (current-tags add-tags) + "Add to `current-tags' with any tags from `add-tags' not +currently present and return the result." + (let ((result-tags (copy-sequence current-tags))) + (mapc (lambda (add-tag) + (unless (member add-tag current-tags) + (setq result-tags (push add-tag result-tags)))) + add-tags) + (sort result-tags 'string<))) + +(defun notmuch-show-del-tags-worker (current-tags del-tags) + "Remove any tags in `del-tags' from `current-tags' and return +the result." + (let ((result-tags (copy-sequence current-tags))) + (mapc (lambda (del-tag) + (setq result-tags (delete del-tag result-tags))) + del-tags) + result-tags)) (defun notmuch-show-add-tag (&rest toadd) "Add a tag to the current message." (interactive (list (notmuch-select-tag-with-completion "Tag to add: "))) - (apply 'notmuch-call-notmuch-process - (append (cons "tag" - (mapcar (lambda (s) (concat "+" s)) toadd)) - (cons (notmuch-show-get-message-id) nil))) - (notmuch-show-set-tags (sort (union toadd (notmuch-show-get-tags) :test 'string=) 'string<))) + + (let* ((current-tags (notmuch-show-get-tags)) + (new-tags (notmuch-show-add-tags-worker current-tags toadd))) + + (unless (equal current-tags new-tags) + (apply 'notmuch-call-notmuch-process + (append (cons "tag" + (mapcar (lambda (s) (concat "+" s)) toadd)) + (cons (notmuch-show-get-message-id) nil))) + (notmuch-show-set-tags new-tags)))) (defun notmuch-show-remove-tag (&rest toremove) "Remove a tag from the current message." (interactive (list (notmuch-select-tag-with-completion "Tag to remove: " (notmuch-show-get-message-id)))) - (let ((tags (notmuch-show-get-tags))) - (if (intersection tags toremove :test 'string=) - (progn - (apply 'notmuch-call-notmuch-process - (append (cons "tag" - (mapcar (lambda (s) (concat "-" s)) toremove)) - (cons (notmuch-show-get-message-id) nil))) - (notmuch-show-set-tags (sort (set-difference tags toremove :test 'string=) 'string<)))))) + + (let* ((current-tags (notmuch-show-get-tags)) + (new-tags (notmuch-show-del-tags-worker current-tags toremove))) + + (unless (equal current-tags new-tags) + (apply 'notmuch-call-notmuch-process + (append (cons "tag" + (mapcar (lambda (s) (concat "-" s)) toremove)) + (cons (notmuch-show-get-message-id) nil))) + (notmuch-show-set-tags new-tags)))) (defun notmuch-show-toggle-headers () "Toggle the visibility of the current message headers." @@ -990,7 +1043,7 @@ argument, hide all of the messages." until (not (notmuch-show-goto-message-next))) ;; Move to the next item in the search results, if any. (let ((parent-buffer notmuch-show-parent-buffer)) - (kill-this-buffer) + (notmuch-kill-this-buffer) (if parent-buffer (progn (switch-to-buffer parent-buffer) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index fe1041f0..4a9223e4 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -47,7 +47,7 @@ ; kudos: Notmuch list (subscription is not ; required, but is available from http://notmuchmail.org). -(require 'cl) +(eval-when-compile (require 'cl)) (require 'mm-view) (require 'message) @@ -232,7 +232,7 @@ For a mouse binding, return nil." "Exit the search buffer, calling any defined continuation function." (interactive) (let ((continuation notmuch-search-continuation)) - (kill-this-buffer) + (notmuch-kill-this-buffer) (when continuation (funcall continuation)))) @@ -329,7 +329,6 @@ For a mouse binding, return nil." "Face used in search mode face for tags." :group 'notmuch) -;;;###autoload (defun notmuch-search-mode () "Major mode displaying results of a notmuch search. @@ -575,9 +574,10 @@ This function advances the next thread when finished." (if (and atbob (not (string= notmuch-search-target-thread "found"))) (set 'never-found-target-thread t)))))) - (if (and never-found-target-thread + (when (and never-found-target-thread notmuch-search-target-line) - (goto-line notmuch-search-target-line))))))) + (goto-char (point-min)) + (forward-line (1- notmuch-search-target-line)))))))) (defcustom notmuch-search-line-faces nil "Tag/face mapping for line highlighting in notmuch-search. @@ -585,28 +585,33 @@ This function advances the next thread when finished." Here is an example of how to color search results based on tags. (the following text would be placed in your ~/.emacs file): - (setq notmuch-search-line-faces '((\"delete\" . '(:foreground \"red\")) + (setq notmuch-search-line-faces '((\"delete\" . '(:foreground \"red\" + :background \"blue\")) (\"unread\" . '(:foreground \"green\")))) -Order matters: for lines with multiple tags, the the first -matching will be applied." +The attributes defined for matching tags are merged, with later +attributes overriding earlier. A message having both \"delete\" +and \"unread\" tags with the above settings would have a green +foreground and blue background." :type '(alist :key-type (string) :value-type (list)) :group 'notmuch) (defun notmuch-search-color-line (start end line-tag-list) - "Colorize lines in notmuch-show based on tags" - (if notmuch-search-line-faces - (let ((overlay (make-overlay start end)) - (tags-faces (copy-alist notmuch-search-line-faces))) - (while tags-faces - (let* ((tag-face (car tags-faces)) - (tag (car tag-face)) - (face (cdr tag-face))) - (cond ((member tag line-tag-list) - (overlay-put overlay 'face face) - (setq tags-faces nil)) - (t - (setq tags-faces (cdr tags-faces))))))))) + "Colorize lines in `notmuch-show' based on tags." + ;; Create the overlay only if the message has tags which match one + ;; of those specified in `notmuch-search-line-faces'. + (let (overlay) + (mapc '(lambda (elem) + (let ((tag (car elem)) + (attributes (cdr elem))) + (when (member tag line-tag-list) + (when (not overlay) + (setq overlay (make-overlay start end))) + ;; Merge the specified properties with any already + ;; applied from an earlier match. + (overlay-put overlay 'face + (append (overlay-get overlay 'face) attributes))))) + notmuch-search-line-faces))) (defun notmuch-search-isearch-authors-show (overlay) (remove-from-invisibility-spec (cons (overlay-get overlay 'invisible) t))) @@ -691,7 +696,7 @@ matching will be applied." (more t) (inhibit-read-only t)) (while more - (if (string-match "^\\(thread:[0-9A-Fa-f]*\\) \\(.*\\) \\(\\[[0-9/]*\\]\\) \\([^;]*\\); \\(.*\\) (\\([^()]*\\))$" string line) + (if (string-match "^\\(thread:[0-9A-Fa-f]*\\) \\([^][]*\\) \\(\\[[0-9/]*\\]\\) \\([^;]*\\); \\(.*\\) (\\([^()]*\\))$" string line) (let* ((thread-id (match-string 1 string)) (date (match-string 2 string)) (count (match-string 3 string)) @@ -741,10 +746,16 @@ characters as well as `_.+-'. (defun notmuch-search-buffer-title (query) "Returns the title for a buffer with notmuch search results." - (let* ((saved-search (rassoc-if (lambda (key) - (string-match (concat "^" (regexp-quote key)) - query)) - (reverse (notmuch-saved-searches)))) + (let* ((saved-search + (let (longest + (longest-length 0)) + (loop for tuple in notmuch-saved-searches + if (let ((quoted-query (regexp-quote (cdr tuple)))) + (and (string-match (concat "^" quoted-query) query) + (> (length (match-string 0 query)) + longest-length))) + do (setq longest tuple)) + longest)) (saved-search-name (car saved-search)) (saved-search-query (cdr saved-search))) (cond ((and saved-search (equal saved-search-query query)) @@ -788,10 +799,13 @@ The optional parameters are used as follows: (erase-buffer) (goto-char (point-min)) (save-excursion - (let ((proc (start-process-shell-command - "notmuch-search" buffer notmuch-command "search" - (if oldest-first "--sort=oldest-first" "--sort=newest-first") - (shell-quote-argument query)))) + (let ((proc (start-process + "notmuch-search" buffer + notmuch-command "search" + (if oldest-first + "--sort=oldest-first" + "--sort=newest-first") + query))) (set-process-sentinel proc 'notmuch-search-process-sentinel) (set-process-filter proc 'notmuch-search-process-filter)))) (run-hooks 'notmuch-search-hook))) @@ -810,7 +824,7 @@ same relative position within the new buffer." (target-thread (notmuch-search-find-thread-id)) (query notmuch-search-query-string) (continuation notmuch-search-continuation)) - (kill-this-buffer) + (notmuch-kill-this-buffer) (notmuch-search query oldest-first target-thread target-line continuation) (goto-char (point-min)))) diff --git a/lib/Makefile.local b/lib/Makefile.local index 9c0facce..3d7de5ca 100644 --- a/lib/Makefile.local +++ b/lib/Makefile.local @@ -15,7 +15,7 @@ LIBNOTMUCH_VERSION_MAJOR = 1 # The minor version of the library interface. This should be incremented at # the time of release for any additions to the library interface. -LIBNOTMUCH_VERSION_MINOR = 1 +LIBNOTMUCH_VERSION_MINOR = 2 # The release version the library interface. This should be incremented at # the time of release if there have been no changes to the interface, (but @@ -36,9 +36,11 @@ SONAME = $(LINKER_NAME).$(LIBNOTMUCH_VERSION_MAJOR) LIBNAME = $(SONAME).$(LIBNOTMUCH_VERSION_MINOR).$(LIBNOTMUCH_VERSION_RELEASE) LIBRARY_LINK_FLAG = -shared -Wl,-soname=$(SONAME) ifeq ($(LIBDIR_IN_LDCONFIG),1) +ifeq ($(DESTDIR),) LIBRARY_INSTALL_POST_COMMAND=ldconfig endif endif +endif dir := lib extra_cflags += -I$(dir) -fPIC @@ -76,13 +78,17 @@ $(dir)/$(LINKER_NAME): $(dir)/$(SONAME) install: install-$(dir) +# The (often-reused) $dir works fine within targets/pre-requisites, +# but cannot be used reliably within commands, so copy its value to a +# variable that is not reused. +lib := $(dir) install-$(dir): mkdir -p $(DESTDIR)$(libdir)/ - install -m0644 $(dir)/$(LIBNAME) $(DESTDIR)$(libdir)/ + install -m0644 $(lib)/$(LIBNAME) $(DESTDIR)$(libdir)/ ln -sf $(LIBNAME) $(DESTDIR)$(libdir)/$(SONAME) ln -sf $(LIBNAME) $(DESTDIR)$(libdir)/$(LINKER_NAME) mkdir -p $(DESTDIR)$(includedir) - install -m0644 $(dir)/notmuch.h $(DESTDIR)$(includedir)/ + install -m0644 $(lib)/notmuch.h $(DESTDIR)$(includedir)/ $(LIBRARY_INSTALL_POST_COMMAND) SRCS := $(SRCS) $(libnotmuch_c_srcs) $(libnotmuch_cxx_srcs) diff --git a/lib/database-private.h b/lib/database-private.h index bd72f670..e42b8bb8 100644 --- a/lib/database-private.h +++ b/lib/database-private.h @@ -34,6 +34,8 @@ #include +#pragma GCC visibility push(hidden) + struct _notmuch_database { notmuch_bool_t exception_reported; @@ -65,4 +67,6 @@ notmuch_tags_t * _notmuch_convert_tags (void *ctx, Xapian::TermIterator &i, Xapian::TermIterator &end); +#pragma GCC visibility pop + #endif diff --git a/lib/database.cc b/lib/database.cc index 6affc205..82c07886 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -24,7 +24,6 @@ #include #include -#include #include /* g_free, GPtrArray, GHashTable */ @@ -185,7 +184,7 @@ typedef struct { * nearly universal to all mail messages). */ -prefix_t BOOLEAN_PREFIX_INTERNAL[] = { +static prefix_t BOOLEAN_PREFIX_INTERNAL[] = { { "type", "T" }, { "reference", "XREFERENCE" }, { "replyto", "XREPLYTO" }, @@ -194,14 +193,14 @@ prefix_t BOOLEAN_PREFIX_INTERNAL[] = { { "directory-direntry", "XDDIRENTRY" }, }; -prefix_t BOOLEAN_PREFIX_EXTERNAL[] = { +static prefix_t BOOLEAN_PREFIX_EXTERNAL[] = { { "thread", "G" }, { "tag", "K" }, { "is", "K" }, { "id", "Q" } }; -prefix_t PROBABILISTIC_PREFIX[]= { +static prefix_t PROBABILISTIC_PREFIX[]= { { "from", "XFROM" }, { "to", "XTO" }, { "attachment", "XATTACHMENT" }, @@ -1300,7 +1299,7 @@ _resolve_message_id_to_thread_id (notmuch_database_t *notmuch, talloc_free (metadata_key); - return thread_id; + return talloc_strdup (ctx, thread_id); } static notmuch_status_t diff --git a/lib/directory.cc b/lib/directory.cc index 5d673e21..2540ca76 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -21,8 +21,6 @@ #include "notmuch-private.h" #include "database-private.h" -#include - struct _notmuch_filenames { Xapian::TermIterator iterator; Xapian::TermIterator end; @@ -52,7 +50,7 @@ _notmuch_filenames_destructor (notmuch_filenames_t *filenames) * iterating over the non-prefixed portion of terms sharing a common * prefix. */ -notmuch_filenames_t * +static notmuch_filenames_t * _notmuch_filenames_create (void *ctx, notmuch_database_t *notmuch, const char *prefix) diff --git a/lib/index.cc b/lib/index.cc index 0d6640bc..00478f8d 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -63,7 +63,7 @@ struct _NotmuchFilterDiscardUuencodeClass { GMimeFilterClass parent_class; }; -GMimeFilter *notmuch_filter_discard_uuencode_new (void); +static GMimeFilter *notmuch_filter_discard_uuencode_new (void); static void notmuch_filter_discard_uuencode_finalize (GObject *object); @@ -195,7 +195,7 @@ filter_reset (GMimeFilter *gmime_filter) * * Returns: a new #NotmuchFilterDiscardUuencode filter. **/ -GMimeFilter * +static GMimeFilter * notmuch_filter_discard_uuencode_new (void) { static GType type = 0; diff --git a/lib/libsha1.h b/lib/libsha1.h index b4dca93b..c1c848fc 100644 --- a/lib/libsha1.h +++ b/lib/libsha1.h @@ -43,6 +43,8 @@ extern "C" #include +#pragma GCC visibility push(hidden) + /* Size of SHA1 digest */ #define SHA1_DIGEST_SIZE 20 @@ -60,6 +62,8 @@ void sha1_hash(const unsigned char data[], unsigned long len, sha1_ctx ctx[1]); void sha1_end(unsigned char hval[], sha1_ctx ctx[1]); void sha1(unsigned char hval[], const unsigned char data[], unsigned long len); +#pragma GCC visibility pop + #if defined(__cplusplus) } #endif diff --git a/lib/message.cc b/lib/message.cc index 71f5619f..bf9f1edb 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -25,8 +25,6 @@ #include -#include - struct _notmuch_message { notmuch_database_t *notmuch; Xapian::docid doc_id; diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 5a0cf925..5b32f84a 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -48,6 +48,8 @@ NOTMUCH_BEGIN_DECLS #include "xutil.h" +#pragma GCC visibility push(hidden) + #ifdef DEBUG # define DEBUG_DATABASE_SANITY 1 # define DEBUG_QUERY 1 @@ -442,6 +444,8 @@ _notmuch_tags_add_tag (notmuch_tags_t *tags, const char *tag); void _notmuch_tags_prepare_iterator (notmuch_tags_t *tags); +#pragma GCC visibility pop + NOTMUCH_END_DECLS #endif diff --git a/lib/notmuch.h b/lib/notmuch.h index 505ad19f..bd0880f3 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -361,10 +361,18 @@ typedef enum { NOTMUCH_SORT_UNSORTED } notmuch_sort_t; +/* Return the query_string of this query. See notmuch_query_create. */ +const char * +notmuch_query_get_query_string (notmuch_query_t *query); + /* Specify the sorting desired for this query. */ void notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort); +/* Return the sort specified for this query. See notmuch_query_set_sort. */ +notmuch_sort_t +notmuch_query_get_sort (notmuch_query_t *query); + /* Execute a query for threads, returning a notmuch_threads_t object * which can be used to iterate over the results. The returned threads * object is owned by the query and as such, will only be valid until @@ -400,6 +408,8 @@ notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort); * notmuch_threads_t object. (For consistency, we do provide a * notmuch_threads_destroy function, but there's no good reason * to call it if the query is about to be destroyed). + * + * If a Xapian exception occurs this function will return NULL. */ notmuch_threads_t * notmuch_query_search_threads (notmuch_query_t *query); diff --git a/lib/query.cc b/lib/query.cc index d241dc1d..7916421e 100644 --- a/lib/query.cc +++ b/lib/query.cc @@ -23,8 +23,6 @@ #include /* GHashTable, GPtrArray */ -#include - struct _notmuch_query { notmuch_database_t *notmuch; const char *query_string; @@ -70,12 +68,24 @@ notmuch_query_create (notmuch_database_t *notmuch, return query; } +const char * +notmuch_query_get_query_string (notmuch_query_t *query) +{ + return query->query_string; +} + void notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort) { query->sort = sort; } +notmuch_sort_t +notmuch_query_get_sort (notmuch_query_t *query) +{ + return query->sort; +} + /* We end up having to call the destructors explicitly because we had * to use "placement new" in order to initialize C++ objects within a * block that we allocated with talloc. So C++ is making talloc @@ -249,6 +259,10 @@ notmuch_query_search_threads (notmuch_query_t *query) free, NULL); threads->messages = notmuch_query_search_messages (query); + if (threads->messages == NULL) { + talloc_free (threads); + return NULL; + } threads->thread_id = NULL; diff --git a/lib/thread.cc b/lib/thread.cc index 13872d46..7f155862 100644 --- a/lib/thread.cc +++ b/lib/thread.cc @@ -21,8 +21,6 @@ #include "notmuch-private.h" #include "database-private.h" -#include - #include #include /* GHashTable */ @@ -140,14 +138,14 @@ _complete_thread_authors (notmuch_thread_t *thread) thread->authors_array = NULL; } -/* clean up the uggly "Lastname, Firstname" format that some mail systems +/* clean up the ugly "Lastname, Firstname" format that some mail systems * (most notably, Exchange) are creating to be "Firstname Lastname" * To make sure that we don't change other potential situations where a * comma is in the name, we check that we match one of these patterns * "Last, First" * "Last, First MI" */ -char * +static char * _thread_cleanup_author (notmuch_thread_t *thread, const char *author, const char *from) { diff --git a/lib/xutil.h b/lib/xutil.h index b973f7dc..fd77f733 100644 --- a/lib/xutil.h +++ b/lib/xutil.h @@ -25,6 +25,8 @@ #include #include +#pragma GCC visibility push(hidden) + /* xutil.c */ void * xcalloc (size_t nmemb, size_t size); @@ -48,4 +50,6 @@ int xregexec (const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags); +#pragma GCC visibility pop + #endif diff --git a/notmuch-client.h b/notmuch-client.h index 20be43b9..fdfb94ad 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -110,9 +110,15 @@ notmuch_tag_command (void *ctx, int argc, char *argv[]); int notmuch_search_tags_command (void *ctx, int argc, char *argv[]); +int +notmuch_cat_command (void *ctx, int argc, char *argv[]); + int notmuch_part_command (void *ctx, int argc, char *argv[]); +int +notmuch_config_command (void *ctx, int argc, char *argv[]); + const char * notmuch_time_relative_date (const void *ctx, time_t then); @@ -174,7 +180,7 @@ void notmuch_config_set_user_primary_email (notmuch_config_t *config, const char *primary_email); -char ** +const char ** notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length); diff --git a/notmuch-config.c b/notmuch-config.c index 58f83b0d..dcdb0369 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -68,7 +68,7 @@ struct _notmuch_config { char *database_path; char *user_name; char *user_primary_email; - char **user_other_email; + const char **user_other_email; size_t user_other_email_length; const char **new_tags; size_t new_tags_length; @@ -151,26 +151,33 @@ get_username_from_passwd_file (void *ctx) * etc.), this function will print a message to stderr and return * NULL. * - * Note: It is *not* an error if the specified configuration file does - * not exist. In this case, a default configuration will be created - * and returned. Subsequently calling notmuch_config_save will cause - * the configuration to be written to the filename specified at the - * time of notmuch_config_open. + * FILE NOT FOUND: When the specified configuration file (whether from + * 'filename' or the $NOTMUCH_CONFIG environment variable) does not + * exist, the behavior of this function depends on the 'is_new_ret' + * variable. * - * The default configuration settings are determined as follows: + * If is_new_ret is NULL, then a "file not found" message will be + * printed to stderr and NULL will be returned. + + * If is_new_ret is non-NULL then a default configuration will be + * returned and *is_new_ret will be set to 1 on return so that + * the caller can recognize this case. + * + * These default configuration settings are determined as + * follows: * - * database_path: $HOME/mail + * database_path: $HOME/mail * - * user_name: From /etc/passwd + * user_name: From /etc/passwd * - * user_primary_mail: $EMAIL variable if set, otherwise - * constructed from the username and - * hostname of the current machine. + * user_primary_mail: $EMAIL variable if set, otherwise + * constructed from the username and + * hostname of the current machine. * - * user_other_email: Not set. + * user_other_email: Not set. * - * The default configuration also contains comments to guide the user - * in editing the file directly. + * The default configuration also contains comments to guide the + * user in editing the file directly. */ notmuch_config_t * notmuch_config_open (void *ctx, @@ -220,14 +227,19 @@ notmuch_config_open (void *ctx, G_KEY_FILE_KEEP_COMMENTS, &error)) { - /* We are capable of dealing with a non-existent configuration - * file, so be silent about that (unless the user had set a - * non-default configuration file with the NOTMUCH_CONFIG - * variable) + /* If the caller passed a non-NULL value for is_new_ret, then + * the caller is prepared for a default configuration file in + * the case of FILE NOT FOUND. Otherwise, any read failure is + * an error. */ - if (notmuch_config_env || - !(error->domain == G_FILE_ERROR && - error->code == G_FILE_ERROR_NOENT)) + if (is_new_ret && + error->domain == G_FILE_ERROR && + error->code == G_FILE_ERROR_NOENT) + { + g_error_free (error); + is_new = 1; + } + else { fprintf (stderr, "Error reading configuration file %s: %s\n", config->filename, error->message); @@ -235,9 +247,6 @@ notmuch_config_open (void *ctx, g_error_free (error); return NULL; } - - g_error_free (error); - is_new = 1; } /* Whenever we know of configuration sections that don't appear in @@ -465,7 +474,7 @@ notmuch_config_set_user_primary_email (notmuch_config_t *config, config->user_primary_email = NULL; } -char ** +const char ** notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length) { @@ -553,3 +562,144 @@ notmuch_config_set_new_tags (notmuch_config_t *config, config->new_tags = NULL; } +/* Given a configuration item of the form . return the + * component group and key. If any error occurs, print a message on + * stderr and return 1. Otherwise, return 0. + * + * Note: This function modifies the original 'item' string. + */ +static int +_item_split (char *item, char **group, char **key) +{ + char *period; + + *group = item; + + period = index (item, '.'); + if (period == NULL || *(period+1) == '\0') { + fprintf (stderr, + "Invalid configuration name: %s\n" + "(Should be of the form
.)\n", item); + return 1; + } + + *period = '\0'; + *key = period + 1; + + return 0; +} + +static int +notmuch_config_command_get (void *ctx, char *item) +{ + notmuch_config_t *config; + + config = notmuch_config_open (ctx, NULL, NULL); + if (config == NULL) + return 1; + + if (strcmp(item, "database.path") == 0) { + printf ("%s\n", notmuch_config_get_database_path (config)); + } else if (strcmp(item, "user.name") == 0) { + printf ("%s\n", notmuch_config_get_user_name (config)); + } else if (strcmp(item, "user.primary_email") == 0) { + printf ("%s\n", notmuch_config_get_user_primary_email (config)); + } else if (strcmp(item, "user.other_email") == 0) { + const char **other_email; + size_t i, length; + + other_email = notmuch_config_get_user_other_email (config, &length); + for (i = 0; i < length; i++) + printf ("%s\n", other_email[i]); + } else if (strcmp(item, "new.tags") == 0) { + const char **tags; + size_t i, length; + + tags = notmuch_config_get_new_tags (config, &length); + for (i = 0; i < length; i++) + printf ("%s\n", tags[i]); + } else { + char **value; + size_t i, length; + char *group, *key; + + if (_item_split (item, &group, &key)) + return 1; + + value = g_key_file_get_string_list (config->key_file, + group, key, + &length, NULL); + if (value == NULL) { + fprintf (stderr, "Unknown configuration item: %s.%s\n", + group, key); + return 1; + } + + for (i = 0; i < length; i++) + printf ("%s\n", value[i]); + + free (value); + } + + notmuch_config_close (config); + + return 0; +} + +static int +notmuch_config_command_set (void *ctx, char *item, int argc, char *argv[]) +{ + notmuch_config_t *config; + char *group, *key; + int ret; + + if (_item_split (item, &group, &key)) + return 1; + + config = notmuch_config_open (ctx, NULL, NULL); + if (config == NULL) + return 1; + + /* With only the name of an item, we clear it from the + * configuration file. + * + * With a single value, we set it as a string. + * + * With multiple values, we set them as a string list. + */ + switch (argc) { + case 0: + g_key_file_remove_key (config->key_file, group, key, NULL); + break; + case 1: + g_key_file_set_string (config->key_file, group, key, argv[0]); + break; + default: + g_key_file_set_string_list (config->key_file, group, key, + (const gchar **) argv, argc); + break; + } + + ret = notmuch_config_save (config); + notmuch_config_close (config); + + return ret; +} + +int +notmuch_config_command (void *ctx, int argc, char *argv[]) +{ + if (argc < 2) { + fprintf (stderr, "Error: notmuch config requires at least two arguments.\n"); + return 1; + } + + if (strcmp (argv[0], "get") == 0) + return notmuch_config_command_get (ctx, argv[1]); + else if (strcmp (argv[0], "set") == 0) + return notmuch_config_command_set (ctx, argv[1], argc - 2, argv + 2); + + fprintf (stderr, "Unrecognized argument for notmuch config: %s\n", + argv[0]); + return 1; +} diff --git a/notmuch-reply.c b/notmuch-reply.c index fd1de3b9..23d04b8b 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -119,7 +119,7 @@ static int address_is_users (const char *address, notmuch_config_t *config) { const char *primary; - char **other; + const char **other; size_t i, other_len; primary = notmuch_config_get_user_primary_email (config); @@ -312,7 +312,8 @@ static const char * guess_from_received_header (notmuch_config_t *config, notmuch_message_t *message) { const char *received,*primary,*by; - char **other,*tohdr; + const char **other; + char *tohdr; char *mta,*ptr,*token; char *domain=NULL; char *tld=NULL; @@ -481,9 +482,6 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_ g_mime_object_set_header (GMIME_OBJECT (reply), "From", from_addr); - g_mime_object_set_header (GMIME_OBJECT (reply), "Bcc", - notmuch_config_get_user_primary_email (config)); - in_reply_to = talloc_asprintf (ctx, "<%s>", notmuch_message_get_message_id (message)); @@ -558,9 +556,6 @@ notmuch_reply_format_headers_only(void *ctx, notmuch_config_t *config, notmuch_q (void)add_recipients_from_message (reply, config, message); - g_mime_object_set_header (GMIME_OBJECT (reply), "Bcc", - notmuch_config_get_user_primary_email (config)); - reply_headers = g_mime_object_to_string (GMIME_OBJECT (reply)); printf ("%s", reply_headers); free (reply_headers); diff --git a/notmuch-search.c b/notmuch-search.c index 8a1cdca3..bb989dac 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -20,25 +20,41 @@ #include "notmuch-client.h" +typedef enum { + OUTPUT_SUMMARY, + OUTPUT_THREADS, + OUTPUT_MESSAGES, + OUTPUT_FILES, + OUTPUT_TAGS +} output_t; + typedef struct search_format { const char *results_start; - const char *thread_start; - void (*thread) (const void *ctx, - const char *thread_id, - const time_t date, - const int matched, - const int total, - const char *authors, - const char *subject); + const char *item_start; + void (*item_id) (const void *ctx, + const char *item_type, + const char *item_id); + void (*thread_summary) (const void *ctx, + const char *thread_id, + const time_t date, + const int matched, + const int total, + const char *authors, + const char *subject); const char *tag_start; const char *tag; const char *tag_sep; const char *tag_end; - const char *thread_sep; - const char *thread_end; + const char *item_sep; + const char *item_end; const char *results_end; } search_format_t; +static void +format_item_id_text (const void *ctx, + const char *item_type, + const char *item_id); + static void format_thread_text (const void *ctx, const char *thread_id, @@ -50,14 +66,20 @@ format_thread_text (const void *ctx, static const search_format_t format_text = { "", "", + format_item_id_text, format_thread_text, " (", "%s", " ", - ")", "", - "\n", - "", + ")", "\n", + "", + "\n", }; +static void +format_item_id_json (const void *ctx, + const char *item_type, + const char *item_id); + static void format_thread_json (const void *ctx, const char *thread_id, @@ -69,6 +91,7 @@ format_thread_json (const void *ctx, static const search_format_t format_json = { "[", "{", + format_item_id_json, format_thread_json, "\"tags\": [", "\"%s\"", ", ", @@ -77,6 +100,14 @@ static const search_format_t format_json = { "]\n", }; +static void +format_item_id_text (unused (const void *ctx), + const char *item_type, + const char *item_id) +{ + printf ("%s%s", item_type, item_id); +} + static void format_thread_text (const void *ctx, const char *thread_id, @@ -95,6 +126,19 @@ format_thread_text (const void *ctx, subject); } +static void +format_item_id_json (const void *ctx, + unused (const char *item_type), + const char *item_id) +{ + void *ctx_quote = talloc_new (ctx); + + printf ("%s", json_quote_str (ctx_quote, item_id)); + + talloc_free (ctx_quote); + +} + static void format_thread_json (const void *ctx, const char *thread_id, @@ -122,11 +166,12 @@ format_thread_json (const void *ctx, talloc_free (ctx_quote); } -static void +static int do_search_threads (const void *ctx, const search_format_t *format, notmuch_query_t *query, - notmuch_sort_t sort) + notmuch_sort_t sort, + output_t output) { notmuch_thread_t *thread; notmuch_threads_t *threads; @@ -134,55 +179,160 @@ do_search_threads (const void *ctx, time_t date; int first_thread = 1; - fputs (format->results_start, stdout); + threads = notmuch_query_search_threads (query); + if (threads == NULL) + return 1; - for (threads = notmuch_query_search_threads (query); + for (; notmuch_threads_valid (threads); notmuch_threads_move_to_next (threads)) { int first_tag = 1; - if (! first_thread) - fputs (format->thread_sep, stdout); + if (first_thread) + fputs (format->results_start, stdout); + else + fputs (format->item_sep, stdout); thread = notmuch_threads_get (threads); - if (sort == NOTMUCH_SORT_OLDEST_FIRST) - date = notmuch_thread_get_oldest_date (thread); - else - date = notmuch_thread_get_newest_date (thread); - - fputs (format->thread_start, stdout); - - format->thread (ctx, - notmuch_thread_get_thread_id (thread), - date, - notmuch_thread_get_matched_messages (thread), - notmuch_thread_get_total_messages (thread), - notmuch_thread_get_authors (thread), - notmuch_thread_get_subject (thread)); - - fputs (format->tag_start, stdout); - - for (tags = notmuch_thread_get_tags (thread); - notmuch_tags_valid (tags); - notmuch_tags_move_to_next (tags)) - { - if (! first_tag) - fputs (format->tag_sep, stdout); - printf (format->tag, notmuch_tags_get (tags)); - first_tag = 0; - } + if (output == OUTPUT_THREADS) { + format->item_id (ctx, "thread:", + notmuch_thread_get_thread_id (thread)); + } else { /* output == OUTPUT_SUMMARY */ + fputs (format->item_start, stdout); + + if (sort == NOTMUCH_SORT_OLDEST_FIRST) + date = notmuch_thread_get_oldest_date (thread); + else + date = notmuch_thread_get_newest_date (thread); + + format->thread_summary (ctx, + notmuch_thread_get_thread_id (thread), + date, + notmuch_thread_get_matched_messages (thread), + notmuch_thread_get_total_messages (thread), + notmuch_thread_get_authors (thread), + notmuch_thread_get_subject (thread)); + + fputs (format->tag_start, stdout); + + for (tags = notmuch_thread_get_tags (thread); + notmuch_tags_valid (tags); + notmuch_tags_move_to_next (tags)) + { + if (! first_tag) + fputs (format->tag_sep, stdout); + printf (format->tag, notmuch_tags_get (tags)); + first_tag = 0; + } - fputs (format->tag_end, stdout); - fputs (format->thread_end, stdout); + fputs (format->tag_end, stdout); + + fputs (format->item_end, stdout); + } first_thread = 0; notmuch_thread_destroy (thread); } - fputs (format->results_end, stdout); + if (! first_thread) + fputs (format->results_end, stdout); + + return 0; +} + +static int +do_search_messages (const void *ctx, + const search_format_t *format, + notmuch_query_t *query, + output_t output) +{ + notmuch_message_t *message; + notmuch_messages_t *messages; + int first_message = 1; + + messages = notmuch_query_search_messages (query); + if (messages == NULL) + return 1; + + for (; + notmuch_messages_valid (messages); + notmuch_messages_move_to_next (messages)) + { + message = notmuch_messages_get (messages); + + if (first_message) + fputs (format->results_start, stdout); + else + fputs (format->item_sep, stdout); + + if (output == OUTPUT_FILES) { + format->item_id (ctx, "", + notmuch_message_get_filename (message)); + } else { /* output == OUTPUT_MESSAGES */ + format->item_id (ctx, "id:", + notmuch_message_get_message_id (message)); + } + + first_message = 0; + + notmuch_message_destroy (message); + } + + notmuch_messages_destroy (messages); + + if (! first_message) + fputs (format->results_end, stdout); + + return 0; +} + +static int +do_search_tags (const void *ctx, + notmuch_database_t *notmuch, + const search_format_t *format, + notmuch_query_t *query) +{ + notmuch_messages_t *messages = NULL; + notmuch_tags_t *tags; + const char *tag; + int first_tag = 1; + + /* Special-case query of "*" for better performance. */ + if (strcmp (notmuch_query_get_query_string (query), "*") == 0) { + tags = notmuch_database_get_all_tags (notmuch); + } else { + messages = notmuch_query_search_messages (query); + if (messages == NULL) + return 1; + + tags = notmuch_messages_collect_tags (messages); + } + if (tags == NULL) + return 1; + + for (; + notmuch_tags_valid (tags); + notmuch_tags_move_to_next (tags)) + { + tag = notmuch_tags_get (tags); + + if (! first_tag) + fputs (format->item_sep, stdout); + + format->item_id (ctx, "", tag); + + first_tag = 0; + } + + notmuch_tags_destroy (tags); + + if (messages) + notmuch_messages_destroy (messages); + + return 0; } int @@ -195,7 +345,8 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) char *opt; notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST; const search_format_t *format = &format_text; - int i; + int i, ret; + output_t output = OUTPUT_SUMMARY; for (i = 0; i < argc && argv[i][0] == '-'; i++) { if (strcmp (argv[i], "--") == 0) { @@ -222,6 +373,22 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) fprintf (stderr, "Invalid value for --format: %s\n", opt); return 1; } + } else if (STRNCMP_LITERAL (argv[i], "--output=") == 0) { + opt = argv[i] + sizeof ("--output=") - 1; + if (strcmp (opt, "summary") == 0) { + output = OUTPUT_SUMMARY; + } else if (strcmp (opt, "threads") == 0) { + output = OUTPUT_THREADS; + } else if (strcmp (opt, "messages") == 0) { + output = OUTPUT_MESSAGES; + } else if (strcmp (opt, "files") == 0) { + output = OUTPUT_FILES; + } else if (strcmp (opt, "tags") == 0) { + output = OUTPUT_TAGS; + } else { + fprintf (stderr, "Invalid value for --output: %s\n", opt); + return 1; + } } else { fprintf (stderr, "Unrecognized option: %s\n", argv[i]); return 1; @@ -258,10 +425,23 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) notmuch_query_set_sort (query, sort); - do_search_threads (ctx, format, query, sort); + switch (output) { + default: + case OUTPUT_SUMMARY: + case OUTPUT_THREADS: + ret = do_search_threads (ctx, format, query, sort, output); + break; + case OUTPUT_MESSAGES: + case OUTPUT_FILES: + ret = do_search_messages (ctx, format, query, output); + break; + case OUTPUT_TAGS: + ret = do_search_tags (ctx, notmuch, format, query); + break; + } notmuch_query_destroy (query); notmuch_database_close (notmuch); - return 0; + return ret; } diff --git a/notmuch-setup.c b/notmuch-setup.c index 955deb7e..c3ea9371 100644 --- a/notmuch-setup.c +++ b/notmuch-setup.c @@ -92,9 +92,9 @@ notmuch_setup_command (unused (void *ctx), unused (int argc), unused (char *argv[])) { char *response = NULL; - size_t response_size; + size_t response_size = 0; notmuch_config_t *config; - char **old_other_emails; + const char **old_other_emails; size_t old_other_emails_len; GPtrArray *other_emails; unsigned int i; diff --git a/notmuch-show.c b/notmuch-show.c index af7854d8..ef421ec7 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -77,6 +77,20 @@ static const show_format_t format_json = { "]" }; +static void +format_message_mbox (const void *ctx, + notmuch_message_t *message, + unused (int indent)); + +static const show_format_t format_mbox = { + "", + "", format_message_mbox, + "", NULL, "", + "", NULL, "", + "", "", + "" +}; + static const char * _get_tags_as_string (const void *ctx, notmuch_message_t *message) { @@ -163,6 +177,112 @@ format_message_json (const void *ctx, notmuch_message_t *message, unused (int in talloc_free (ctx_quote); } +/* Extract just the email address from the contents of a From: + * header. */ +static const char * +_extract_email_address (const void *ctx, const char *from) +{ + InternetAddressList *addresses; + InternetAddress *address; + InternetAddressMailbox *mailbox; + const char *email = "MAILER-DAEMON"; + + addresses = internet_address_list_parse_string (from); + + /* Bail if there is no address here. */ + if (addresses == NULL || internet_address_list_length (addresses) < 1) + goto DONE; + + /* Otherwise, just use the first address. */ + address = internet_address_list_get_address (addresses, 0); + + /* The From header should never contain an address group rather + * than a mailbox. So bail if it does. */ + if (! INTERNET_ADDRESS_IS_MAILBOX (address)) + goto DONE; + + mailbox = INTERNET_ADDRESS_MAILBOX (address); + email = internet_address_mailbox_get_addr (mailbox); + email = talloc_strdup (ctx, email); + + DONE: + /* XXX: How to free addresses here? */ + return email; + } + +/* Return 1 if 'line' is an mbox From_ line---that is, a line + * beginning with zero or more '>' characters followed by the + * characters 'F', 'r', 'o', 'm', and space. + * + * Any characters at all may appear after that in the line. + */ +static int +_is_from_line (const char *line) +{ + const char *s = line; + + if (line == NULL) + return 0; + + while (*s == '>') + s++; + + if (STRNCMP_LITERAL (s, "From ") == 0) + return 1; + else + return 0; +} + +/* Print a message in "mboxrd" format as documented, for example, + * here: + * + * http://qmail.org/qmail-manual-html/man5/mbox.html + */ +static void +format_message_mbox (const void *ctx, + notmuch_message_t *message, + unused (int indent)) +{ + const char *filename; + FILE *file; + const char *from; + + time_t date; + struct tm date_gmtime; + char date_asctime[26]; + + char *line = NULL; + size_t line_size; + ssize_t line_len; + + filename = notmuch_message_get_filename (message); + file = fopen (filename, "r"); + if (file == NULL) { + fprintf (stderr, "Failed to open %s: %s\n", + filename, strerror (errno)); + return; + } + + from = notmuch_message_get_header (message, "from"); + from = _extract_email_address (ctx, from); + + date = notmuch_message_get_date (message); + gmtime_r (&date, &date_gmtime); + asctime_r (&date_gmtime, date_asctime); + + printf ("From %s %s", from, date_asctime); + + while ((line_len = getline (&line, &line_size, file)) != -1 ) { + if (_is_from_line (line)) + putchar ('>'); + printf ("%s", line); + } + + printf ("\n"); + + fclose (file); +} + static void format_headers_text (const void *ctx, notmuch_message_t *message) { @@ -408,21 +528,105 @@ show_messages (void *ctx, const show_format_t *format, notmuch_messages_t *messa fputs (format->message_set_end, stdout); } +/* Support for --format=raw */ +static int +do_show_raw (unused(void *ctx), notmuch_query_t *query) +{ + notmuch_messages_t *messages; + notmuch_message_t *message; + const char *filename; + FILE *file; + size_t size; + char buf[4096]; + + if (notmuch_query_count_messages (query) != 1) { + fprintf (stderr, "Error: search term did not match precisely one message.\n"); + return 1; + } + + messages = notmuch_query_search_messages (query); + message = notmuch_messages_get (messages); + + if (message == NULL) { + fprintf (stderr, "Error: Cannot find matching message.\n"); + return 1; + } + + filename = notmuch_message_get_filename (message); + if (filename == NULL) { + fprintf (stderr, "Error: Cannot message filename.\n"); + return 1; + } + + file = fopen (filename, "r"); + if (file == NULL) { + fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno)); + return 1; + } + + while (!feof (file)) { + size = fread (buf, 1, sizeof (buf), file); + fwrite (buf, size, 1, stdout); + } + + fclose (file); + + return 0; +} + +/* Support for --format=text|json|mbox */ +static int +do_show (void *ctx, + notmuch_query_t *query, + const show_format_t *format, + int entire_thread) +{ + notmuch_threads_t *threads; + notmuch_thread_t *thread; + notmuch_messages_t *messages; + int first_toplevel = 1; + + fputs (format->message_set_start, stdout); + + for (threads = notmuch_query_search_threads (query); + notmuch_threads_valid (threads); + notmuch_threads_move_to_next (threads)) + { + thread = notmuch_threads_get (threads); + + messages = notmuch_thread_get_toplevel_messages (thread); + + if (messages == NULL) + INTERNAL_ERROR ("Thread %s has no toplevel messages.\n", + notmuch_thread_get_thread_id (thread)); + + if (!first_toplevel) + fputs (format->message_set_sep, stdout); + first_toplevel = 0; + + show_messages (ctx, format, messages, 0, entire_thread); + + notmuch_thread_destroy (thread); + + } + + fputs (format->message_set_end, stdout); + + return 0; +} + int notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { notmuch_config_t *config; notmuch_database_t *notmuch; notmuch_query_t *query; - notmuch_threads_t *threads; - notmuch_thread_t *thread; - notmuch_messages_t *messages; char *query_string; char *opt; const show_format_t *format = &format_text; int entire_thread = 0; int i; - int first_toplevel = 1; + int raw = 0; for (i = 0; i < argc && argv[i][0] == '-'; i++) { if (strcmp (argv[i], "--") == 0) { @@ -436,6 +640,10 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) } else if (strcmp (opt, "json") == 0) { format = &format_json; entire_thread = 1; + } else if (strcmp (opt, "mbox") == 0) { + format = &format_mbox; + } else if (strcmp (opt, "raw") == 0) { + raw = 1; } else { fprintf (stderr, "Invalid value for --format: %s\n", opt); return 1; @@ -477,31 +685,10 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } - fputs (format->message_set_start, stdout); - - for (threads = notmuch_query_search_threads (query); - notmuch_threads_valid (threads); - notmuch_threads_move_to_next (threads)) - { - thread = notmuch_threads_get (threads); - - messages = notmuch_thread_get_toplevel_messages (thread); - - if (messages == NULL) - INTERNAL_ERROR ("Thread %s has no toplevel messages.\n", - notmuch_thread_get_thread_id (thread)); - - if (!first_toplevel) - fputs (format->message_set_sep, stdout); - first_toplevel = 0; - - show_messages (ctx, format, messages, 0, entire_thread); - - notmuch_thread_destroy (thread); - - } - - fputs (format->message_set_end, stdout); + if (raw) + return do_show_raw (ctx, query); + else + return do_show (ctx, query, format, entire_thread); notmuch_query_destroy (query); notmuch_database_close (notmuch); diff --git a/notmuch.1 b/notmuch.1 index 86830f4b..3878a040 100644 --- a/notmuch.1 +++ b/notmuch.1 @@ -150,6 +150,53 @@ include Presents the results in either JSON or plain-text (default). .RE + +.RS 4 +.TP 4 +.B \-\-output=(summary|threads|messages|files|tags) + +.RS 4 +.TP 4 +.B summary + +Output a summary of each thread with any message matching the search +terms. The summary includes the thread ID, date, the number of +messages in the thread (both the number matched and the total number), +the authors of the thread and the subject. +.RE +.RS 4 +.TP 4 +.B threads + +Output the thread IDs of all threads with any message matching the +search terms, either one per line (\-\-format=text) or as a JSON array +(\-\-format=json). +.RE +.RS 4 +.TP 4 +.B messages + +Output the message IDs of all messages matching the search terms, +either one per line (\-\-format=text) or as a JSON array +(\-\-format=json). +.RE +.RS 4 +.TP 4 +.B files + +Output the filenames of all messages matching the search terms, either +one per line (\-\-format=text) or as a JSON array (\-\-format=json). +.RE +.RS 4 +.TP 4 +.B tags + +Output all tags that appear on any message matching the search terms, +either one per line (\-\-format=text) or as a JSON array +(\-\-format=json). +.RE +.RE + .RS 4 .TP 4 .BR \-\-sort= ( newest\-first | oldest\-first ) @@ -201,31 +248,54 @@ matched message will be displayed. .RS 4 .TP 4 -.B \-\-format=(json|text) +.B \-\-format=(text|json|mbox|raw) .RS 4 .TP 4 .B text -The default plain-text format has text-content MIME parts +The default plain-text format has all text-content MIME parts decoded. Various components in the output, .RB ( message ", " header ", " body ", " attachment ", and MIME " part ), will be delimited by easily-parsed markers. Each marker consists of a Control-L character (ASCII decimal 12), the name of the marker, and then either an opening or closing brace, ('{' or '}'), to either open or close the component. - .RE .RS 4 .TP 4 .B json -Format output as Javascript Object Notation (JSON). JSON output always -includes all messages in a matching thread; in effect +The output is formatted with Javascript Object Notation (JSON). This +format is more robust than the text format for automated +processing. JSON output always includes all messages in a matching +thread; in effect .B \-\-format=json implies .B \-\-entire\-thread +.RE +.RS 4 +.TP 4 +.B mbox + +All matching messages are output in the traditional, Unix mbox format +with each message being prefixed by a line beginning with "From " and +a blank line separating each message. Lines in the message content +beginning with "From " (preceded by zero or more '>' characters) have +an additional '>' character added. This reversible escaping +is termed "mboxrd" format and described in detail here: +http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html +.RE +.RS 4 +.TP 4 +.B raw + +The original, raw content of the email message is displayed. +Consumers of this format should expect to implement MIME decoding and +similar functions. This format must only be used with search terms +matching a single message. + .RE A common use of .B notmuch show @@ -389,6 +459,44 @@ See the section below for details of the supported syntax for . .RE +The +.B config +command can be used to get or set settings int the notmuch +configuration file. + +.RS 4 +.TP 4 +.BR "config get "
. + +The value of the specified configuration item is printed to stdout. If +the item has multiple values, each value is separated by a newline +character. + +Available configuration items include at least + + database.path + + user.name + + user.primary_email + + user.other_email + + new.tags +.RE + +.RS 4 +.TP 4 +.BR "config set "
. " [values ...]" + +The specified configuration item is set to the given value. To +specify a multiple-value item, provide each value as a separate +command-line argument. + +If no values are provided, the specified configuration item will be +removed from the configuration file. +.RE + .SH SEARCH SYNTAX Several notmuch commands accept a common syntax for search terms. diff --git a/notmuch.c b/notmuch.c index 13e2612d..9ba0ec03 100644 --- a/notmuch.c +++ b/notmuch.c @@ -165,6 +165,39 @@ command_t commands[] = { "\t\tPresents the results in either JSON or\n" "\t\tplain-text (default)\n" "\n" + "\t--output=(summary|threads|messages|files|tags)\n" + "\n" + "\t\tsummary (default)\n" + "\n" + "\t\tOutput a summary of each thread with any message matching the\n" + "\t\tsearch terms. The summary includes the thread ID, date, the\n" + "\t\tnumber of messages in the thread (both the number matched and\n" + "\t\tthe total number), the authors of the thread and the subject.\n" + "\n" + "\t\tthreads\n" + "\n" + "\t\tOutput the thread IDs of all threads with any message matching\n" + "\t\tthe search terms, either one per line (--format=text) or as a\n" + "\t\tJSON array (--format=json).\n" + "\n" + "\t\tmessages\n" + "\n" + "\t\tOutput the message IDs of all messages matching the search\n" + "\t\tterms, either one per line (--format=text) or as a JSON array\n" + "\t\t(--format=json).\n" + "\n" + "\t\tfiles\n" + "\n" + "\t\tOutput the filenames of all messages matching the search\n" + "\t\tterms, either one per line (--format=text) or as a JSON array\n" + "\t\t(--format=json).\n" + "\n" + "\t\ttags\n" + "\n" + "\t\tOutput all tags that appear on any message matching the search\n" + "\t\tterms, either one per line (--format=text) or as a JSON array\n" + "\t\t(--format=json).\n" + "\n" "\t--sort=(newest-first|oldest-first)\n" "\n" "\t\tPresent results in either chronological order\n" @@ -189,12 +222,12 @@ command_t commands[] = { "\t\tall messages in the same thread as any matched\n" "\t\tmessage will be displayed.\n" "\n" - "\t--format=(json|text)\n" + "\t--format=(text|json|mbox|raw)\n" "\n" - "\t\ttext\t(default)\n" + "\t\ttext (default)\n" "\n" - "\t\tThe plain-text has all text-content MIME parts decoded.\n" - "\t\tVarious components in the output, ('message', 'header',\n" + "\t\tThe default plain-text format has all text-content MIME parts\n" + "\t\tdecoded. Various components in the output, ('message', 'header',\n" "\t\t'body', 'attachment', and MIME 'part') are delimited by\n" "\t\teasily-parsed markers. Each marker consists of a Control-L\n" "\t\tcharacter (ASCII decimal 12), the name of the marker, and\n" @@ -203,9 +236,30 @@ command_t commands[] = { "\n" "\t\tjson\n" "\n" - "\t\tFormat output as Javascript Object Notation (JSON).\n" - "\t\tJSON output always includes all messages in a matching,\n" - "\t\tthread i.e. '--format=json' implies '--entire-thread'\n" + "\t\tThe output is formatted with Javascript Object Notation\n" + "\t\t(JSON). This format is more robust than the text format\n" + "\t\tfor automated processing. JSON output always includes all\n" + "\t\tmessages in a matching thread; in effect '--format=json'\n" + "\t\timplies '--entire-thread'\n" + "\n" + "\t\tmbox\n" + "\n" + "\t\tAll matching messages are output in the traditional, Unix\n" + "\t\tmbox format with each message being prefixed by a line\n" + "\t\tbeginning with 'From ' and a blank line separating each\n" + "\t\tmessage. Lines in the message content beginning with 'From '\n" + "\t\t(preceded by zero or more '>' characters) have an additional\n" + "\t\t'>' character added. This reversible escaping is termed\n" + "\t\t\"mboxrd\" format and described in detail here:\n" + "\n" + "\t\thttp://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html\n" + "\n" + "\t\traw\n" + "\n" + "\t\tThe original, raw content of the email message is displayed.\n" + "\t\tConsumers of this format should expect to implement MIME\n" + "\t\tdecoding and similar functions. This format must only\n" + "\t\tbe used with search terms matching a single message.\n" "\n" "\tA common use of \"notmuch show\" is to display a single\n" "\tthread of email messages. For this, use a search term of\n" @@ -307,6 +361,31 @@ command_t commands[] = { "\tby the \"--format=json\" option of \"notmuch show\". If the\n" "\tmessage specified by the search terms does not include a\n" "\tpart with the specified \"id\" there will be no output." }, + { "config", notmuch_config_command, + "[get|set]
. [value ...]", + "Get or set settings in the notmuch configuration file.", + " config get
.\n" + "\n" + "\tThe value of the specified configuration item is printed\n" + "\tto stdout. If the item has multiple values, each value\n" + "\tis separated by a newline character.\n" + "\n" + "\tAvailable configuration items include at least\n" + "\n" + "\t\tdatabase.path\n" + "\t\tuser.name\n" + "\t\tuser.primary_email\n" + "\t\tuser.other_email\n" + "\t\tnew.tags\n" + "\n" + " config set
. [value ...]\n" + "\n" + "\tThe specified configuration item is set to the given value.\n" + "\tTo specify a multiple-value item, provide each value as\n" + "\ta separate command-line argument.\n" + "\n" + "\tIf no values are provided, the specified configuration item\n" + "\twill be removed from the configuration file." }, { "help", notmuch_help_command, "[]", "This message, or more detailed help for the named command.", diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000..b34778f4 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,3 @@ +test-results +corpus.mail +smtp-dummy diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 00000000..b6859eac --- /dev/null +++ b/test/Makefile @@ -0,0 +1,7 @@ +# See Makfefile.local for the list of files to be compiled in this +# directory. +all: + $(MAKE) -C .. all + +.DEFAULT: + $(MAKE) -C .. $@ diff --git a/test/Makefile.local b/test/Makefile.local new file mode 100644 index 00000000..7b602bcf --- /dev/null +++ b/test/Makefile.local @@ -0,0 +1,14 @@ +# -*- makefile -*- + +dir := test + +$(dir)/smtp-dummy: $(dir)/smtp-dummy.c + $(call quiet,CC) $^ -o $@ + +.PHONY: test check +test: all $(dir)/smtp-dummy + @${dir}/notmuch-test $(OPTIONS) + +check: test + +CLEAN := $(CLEAN) $(dir)/smtp-dummy diff --git a/test/README b/test/README new file mode 100644 index 00000000..ebaa3cfd --- /dev/null +++ b/test/README @@ -0,0 +1,202 @@ +Notmuch test suite +================== +This directory contains the test suite for notmuch. + +When fixing bugs or enhancing notmuch, you are strongly encouraged to +add tests in this directory to cover what you are trying to fix or +enhance. + +Running Tests +------------- +The easiest way to run tests is to say "make test", (or simply run the +notmuch-test script). Either command will run all available tests. + +Alternately, you can run a specific subset of tests by simply invoking +one of the executable scripts in this directory, (such as ./search, +./reply, etc.) + +The following command-line options are available when running tests: + +--debug:: + This may help the person who is developing a new test. + It causes the command defined with test_debug to run. + +--immediate:: + This causes the test to immediately exit upon the first + failed test. + +--valgrind:: + Execute notmuch with valgrind and exit with status + 126 on errors (just like regular tests, this will only stop + the test script when running under -i). Valgrind errors + go to stderr, so you might want to pass the -v option, too. + + Since it makes no sense to run the tests with --valgrind and + not see any output, this option implies --verbose. For + convenience, it also implies --tee. + +--tee:: + In addition to printing the test output to the terminal, + write it to files named 't/test-results/$TEST_NAME.out'. + As the names depend on the tests' file names, it is safe to + run the tests with this option in parallel. + +When invoking the test suite via "make test" any of the above options +can be specified as follows: + + make test OPTIONS="--verbose" + +Skipping Tests +-------------- +If, for any reason, you need to skip one or more tests, you can do so +by setting the NOTMUCH_SKIP_TESTS variable to the name of one or more +sections of tests. + +For example: + + $ NOTMUCH_SKIP_TESTS="search reply" make test + +Even more fine-grained skipping is possible by appending a test number +(or glob pattern) after the section name. For example, the first +search test and the second reply test could be skipped with: + + $ NOTMUCH_SKIP_TESTS="search.1 reply.2" make test + +Note that some tests in the existing test suite rely on previous test +items, so you cannot arbitrarily skip any test and expect the +remaining tests to be unaffected. + +Writing Tests +------------- +The test script is written as a shell script. It should start +with the standard "#!/bin/bash" with copyright notices, and an +assignment to variable 'test_description', like this: + + #!/bin/bash + # + # Copyright (c) 2005 Junio C Hamano + # + + test_description='xxx test (option --frotz) + + This test exercises the "notmuch xxx" command when + given the option --frotz.' + +Source 'test-lib.sh' +-------------------- +After assigning test_description, the test script should source +test-lib.sh like this: + + . ./test-lib.sh + +This test harness library does the following things: + + - If the script is invoked with command line argument --help + (or -h), it shows the test_description and exits. + + - Creates a temporary directory with default notmuch-config and a + mail store with a corpus of mail, (initially, 50 early messages + sent to the notmuch list). This directory is + test/tmp.. The path to notmuch-config is exported in + NOTMUCH_CONFIG environment variable and mail store path is stored + in MAIL_DIR variable. + + - Defines standard test helper functions for your scripts to + use. These functions are designed to make all scripts behave + consistently when command line arguments --verbose (or -v), + --debug (or -d), and --immediate (or -i) is given. + +End with test_done +------------------ +Your script will be a sequence of tests, using helper functions +from the test harness library. At the end of the script, call +'test_done'. + +Test harness library +-------------------- +There are a handful helper functions defined in the test harness +library for your script to use. + + test_expect_success