Re: [PATCH v4 13/16] add indexopts to notmuch python bindings.
[notmuch-archives.git] / fc / ea1c99aa602a1e3280949ad56a07d6541d2a59
1 Return-Path: <deepfire@feelingofgreen.ru>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id A10FE40BC64\r
6         for <notmuch@notmuchmail.org>; Mon, 16 Aug 2010 12:38:03 -0700 (PDT)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: 3.185\r
10 X-Spam-Level: ***\r
11 X-Spam-Status: No, score=3.185 tagged_above=-999 required=5\r
12         tests=[BAYES_50=0.8, DATE_IN_PAST_03_06=1.592, RDNS_NONE=0.793]\r
13         autolearn=no\r
14 Received: from olra.theworths.org ([127.0.0.1])\r
15         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
16         with ESMTP id m1022iNIiAx5 for <notmuch@notmuchmail.org>;\r
17         Mon, 16 Aug 2010 12:37:52 -0700 (PDT)\r
18 Received: from feelingofgreen.ru (unknown [80.92.100.69])\r
19         by olra.theworths.org (Postfix) with SMTP id DE54040BC6C\r
20         for <notmuch@notmuchmail.org>; Mon, 16 Aug 2010 12:37:51 -0700 (PDT)\r
21 Received: (qmail 29960 invoked by uid 1000); 16 Aug 2010 19:38:43 +0400\r
22 From: Samium Gromoff <_deepfire@feelingofgreen.ru>\r
23 To: notmuch@notmuchmail.org\r
24 Subject: Integration with training-based bayesian filters \r
25 References: <AANLkTinuEAhRo5vAjF0e9k8JGt9gVooLNimrBVyGuZtZ@mail.gmail.com>\r
26 User-Agent: Notmuch/0.3.1-58-g6607fd6 (http://notmuchmail.org) Emacs/23.2.1\r
27         (x86_64-pc-linux-gnu)\r
28 Date: Mon, 16 Aug 2010 19:38:43 +0400\r
29 Message-ID: <877hjqtvng.fsf@auriga.deep>\r
30 MIME-Version: 1.0\r
31 Content-Type: text/plain; charset=us-ascii\r
32 X-BeenThere: notmuch@notmuchmail.org\r
33 X-Mailman-Version: 2.1.13\r
34 Precedence: list\r
35 List-Id: "Use and development of the notmuch mail system."\r
36         <notmuch.notmuchmail.org>\r
37 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
38         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
39 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
40 List-Post: <mailto:notmuch@notmuchmail.org>\r
41 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
42 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
43         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
44 X-List-Received-Date: Mon, 16 Aug 2010 19:38:03 -0000\r
45 \r
46 Good day folks,\r
47 \r
48 My "+notmuch AND train" query on the local notmuch list archive didn't\r
49 yield anything relevant, so I've got at least one excuse if the\r
50 question I'm going to pose was already answered to death here.\r
51 \r
52 So, how is a notmuch user supposed to integrate a train-based message\r
53 classifier like crm114[1], which operates as follows:\r
54 \r
55   - the filter->you information flow is established by prepending\r
56     either "ADV: " or "UNS: " strings to the message subject, denoting,\r
57     correspondingly, either "spam" or "please tell me if this is spam"\r
58     categories.  The non-spam messages, naturally, have their subject\r
59     lines unmodified.\r
60 \r
61   - the you->filter information flow is established by taking the\r
62     message file whose status you want to pin down (mostly those marked\r
63     as UNS, because after a while crm144 gets really really good),\r
64     and piping it to the classifier executable.\r
65 \r
66 One thing is certain -- we're talking elisp territory here.\r
67 \r
68 Another is certain, also -- such questions appear at some point, sooner\r
69 or later, in the life of every mail user agent.  Again, sorry if\r
70 I failed the due diligence part of prior art discovery.\r
71 \r
72 Now to some answers (the unexpected part):\r
73 \r
74 The first part is handled easily, well, by a composition of procmailing\r
75 the "ADV: "-prefixed messages out of one's sight, which becomes a\r
76 plausible strategy once the classifier becomes clueful enough, and\r
77 by adding a simple xapian "subject:" rule for "UNS: "-prefixed ones.\r
78 \r
79 The second part can be solved either in a way pleasant to the user,\r
80 or easily.\r
81 \r
82 The easy way is to expect the user enter the spam thread, which contains\r
83 exactly one message (never seen longer spam threads, still wondering\r
84 why...), and then press some key and confirm the destination, station\r
85 purple hell.  Then you exit the thread.  To enter another one...\r
86 \r
87 So, after a couple of minutes of processing the backlog, it's becoming\r
88 painfully clear, that you don't want to spend more effort on these\r
89 one-message spam threads than pressing 's', and then confirming it with\r
90 'y', avoiding the painful, distracting and redrawing thread enter/exit\r
91 sequence.\r
92 \r
93 Note, that this conveniently avoids the question of non-spam messages,\r
94 which actually often land within threads, but I'd like to keep this\r
95 aside, sorry for incomplete solutions.\r
96 \r
97 So, the crux is, to pipe the file to the classifier you need the filename,\r
98 and the filename appears to be easily available only in the 'show' mode.\r
99 \r
100 I've had to introduce some code to operate on single-message threads,\r
101 or actually, threads with all messages ignored, but the first one.\r
102 \r
103 So, here goes, the solution modulo the conveniently avoided question\r
104 of non-spam messages:\r
105 \r
106 \r
107 (defun notmuch-pipe-file (filename command)\r
108   (apply 'start-process-shell-command "notmuch-pipe-command" "*notmuch-pipe*"\r
109          (list command " < " (shell-quote-argument filename))))\r
110 \r
111 (defun notmuch-query (query)\r
112   (notmuch-query-get-threads (append (list "\'") query (list "\'"))))\r
113 \r
114 (defun notmuch-result-firstmsg-property (result property)\r
115   (plist-get (caaar result) property))\r
116 \r
117 (defun notmuch-result-backend-remove-tags (result tags)\r
118   (apply 'notmuch-call-notmuch-process\r
119          (append (cons "tag" (mapcar (lambda (s) (concat "-" s)) tags))\r
120                  (cons (concat "id:" (notmuch-result-firstmsg-property result :id)) nil))))\r
121 \r
122 (defun notmuch-search-result-remove-tags (result tags)\r
123   "Remove a tag from the current message.  RESULT is not updated."\r
124   (let ((current-tags (notmuch-result-firstmsg-property result :tags)))\r
125     (if (intersection current-tags tags :test 'string=)\r
126         ;; new result tags are (sort (set-difference current-tags tags :test 'string=) 'string<)\r
127         ;; however, it's unlikely we'll need them, so no need to update\r
128         (notmuch-result-backend-remove-tags result tags))))\r
129 \r
130 (defun notmuch-search-query-current-thread ()\r
131   (notmuch-query (list (notmuch-search-find-thread-id))))\r
132 \r
133 (defun notmuch-show-pipe-current-message (command)\r
134   "Pipe the message currently pointed at within the show mode,\r
135 through COMMAND."\r
136   (interactive "sPipe message to command: ")\r
137   (notmuch-pipe-file (notmuch-show-get-filename) command))\r
138 \r
139 (defun notmuch-search-pipe-current-message (command)\r
140   "Pipe the first message of the thread currently pointed at within\r
141 the search mode, through COMMAND."\r
142   (interactive "sPipe message to command: ")\r
143   (let* ((result (notmuch-search-query-current-thread))\r
144          (filename (notmuch-result-firstmsg-property result :filename)))\r
145     (notmuch-pipe-file filename command)\r
146     result))\r
147 \r
148 (setq mark-as-good-command "~/bin/stdin-is-good"\r
149       mark-as-spam-command "~/bin/stdin-is-spam"\r
150       spam-tagdrop-list '("inbox" "unread" "sent" "train"))\r
151 \r
152 (defun make-mark-as-good (piper)\r
153   "Mark the message as good."\r
154   (lexical-let ((piper piper))\r
155     (lambda ()\r
156       (interactive)\r
157       (if (y-or-n-p "Mark as good? ")\r
158           (progn\r
159             (funcall piper mark-as-good-command)\r
160             (forward-line 1))))))\r
161 \r
162 (defun make-mark-as-spam (piper searchp)\r
163   "Mark the message as spam."\r
164   (lexical-let ((piper piper)\r
165                 (searchp searchp))\r
166     (lambda ()\r
167       (interactive)\r
168       (if (y-or-n-p "Mark as spam? ")\r
169           (let ((maybe-result (funcall piper mark-as-spam-command)))\r
170             (if searchp\r
171                 (progn\r
172                   (notmuch-search-result-remove-tags maybe-result spam-tagdrop-list)\r
173                   (forward-line 1))\r
174                 (notmuch-show-mark-read)))))))\r
175 \r
176 (define-key notmuch-show-mode-map "g"    (make-mark-as-good 'notmuch-show-pipe-current-message))\r
177 (define-key notmuch-show-mode-map "s"    (make-mark-as-spam 'notmuch-show-pipe-current-message nil))\r
178 (define-key notmuch-search-mode-map "g"  (make-mark-as-good 'notmuch-search-pipe-current-message))\r
179 (define-key notmuch-search-mode-map "s"  (make-mark-as-spam 'notmuch-search-pipe-current-message t))\r
180 \r
181 \r
182 I'll leave it to the more qualified people to decide which part (and in\r
183 which form) is supposed to go into notmuch, and which is destined to\r
184 live in the end-user's init file.\r
185 \r
186 \r
187 -- \r
188 regards,\r
189   Samium Gromoff\r
190 --\r
191 1. http://crm114.sourceforge.net/\r
192 \r
193 --\r
194 "Actually I made up the term 'object-oriented', and I can tell you I\r
195 did not have C++ in mind." - Alan Kay (OOPSLA 1997 Keynote)\r