[notmuch] SWIG (and particularly Python) bindings
authorBen Gamari <bgamari@gmail.com>
Tue, 29 Dec 2009 09:16:43 +0000 (04:16 +1900)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:35:56 +0000 (09:35 -0800)
ce/2c48b57a3b7974e0947caeb92307f2ec052deb [new file with mode: 0644]

diff --git a/ce/2c48b57a3b7974e0947caeb92307f2ec052deb b/ce/2c48b57a3b7974e0947caeb92307f2ec052deb
new file mode 100644 (file)
index 0000000..e91a6ed
--- /dev/null
@@ -0,0 +1,373 @@
+Return-Path: <bgamari@gmail.com>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       by olra.theworths.org (Postfix) with ESMTP id 48AAB431FBC\r
+       for <notmuch@notmuchmail.org>; Tue, 29 Dec 2009 01:16:49 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+       with ESMTP id lZx0k+k4kSYT for <notmuch@notmuchmail.org>;\r
+       Tue, 29 Dec 2009 01:16:48 -0800 (PST)\r
+Received: from mail-qy0-f202.google.com (mail-qy0-f202.google.com\r
+       [209.85.221.202])\r
+       by olra.theworths.org (Postfix) with ESMTP id 0C4CC431FAE\r
+       for <notmuch@notmuchmail.org>; Tue, 29 Dec 2009 01:16:47 -0800 (PST)\r
+Received: by qyk40 with SMTP id 40so5264405qyk.22\r
+       for <notmuch@notmuchmail.org>; Tue, 29 Dec 2009 01:16:46 -0800 (PST)\r
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma;\r
+       h=domainkey-signature:received:received:content-type:subject:from:to\r
+       :date:message-id:user-agent:content-transfer-encoding;\r
+       bh=cnZ8E1369lPl4czU/3aXQAa3yUO9YDGIOQ6GLJ3KAao=;\r
+       b=TG3MuSKA2ZYBbmoODzGPKhp1zEyWM7vYaWQjHG9KrbuTXpH6I4tUTVFYJ+T+KaiQh0\r
+       c/SUa4ridmXxuR9kp8AiZzhwnWY/aGUaAnii17LQDgZlVy3WQ7BgGnEJpucLyTDb+CDF\r
+       ugoTw8Lgxtf0V7SprjUFhI5XdOyhARltmHWr4=\r
+DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma;\r
+       h=content-type:subject:from:to:date:message-id:user-agent\r
+       :content-transfer-encoding;\r
+       b=KIG/6hxK0Z4QGZbPFIaF9PZL/LzYU2BMr2h2qBBL5JArwwpOeq5E0joyrNqXGA4V60\r
+       DBNnPLv7QrLl6aigCZQTEN30GERlmrSmQm8X1OFy5AfbrZolhJM/HCTtaqeDXud9glEN\r
+       frE34NFuEvIRHNuy2K/5EO2IvUd6iVA/c1li4=\r
+Received: by 10.224.26.200 with SMTP id f8mr7947250qac.156.1262078205941;\r
+       Tue, 29 Dec 2009 01:16:45 -0800 (PST)\r
+Received: from localhost (c-24-61-223-13.hsd1.nh.comcast.net [24.61.223.13])\r
+       by mx.google.com with ESMTPS id 6sm33608070qwd.26.2009.12.29.01.16.44\r
+       (version=TLSv1/SSLv3 cipher=RC4-MD5);\r
+       Tue, 29 Dec 2009 01:16:44 -0800 (PST)\r
+Content-Type: text/plain; charset=UTF-8\r
+From: Ben Gamari <bgamari@gmail.com>\r
+To: notmuch <notmuch@notmuchmail.org>\r
+Date: Tue, 29 Dec 2009 04:16:43 -0500\r
+Message-Id: <1262078148-sup-7891@ben-laptop>\r
+User-Agent: Sup/git\r
+Content-Transfer-Encoding: 8bit\r
+X-Mailman-Approved-At: Tue, 29 Dec 2009 21:42:08 -0800\r
+Subject: [notmuch] SWIG (and particularly Python) bindings\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.12\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+       <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Tue, 29 Dec 2009 09:16:49 -0000\r
+\r
+Hey all,\r
+\r
+I've been looking at switching away from sup recently to something with\r
+a slightly little less everything-and-the-kitchen-sink philosophy.\r
+Notmuch looks excellent, although it appears that its current front-end\r
+for my editor of choice (vim) is a little lacking in some ways\r
+(operations involving a call to notmuch invariably lock up vi for the\r
+duration of the operation).\r
+\r
+Regardless, I thought it might be nice to have access to the notmuch\r
+backend from a language other than C (preferably my high-level language\r
+of choice, python). To this end, I took a few hours today acquainting\r
+myself with SWIG and produced these bindings for Python. Unfortunately,\r
+it doesn't appear that SWIG has particularly good support for\r
+object-oriented C, so the approach I took was using SWIG to generate the\r
+low-level glue, on top of which I wrote a properly structured set of\r
+Python bindings. While I theoretically have full interface coverage,\r
+I unfortunately haven't had a chance to use these for anything useful,\r
+so who knows whether much of it actually works. Regardless, I thought\r
+folks might be interested.\r
+\r
+While the bindings are currently in the form of a patch to notmuch\r
+(creating a top-level swig directory in the source tree), they could\r
+certainly be moved out-of-tree if the powers that be don't feel it\r
+appropriate to include them. Unfortunately, the build system is\r
+currently almost entirely independent from the notmuch build system. If\r
+these are to be included in-tree, I would be curious to hear people have\r
+to say about how we might integrate it into the sup build system.\r
+\r
+Hope this helps someone. I'll hopefully have a chance to work with the\r
+bindings soon and will send a new set of patches once I think I've found\r
+all of the obvious issues.\r
+\r
+- Ben\r
+\r
+\r
+---\r
+ swig/Makefile        |   18 ++++\r
+ swig/notmuch.py      |  222 ++++++++++++++++++++++++++++++++++++++++++++++++++\r
+ swig/notmuch_funcs.i |    9 ++\r
+ 3 files changed, 249 insertions(+), 0 deletions(-)\r
+ create mode 100644 swig/Makefile\r
+ create mode 100644 swig/notmuch.py\r
+ create mode 100644 swig/notmuch_funcs.i\r
+\r
+diff --git a/swig/Makefile b/swig/Makefile\r
+new file mode 100644\r
+index 0000000..c01c427\r
+--- /dev/null\r
++++ b/swig/Makefile\r
+@@ -0,0 +1,18 @@\r
++include ../Makefile.config\r
++\r
++INCLUDES=-I../lib -I/usr/include/python2.6\r
++CFLAGS=${INCLUDES}\r
++\r
++all : python\r
++\r
++python : _notmuch_funcs.so notmuch.py\r
++\r
++_notmuch_funcs.so : notmuch_funcs_wrap.o\r
++      g++ -shared ${XAPIAN_LDFLAGS} ${GMIME_LDFLAGS} ${TALLOC_LDFLAGS} -o $@ $< ../lib/notmuch.a\r
++\r
++%_wrap.o : %_wrap.c\r
++      gcc ${CFLAGS} -fPIC -c -o $@ $<\r
++\r
++%_wrap.c %.py : %.i \r
++      swig -python ${INCLUDES} $<\r
++\r
+diff --git a/swig/notmuch.py b/swig/notmuch.py\r
+new file mode 100644\r
+index 0000000..95b81ad\r
+--- /dev/null\r
++++ b/swig/notmuch.py\r
+@@ -0,0 +1,222 @@\r
++import notmuch_funcs as nm\r
++from datetime import datetime\r
++\r
++class Exception(Exception):\r
++        def get_message():\r
++                errors = {\r
++                        nm.NOTMUCH_STATUS_OUT_OF_MEMORY: 'out of memory',\r
++                        nm.NOTMUCH_STATUS_READONLY_DATABASE: 'database opened as read-only',\r
++                        nm.NOTMUCH_STATUS_XAPIAN_EXCEPTION: 'xapian error',\r
++                        nm.NOTMUCH_STATUS_FILE_ERROR: 'file error',\r
++                        nm.NOTMUCH_STATUS_FILE_NOT_EMAIL: 'file not email message',\r
++                        nm.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: 'duplicate message id',\r
++                        nm.NOTMUCH_STATUS_NULL_POINTER: 'null pointer',\r
++                        nm.NOTMUCH_STATUS_TAG_TOO_LONG: 'tag name too long',\r
++                        nm.NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: 'unbalanced freeze/thaw',\r
++                }\r
++                return errors.get(self.status, 'unknown error')\r
++\r
++        def __init__(self, status):\r
++                self.status = status\r
++\r
++def _handle_status(status):\r
++        if (status != nm.NOTMUCH_STATUS_SUCCESS):\r
++                raise Exception(status)\r
++\r
++class Database(object):\r
++        MODE_READ_ONLY = nm.NOTMUCH_DATABASE_MODE_READ_ONLY\r
++        MODE_READ_WRITE = nm.NOTMUCH_DATABASE_MODE_READ_WRITE\r
++\r
++        def __init__(self, db):\r
++                if not db:\r
++                        raise "Failed to open database"\r
++                self.db = db\r
++\r
++        @staticmethod\r
++        def create(path):\r
++                return Database(nm.notmuch_database_create(path))\r
++\r
++        @staticmethod\r
++        def open(path, mode):\r
++                return Database(nm.notmuch_database_open(path, mode))\r
++\r
++        def close(self):\r
++                nm.notmuch_database_close(self.db)\r
++\r
++        def get_path(self):\r
++                return nm.notmuch_database_get_path(self.db)\r
++\r
++        def set_timestamp(self, key, timestamp):\r
++                _handle_status(nm.notmuch_database_set_timestamp(self.db, key, timestamp))\r
++\r
++        def get_timestamp(self, key):\r
++                return datetime.fromtimestamp(nm.notmuch_database_get_timestamp(self.db, key))\r
++\r
++        def add_message(self, filename, message):\r
++                _handle_status(nm.notmuch_database_add_message(self.db, filename, message.message))\r
++\r
++        def find_message(self, message_id):\r
++                return Message(nm.notmuch_database_find_message(self.db, message_id))\r
++\r
++        def get_all_tags(self):\r
++                return Tags(nm.notmuch_database_get_all_tags(self.db))\r
++\r
++        def __destroy__(self):\r
++                self.close()\r
++        \r
++class Query(object):\r
++        def __init__(self, db, query_string):\r
++                self.query = nm.notmuch_query_create(db, query_string)\r
++                if not self.query: # This might not work\r
++                        raise "Failed to create query"\r
++\r
++        def set_sort(self, sort):\r
++                nm.notmuch_query_set_sort(self.query, sort)\r
++                \r
++        def search_threads(self):\r
++                return Threads(nm.notmuch_query_search_threads(self.query))\r
++\r
++        def search_messages(self):\r
++                return Messages(nm.notmuch_query_search_messages(self.query))\r
++\r
++        def count_message(self):\r
++                return nm.notmuch_query_count_messages(self.query)\r
++\r
++        def __destroy__(self):\r
++                nm.notmuch_query_destroy(self.query)\r
++\r
++class Tags(object):\r
++        def __init__(self, tags):\r
++                self.tags = tags\r
++\r
++        def __iter__(self):\r
++                return self\r
++\r
++        def next(self):\r
++                if not nm.notmuch_tags_has_more(self.tags):\r
++                        raise StopIteration\r
++                else:\r
++                        h = nm.notmuch_tags_get(self.tags)\r
++                        nm.notmuch_tags_advance(self.tags)\r
++                        return h\r
++\r
++        def __destroy__(self):\r
++                nm.notmuch_messages_destroy(self.tags)\r
++\r
++class Thread(object):\r
++        def __init__(self, thread):\r
++                self.thread = thread\r
++\r
++        def get_thread_id(self):\r
++                return nm.notmuch_thread_get_thread_id(self.thread)\r
++\r
++        def get_total_messages(self):\r
++                return nm.notmuch_thread_total_messages(self.thread)\r
++        \r
++        def get_toplevel_messages(self):\r
++                return Messages(nm.notmuch_thread_get_toplevel_messages(self.thread))\r
++\r
++        def get_matched_messages(self):\r
++                return nm.notmuch_thread_get_matched_messages(self.thread)\r
++\r
++        def get_authors(self):\r
++                return nm.notmuch_thread_get_authors(self.thread)\r
++\r
++        def get_subject(self):\r
++                return nm.notmuch_thread_get_subject(self.thread)\r
++\r
++        def get_oldest_date(self):\r
++                return nm.notmuch_thread_get_oldest_date(self.thread)\r
++\r
++        def get_newest_date(self):\r
++                return nm.notmuch_thread_get_newest_date(self.thread)\r
++\r
++        def get_tags(self):\r
++                return Tags(nm.notmuch_thread_get_tags(self.thread))\r
++\r
++        def __destroy__(self):\r
++                nm.notmuch_thread_destroy(self.thread)\r
++\r
++class Threads(object):\r
++        def __init__(self, threads):\r
++                self.threads = threads\r
++\r
++        def __iter__(self):\r
++                return self\r
++\r
++        def next(self):\r
++                if not nm.notmuch_threads_has_more(self.threads):\r
++                        raise StopIteration\r
++                else:\r
++                        h = Thread(nm.notmuch_threads_get(self.threads))\r
++                        nm.notmuch_threads_advance(self.threads)\r
++                        return h\r
++\r
++        def __destroy__(self):\r
++                nm.notmuch_threads_destroy(self.threads)\r
++\r
++class Messages(object):\r
++        def __init__(self, messages):\r
++                self.messages = messages\r
++\r
++        def __iter__(self):\r
++                return self\r
++\r
++        def next(self):\r
++                if not nm.notmuch_messages_has_more(self.messages):\r
++                        raise StopIteration\r
++                else:\r
++                        h = Message(nm.notmuch_messages_get(self.messages))\r
++                        nm.notmuch_messages_advance(self.messages)\r
++                        return h\r
++\r
++        def __destroy__(self):\r
++                nm.notmuch_messages_destroy(self.messages)\r
++\r
++        def collect_tags(self):\r
++                return Tags(nm.notmuch_messages_collect_tags(self.messages))\r
++\r
++class Message(object):\r
++        def __init__(self, message):\r
++                self.message = message\r
++\r
++        def get_replies(self):\r
++                return Messages(nm.notmuch_message_get_replies(self.message))\r
++\r
++        def get_filename(self):\r
++                return nm.notmuch_message_get_filename(self.message)\r
++\r
++        def get_flag(self, flag):\r
++                return bool(nm.notmuch_message_get_flag(self.message, flag))\r
++\r
++        def set_flag(self, flag, value):\r
++                return nm.notmuch_message_set_flag(self.message, flag, value)\r
++\r
++        def get_date(self):\r
++                return datetime.fromtimestamp(nm.notmuch_message_get_date(self.message))\r
++\r
++        def get_header(self, header):\r
++                return nm.notmuch_message_get_header(self.message, header)\r
++\r
++        def get_tags(self):\r
++                return Tags(nm.notmuch_message_get_tags(self.message))\r
++\r
++        def add_tag(self, tag):\r
++                _handle_status(nm.notmuch_message_add_tag(self.message, tag))\r
++\r
++        def remove_tag(self, tag):\r
++                _handle_status(nm.notmuch_message_remove_tag(self.message, tag))\r
++\r
++        def remove_all_tags(self):\r
++                nm.notmuch_message_remove_all_tags(self.message)\r
++\r
++        def freeze(self):\r
++                nm.notmuch_message_freeze(self.message)\r
++\r
++        def thaw(self):\r
++                nm.notmuch_message_thaw(self.message)\r
++\r
++        def __destroy__(self):\r
++                nm.notmuch_message_destroy(self.message)\r
++\r
++\r
+diff --git a/swig/notmuch_funcs.i b/swig/notmuch_funcs.i\r
+new file mode 100644\r
+index 0000000..cc1826e\r
+--- /dev/null\r
++++ b/swig/notmuch_funcs.i\r
+@@ -0,0 +1,9 @@\r
++%module notmuch_funcs\r
++\r
++%{\r
++#define SWIG_FILE_WITH_INIT\r
++#include "notmuch.h"\r
++%}\r
++\r
++%include "notmuch.h"\r
++\r
+-- \r
+1.6.3.3\r