]> code.delx.au - gnu-emacs-elpa/blob - packages/debbugs/debbugs.el
Activate `soap-invoke-async' in debbugs
[gnu-emacs-elpa] / packages / debbugs / debbugs.el
1 ;;; debbugs.el --- SOAP library to access debbugs servers
2
3 ;; Copyright (C) 2011-2016 Free Software Foundation, Inc.
4
5 ;; Author: Michael Albinus <michael.albinus@gmx.de>
6 ;; Keywords: comm, hypermedia
7 ;; Package: debbugs
8 ;; Version: 0.8
9
10 ;; This file is not part of GNU Emacs.
11
12 ;; This program is free software: you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
16
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
24
25 ;;; Commentary:
26
27 ;; This package provides basic functions to access a Debbugs SOAP
28 ;; server (see <http://wiki.debian.org/DebbugsSoapInterface>).
29
30 ;; The function "get_versions" is not implemented (yet). "search_est"
31 ;; is an extension on <http://debbugs.gnu.org>.
32
33 ;;; Code:
34
35 ;(setq soap-debug t message-log-max t)
36 (require 'soap-client)
37 (eval-when-compile (require 'cl))
38
39 (declare-function soap-invoke-async "soap-client")
40 (declare-function async-start "async")
41 (declare-function async-get "async")
42
43 (defgroup debbugs nil
44 "Debbugs library"
45 :group 'hypermedia)
46
47 (defcustom debbugs-servers
48 '(("gnu.org"
49 :wsdl "http://debbugs.gnu.org/cgi/soap.cgi?WSDL"
50 :bugreport-url "http://debbugs.gnu.org/cgi/bugreport.cgi")
51 ("debian.org"
52 :wsdl "http://bugs.debian.org/cgi-bin/soap.cgi?WSDL"
53 :bugreport-url "http://bugs.debian.org/cgi-bin/bugreport.cgi"))
54 "*List of Debbugs server specifiers.
55 Each entry is a list that contains a string identifying the port
56 name and the server parameters in keyword-value form. Allowed
57 keywords are:
58
59 `:wsdl' -- Location of WSDL. The value is a string with URL that
60 should return the WSDL specification of Debbugs/SOAP service.
61
62 `:bugreport-url' -- URL of the server script that returns mboxes
63 with bug logs.
64
65 The list initially contains two predefined and configured Debbugs
66 servers: \"gnu.org\" and \"debian.org\"."
67 :group 'debbugs
68 :link '(custom-manual "(debbugs)Debbugs server specifiers")
69 :type '(choice
70 (const nil)
71 (repeat
72 (cons :tag "Server"
73 (string :tag "Port name")
74 (checklist :tag "Options" :greedy t
75 (group :inline t
76 (const :format "" :value :wsdl)
77 (string :tag "WSDL"))
78 (group :inline t
79 (const :format "" :value :bugreport-url)
80 (string :tag "Bugreport URL")))))))
81
82 (defcustom debbugs-port "gnu.org"
83 "The port instance to be applied from `debbugs-wsdl'.
84 This corresponds to the Debbugs server to be accessed, either
85 \"gnu.org\", or \"debian.org\", or user defined port name."
86 ;; Maybe we should create an own group?
87 :group 'debbugs
88 :type '(choice :tag "Debbugs server" (const "gnu.org") (const "debian.org")
89 (string :tag "user defined port name")))
90
91 ;; It would be nice if we could retrieve it from the debbugs server.
92 ;; Not supported yet.
93 (defconst debbugs-wsdl
94 (soap-load-wsdl
95 (expand-file-name
96 "Debbugs.wsdl"
97 (if load-in-progress
98 (file-name-directory load-file-name)
99 default-directory)))
100 "The WSDL object to be used describing the SOAP interface.")
101
102 ;; Please do not increase this value, otherwise we would run into
103 ;; performance problems on the server. Maybe we need to change this a
104 ;; server specific value.
105 (defconst debbugs-max-hits-per-request 500
106 "The max number of bugs or results per soap invocation.")
107
108 (defvar debbugs-soap-invoke-async-object nil
109 "The object manipulated by `debbugs-soap-invoke-async'.")
110
111 (defun debbugs-soap-invoke-async (operation-name &rest parameters)
112 "Invoke the SOAP connection asynchronously.
113 If possible, it uses `soap-invoke-async' from soapclient 3.0.
114 Otherwise, `async-start' from the async package is used."
115 (if (fboundp 'soap-invoke-async)
116 ;; This is soap-client 3.0.
117 (apply
118 'soap-invoke-async
119 (lambda (response &rest args)
120 (setq debbugs-soap-invoke-async-object
121 (append debbugs-soap-invoke-async-object (car response))))
122 nil
123 debbugs-wsdl debbugs-port operation-name parameters)
124 ;; Fallback with async.
125 (async-start
126 `(lambda ()
127 (load ,(locate-library "soap-client"))
128 (apply
129 'soap-invoke
130 (soap-load-wsdl
131 ,(expand-file-name
132 "Debbugs.wsdl"
133 (file-name-directory (locate-library "debbugs"))))
134 ,debbugs-port ,operation-name ',parameters)))))
135
136 (defun debbugs-get-bugs (&rest query)
137 "Return a list of bug numbers which match QUERY.
138
139 QUERY is a sequence of keyword-value pairs where the values are
140 strings, i.e. :KEYWORD \"VALUE\" [:KEYWORD \"VALUE\"]*
141
142 The keyword-value pair is a subquery. The keywords are allowed to
143 have multiple occurrence within the query at any place. The
144 subqueries with the same keyword form the logical subquery, which
145 returns the union of bugs of every subquery it contains.
146
147 The result of the QUERY is an intersection of results of all
148 subqueries.
149
150 Valid keywords are:
151
152 :package -- The value is the name of the package a bug belongs
153 to, like \"emacs\", \"coreutils\", \"gnus\", or \"tramp\".
154
155 :src -- This is used to retrieve bugs that belong to source
156 with given name.
157
158 :severity -- This is the severity of the bug. The exact set of
159 allowed values depends on the Debbugs port. Examples are
160 \"normal\", \"minor\", \"wishlist\" etc.
161
162 :tag -- An arbitrary string the bug is annotated with.
163 Usually, this is used to mark the status of the bug, like
164 \"fixed\", \"moreinfo\", \"notabug\", \"patch\",
165 \"unreproducible\" or \"wontfix\". The exact set of tags
166 depends on the Debbugs port.
167
168 :owner -- This is used to identify bugs by the owner's email
169 address. The special email address \"me\" is used as pattern,
170 replaced with `user-mail-address'.
171
172 :submitter -- With this keyword it is possible to filter bugs
173 by the submitter's email address. The special email address
174 \"me\" is used as pattern, replaced with `user-mail-address'.
175
176 :maint -- This is used to find bugs of the packages which are
177 maintained by the person with the given email address. The
178 special email address \"me\" is used as pattern, replaced with
179 `user-mail-address'.
180
181 :correspondent -- This allows to find bug reports where the
182 person with the given email address has participated. The
183 special email address \"me\" is used as pattern, replaced with
184 `user-mail-address'.
185
186 :affects -- With this keyword it is possible to find bugs which
187 affect the package with the given name. The bugs are chosen by
188 the value of field `affects' in bug's status. The returned bugs
189 do not necessary belong to this package.
190
191 :status -- Status of bug. Valid values are \"done\",
192 \"forwarded\" and \"open\".
193
194 :archive -- A keyword to filter for bugs which are already
195 archived, or not. Valid values are \"0\" (not archived),
196 \"1\" (archived) or \"both\". If this keyword is not given in
197 the query, `:archive \"0\"' is assumed by default.
198
199 Example. Get all opened and forwarded release critical bugs for
200 the packages which are maintained by \"me\" and which have a
201 patch:
202
203 \(debbugs-get-bugs :maint \"me\" :tag \"patch\"
204 :severity \"critical\"
205 :status \"open\"
206 :severity \"grave\"
207 :status \"forwarded\"
208 :severity \"serious\")"
209
210 (let (vec kw key val)
211 ;; Check query.
212 (while (and (consp query) (<= 2 (length query)))
213 (setq kw (pop query)
214 val (pop query))
215 (unless (and (keywordp kw) (stringp val))
216 (error "Wrong query: %s %s" kw val))
217 (setq key (substring (symbol-name kw) 1))
218 (case kw
219 ((:package :severity :tag :src :affects)
220 ;; Value shall be one word.
221 (if (string-match "\\`\\S-+\\'" val)
222 (setq vec (vconcat vec (list key val)))
223 (error "Wrong %s: %s" key val)))
224 ((:owner :submitter :maint :correspondent)
225 ;; Value is an email address.
226 (if (string-match "\\`\\S-+\\'" val)
227 (progn
228 (when (string-equal "me" val)
229 (setq val user-mail-address))
230 (when (string-match "<\\(.+\\)>" val)
231 (setq val (match-string 1 val)))
232 (setq vec (vconcat vec (list key val))))
233 (error "Wrong %s: %s" key val)))
234 (:status
235 ;; Possible values: "done", "forwarded" and "open"
236 (if (string-match "\\`\\(done\\|forwarded\\|open\\)\\'" val)
237 (setq vec (vconcat vec (list key val)))
238 (error "Wrong %s: %s" key val)))
239 (:archive
240 ;; Value is `0' or `1' or `both'.
241 (if (string-match "\\`\\(0\\|1\\|both\\)\\'" val)
242 (setq vec (vconcat vec (list key val)))
243 (error "Wrong %s: %s" key val)))
244 (t (error "Unknown key: %s" kw))))
245
246 (unless (null query)
247 (error "Unknown key: %s" (car query)))
248 (sort (car (soap-invoke debbugs-wsdl debbugs-port "get_bugs" vec)) '<)))
249
250 (defun debbugs-newest-bugs (amount)
251 "Return the list of bug numbers, according to AMOUNT (a number) latest bugs."
252 (sort (car (soap-invoke debbugs-wsdl debbugs-port "newest_bugs" amount)) '<))
253
254 (defun debbugs-get-status (&rest bug-numbers)
255 "Return a list of status entries for the bugs identified by BUG-NUMBERS.
256
257 Every returned entry is an association list with the following attributes:
258
259 `bug_num': The bug number.
260
261 `package': A list of package names the bug belongs to.
262
263 `severity': The severity of the bug report. This can be
264 \"important\", \"grave\", \"normal\", \"minor\" or \"wishlist\".
265
266 `tags': The status of the bug report, a list of strings. This
267 can be \"fixed\", \"notabug\", \"wontfix\", \"unreproducible\",
268 \"moreinfo\" or \"patch\".
269
270 `pending': The string \"pending\", \"forwarded\" or \"done\".
271
272 `subject': Subject/Title of the bugreport.
273
274 `originator': Submitter of the bugreport.
275
276 `mergedwith': A list of bug numbers this bug was merged with.
277 If it is a single bug, then this attribute contains just a
278 number.
279
280 `source': Source package name of the bug report.
281
282 `date': Date of bug creation.
283
284 `log_modified', `last_modified': Date of last update.
285
286 `found_date', `fixed_date': Date of bug report / bug fix
287 \(empty for now).
288
289 `done': The email address of the worker who has closed the bug (if done).
290
291 `archived': `t' if the bug is archived, `nil' otherwise.
292
293 `unarchived': The date the bug has been unarchived, if ever.
294
295 `found_versions', `fixed_versions': List of version strings.
296
297 `forwarded': A URL or an email address.
298
299 `blocks': A list of bug numbers this bug blocks.
300
301 `blockedby': A list of bug numbers this bug is blocked by.
302
303 `msgid': The message id of the initial bug report.
304
305 `owner': Who is responsible for fixing.
306
307 `location': Always the string \"db-h\" or \"archive\".
308
309 `affects': A list of package names.
310
311 `summary': Arbitrary text.
312
313 Example:
314
315 \(debbugs-get-status 10)
316
317 => ;; Attributes with empty values are not shown
318 \(\(\(bug_num . 10)
319 \(source . \"unknown\")
320 \(date . 1203606305.0)
321 \(msgid . \"<87zltuz7eh.fsf@freemail.hu>\")
322 \(severity . \"wishlist\")
323 \(owner . \"Magnus Henoch <mange@freemail.hu>\")
324 \(log_modified . 1261079402.0)
325 \(location . \"db-h\")
326 \(subject . \"url-gw should support HTTP CONNECT proxies\")
327 \(originator . \"Magnus Henoch <mange@freemail.hu>\")
328 \(last_modified . 1271200046.0)
329 \(pending . \"pending\")
330 \(package \"emacs\")))"
331 (when bug-numbers
332 (if (<= (length bug-numbers) debbugs-max-hits-per-request)
333 ;; Do it directly.
334 (setq debbugs-soap-invoke-async-object
335 (car (soap-invoke
336 debbugs-wsdl debbugs-port "get_status"
337 (apply 'vector bug-numbers))))
338
339 ;; Retrieve bugs asynchronously.
340 (let ((bug-ids bug-numbers)
341 results)
342 (setq debbugs-soap-invoke-async-object nil)
343 (while bug-ids
344 (setq results
345 (append
346 results
347 (list
348 (debbugs-soap-invoke-async
349 "get_status"
350 (apply
351 'vector
352 (butlast
353 bug-ids (- (length bug-ids)
354 debbugs-max-hits-per-request))))))
355
356 bug-ids
357 (last bug-ids (- (length bug-ids)
358 debbugs-max-hits-per-request))))
359
360 (dolist (res results)
361 (if (bufferp res)
362 ;; This is soap-client 3.0.
363 (while (buffer-live-p res)
364 (accept-process-output (get-buffer-process res) 0.1))
365 ;; Fallback with async.
366 (dolist (status (async-get res))
367 (setq debbugs-soap-invoke-async-object
368 (append debbugs-soap-invoke-async-object status)))))))
369
370 (mapcar
371 (lambda (x)
372 (let (y)
373 ;; "archived" is the number 1 or 0.
374 (setq y (assoc 'archived (cdr (assoc 'value x))))
375 (setcdr y (= (cdr y) 1))
376 ;; "found_versions" and "fixed_versions" are lists,
377 ;; containing strings or numbers.
378 (dolist (attribute '(found_versions fixed_versions))
379 (setq y (assoc attribute (cdr (assoc 'value x))))
380 (setcdr y (mapcar
381 (lambda (z) (if (numberp z) (number-to-string z) z))
382 (cdr y))))
383 ;; "mergedwith", "blocks" and "blockedby are strings,
384 ;; containing blank separated bug numbers.
385 (dolist (attribute '(mergedwith blocks blockedby))
386 (setq y (assoc attribute (cdr (assoc 'value x))))
387 (when (stringp (cdr y))
388 (setcdr y (mapcar
389 'string-to-number (split-string (cdr y) " " t)))))
390 ;; "package" is a string, containing comma separated
391 ;; package names. "keywords" and "tags" are strings,
392 ;; containing blank separated package names.
393 (dolist (attribute '(package keywords tags))
394 (setq y (assoc attribute (cdr (assoc 'value x))))
395 (when (stringp (cdr y))
396 (setcdr y (split-string (cdr y) ",\\| " t))))
397 (cdr (assoc 'value x))))
398 debbugs-soap-invoke-async-object)))
399
400 (defun debbugs-get-usertag (&rest query)
401 "Return a list of bug numbers which match QUERY.
402
403 QUERY is a sequence of keyword-value pairs where the values are
404 strings, i.e. :KEYWORD \"VALUE\" [:KEYWORD \"VALUE\"]*
405
406 Valid keywords are:
407
408 :user -- The value is the name of the package a bug belongs to,
409 like \"emacs\", \"coreutils\", \"gnus\", or \"tramp\". It can
410 also be an email address of a user who has applied a user tag.
411 The special email address \"me\" is used as pattern, replaced
412 with `user-mail-address'. There must be at least one such
413 entry; it is recommended to have exactly one.
414
415 :tag -- A string applied as user tag. Often, it is a
416 subproduct identification, like \"cedet\" or \"tramp\" for the
417 package \"emacs\".
418
419 If there is no :tag entry, no bug numbers will be returned but a list of
420 existing user tags for :user.
421
422 Example:
423
424 \(debbugs-get-usertag :user \"emacs\")
425
426 => (\"www\" \"solaris\" \"ls-lisp\" \"cygwin\")
427
428 \(debbugs-get-usertag :user \"emacs\" :tag \"www\" :tag \"cygwin\")
429
430 => (807 1223 5637)"
431
432 (let (user tags kw key val object result)
433 ;; Check query.
434 (while (and (consp query) (<= 2 (length query)))
435 (setq kw (pop query)
436 val (pop query))
437 (unless (and (keywordp kw) (stringp val))
438 (error "Wrong query: %s %s" kw val))
439 (setq key (substring (symbol-name kw) 1))
440 (case kw
441 ((:user)
442 ;; Value shall be one word. Extract email address, if existing.
443 (if (string-match "\\`\\S-+\\'" val)
444 (progn
445 (when (string-equal "me" val)
446 (setq val user-mail-address))
447 (when (string-match "<\\(.+\\)>" val)
448 (setq val (match-string 1 val)))
449 (pushnew val user :test #'equal))
450 (error "Wrong %s: %s" key val)))
451 ((:tag)
452 ;; Value shall be one word.
453 (if (string-match "\\`\\S-+\\'" val)
454 (pushnew val tags :test #'equal)
455 (error "Wrong %s: %s" key val)))
456 (t (error "Unknown key: %s" kw))))
457
458 (unless (null query)
459 (error "Unknown key: %s" (car query)))
460 (unless (= (length user) 1)
461 (error "There must be exactly one :user entry"))
462
463 (setq
464 object
465 (car (soap-invoke debbugs-wsdl debbugs-port "get_usertag" (car user))))
466
467 (if (null tags)
468 ;; Return the list of existing tags.
469 (mapcar (lambda (x) (symbol-name (car x))) object)
470
471 ;; Return bug numbers.
472 (dolist (elt object result)
473 (when (member (symbol-name (car elt)) tags)
474 (setq result (append (cdr elt) result)))))))
475
476 (defun debbugs-get-bug-log (bug-number)
477 "Return a list of messages related to BUG-NUMBER.
478
479 Every message is an association list with the following attributes:
480
481 `msg_num': The number of the message inside the bug log. The
482 numbers are ascending, newer messages have a higher number.
483
484 `header': The message header lines, as arrived at the bug tracker.
485
486 `body': The message body.
487
488 `attachments' A list of possible attachments, or `nil'. Not
489 implemented yet server side."
490 (car (soap-invoke debbugs-wsdl debbugs-port "get_bug_log" bug-number)))
491
492 (defun debbugs-search-est (&rest query)
493 "Return the result of a full text search according to QUERY.
494
495 QUERY is a sequence of lists of keyword-value pairs where the
496 values are strings or numbers, i.e. :KEYWORD \"VALUE\" [:KEYWORD
497 VALUE]*
498
499 Every sublist of the QUERY forms a hyperestraier condition. A
500 detailed description of hyperestraier conditions can be found at
501 URL `http://fallabs.com/hyperestraier/uguide-en.html#searchcond'.
502
503 The following conditions are possible:
504
505 \[:phrase SEARCH-PHRASE :skip NUMBER :max NUMBER\]
506
507 The string SEARCH-PHRASE forms the search on the database. It
508 contains words to be searched for, combined by operators like
509 AND, ANDNOT and OR. If there is no operator between the words,
510 AND is used by default. The phrase keyword and value can also
511 be omitted, this is useful in combination with other conditions.
512
513 :skip and :max are optional. They specify, how many hits are
514 skipped, and how many maximal hits are returned. This can be
515 used for paged results. Per default, :skip is 0 and :max is 10.
516
517 There must be exactly one such condition.
518
519 \[ATTRIBUTE VALUE+ :operation OPERATION :order ORDER\]
520
521 ATTRIBUTE is one of the following keywords:
522
523 :status -- Status of bug. Valid values are \"done\",
524 \"forwarded\" and \"open\".
525
526 :subject, :@title -- The subject of a message or the title of
527 the bug, a string.
528
529 :date, :@cdate -- The submission or modification dates of a
530 message, a number.
531
532 :submitter, :@author -- The email address of the submitter of a
533 bug or the author of a message belonging to this bug, a string.
534 The special email address \"me\" is used as pattern, replaced
535 with `user-mail-address'.
536
537 :package -- The value is the name of the package a bug belongs
538 to, like \"emacs\", \"coreutils\", \"gnus\", or \"tramp\".
539
540 :tags -- An arbitrary string the bug is annotated with.
541
542 :severity -- This is the severity of the bug. The exact set of
543 allowed values depends on the Debbugs port. Examples are
544 \"normal\", \"minor\", \"wishlist\" etc.
545
546 :operator defines the comparison operator to be applied to
547 ATTRIBUTE. For string attributes this could be \"STREQ\" \(is
548 equal to the string), \"STRNE\" \(is not equal to the string),
549 \"STRINC\" \(includes the string), \"STRBW\" \(begins with the
550 string), \"STREW\" \(ends with the string), \"STRAND\"
551 \(includes all tokens in the string), \"STROR\" \(includes at
552 least one token in the string), \"STROREQ\" \(is equal to at
553 least one token in the string) or \"STRRX\" \(matches regular
554 expressions of the string). For operators with tokens, several
555 values for ATTRIBUTE shall be used.
556
557 Numbers can be compared by the operators \"NUMEQ\" \(is equal
558 to the number), \"NUMNE\" \(is not equal to the number),
559 \"NUMGT\" \(is greater than the number), \"NUMGE\" \(is greater
560 than or equal to the number), \"NUMLT\" \(is less than the
561 number), \"NUMLE\" \(is less than or equal to the number) or
562 \"NUMBT\" \(is between the two numbers). In the last case,
563 there must be two values for ATTRIBUTE.
564
565 If an operator is leaded by \"!\", the meaning is inverted. If
566 a string operator is leaded by \"I\", the case of the value is
567 ignored.
568
569 The optional :order can be specified only in one condition. It
570 means, that ATTRIBUTE is used for sorting the results. The
571 following order operators exist: \"STRA\" \(ascending by
572 string), \"STRD\" \(descending by string), \"NUMA\" \(ascending
573 by number) or \"NUMD\" \(descending by number).
574
575 A special case is an :order, where there is no corresponding
576 attribute value and no operator. In this case, ATTRIBUTE is
577 not used for the search.
578
579 The result of the QUERY is a list of association lists with the
580 same attributes as in the conditions. Additional attributes are
581
582 `id': The bug number.
583
584 `msg_num': The number of the message inside the bug log.
585
586 `snippet': The surrounding text found by the search. For the
587 syntax of the snippet, consult the hyperestraier user guide.
588
589 Examples:
590
591 \(debbugs-search-est
592 '\(:phrase \"armstrong AND debbugs\" :skip 10 :max 2)
593 '\(:severity \"normal\" :operator \"STRINC\")
594 '\(:date :order \"NUMA\"))
595
596 => \(\(\(msg_num . 21)
597 \(date . 1229208302)
598 \(@author . \"Glenn Morris <rgm@gnu.org>\")
599 \(@title . \"Re: bug#1567: Mailing an archived bug\")
600 \(id . 1567)
601 \(severity . \"normal\")
602 \(@cdate . \"Wed, 17 Dec 2008 14:34:50 -0500\")
603 \(snippet . \"...\")
604 \(subject . \"Mailing an archived bug\")
605 \(package . \"debbugs.gnu.org\"))
606 ...)
607
608 ;; Show all messages from me between 2011-08-01 and 2011-08-31.
609 \(debbugs-search-est
610 '\(:max 20)
611 '\(:@author \"me\" :operator \"ISTRINC\")
612 `\(:date
613 ,\(floor \(float-time \(encode-time 0 0 0 1 8 2011)))
614 ,\(floor \(float-time \(encode-time 0 0 0 31 8 2011)))
615 :operator \"NUMBT\"))"
616
617 (let (args result)
618 ;; Compile search arguments.
619 (dolist (elt query)
620 (let (vec kw key val
621 phrase-cond attr-cond)
622
623 ;; Phrase is mandatory, even if empty.
624 (when (and (or (member :skip elt) (member :max elt))
625 (not (member :phrase elt)))
626 (setq vec (vector "phrase" "")))
627
628 ;; Parse condition.
629 (while (consp elt)
630 (setq kw (pop elt))
631 (unless (keywordp kw)
632 (error "Wrong keyword: %s" kw))
633 (setq key (substring (symbol-name kw) 1))
634 (cl-case kw
635 ;; Phrase condition.
636 (:phrase
637 ;; It shouldn't happen in an attribute condition.
638 (if attr-cond
639 (error "Wrong keyword: %s" kw))
640 (setq phrase-cond t val (pop elt))
641 ;; Value is a string.
642 (if (stringp val)
643 (setq vec (vconcat vec (list key val)))
644 (error "Wrong %s: %s" key val)))
645
646 ((:skip :max)
647 ;; It shouldn't happen in an attribute condition.
648 (if attr-cond
649 (error "Wrong keyword: %s" kw))
650 (setq phrase-cond t val (pop elt))
651 ;; Value is a number.
652 (if (numberp val)
653 (setq vec (vconcat vec (list key (number-to-string val))))
654 (error "Wrong %s: %s" key val)))
655
656 ;; Attribute condition.
657 ((:submitter :@author)
658 ;; It shouldn't happen in a phrase condition.
659 (if phrase-cond
660 (error "Wrong keyword: %s" kw))
661 (if (not (stringp (car elt)))
662 (setq vec (vconcat vec (list key "")))
663 ;; Value is an email address.
664 (while (and (stringp (car elt))
665 (string-match "\\`\\S-+\\'" (car elt)))
666 (when (string-equal "me" (car elt))
667 (setcar elt user-mail-address))
668 (when (string-match "<\\(.+\\)>" (car elt))
669 (setcar elt (match-string 1 (car elt))))
670 (let ((x (pop elt)))
671 (unless (member x val)
672 (setq val (append val (list x))))))
673 (setq vec
674 (vconcat vec (list key (mapconcat 'identity val " "))))))
675
676 (:status
677 ;; It shouldn't happen in a phrase condition.
678 (if phrase-cond
679 (error "Wrong keyword: %s" kw))
680 (setq attr-cond t)
681 (if (not (stringp (car elt)))
682 (setq vec (vconcat vec (list key "")))
683 ;; Possible values: "done", "forwarded" and "open"
684 (while (and (stringp (car elt))
685 (string-match
686 "\\`\\(done\\|forwarded\\|open\\)\\'" (car elt)))
687 (let ((x (pop elt)))
688 (unless (member x val)
689 (setq val (append val (list x))))))
690 (setq vec
691 (vconcat vec (list key (mapconcat 'identity val " "))))))
692
693 ((:subject :package :tags :severity :@title)
694 ;; It shouldn't happen in a phrase condition.
695 (if phrase-cond
696 (error "Wrong keyword: %s" kw))
697 (setq attr-cond t)
698 (if (not (stringp (car elt)))
699 (setq vec (vconcat vec (list key "")))
700 ;; Just a string.
701 (while (stringp (car elt))
702 (let ((x (pop elt)))
703 (unless (member x val)
704 (setq val (append val (list x))))))
705 (setq vec
706 (vconcat vec (list key (mapconcat 'identity val " "))))))
707
708 ((:date :@cdate)
709 ;; It shouldn't happen in a phrase condition.
710 (if phrase-cond
711 (error "Wrong keyword: %s" kw))
712 (setq attr-cond t)
713 (if (not (numberp (car elt)))
714 (setq vec (vconcat vec (list key "")))
715 ;; Just a number.
716 (while (numberp (car elt))
717 (let ((x (pop elt)))
718 (unless (member x val)
719 (setq val (append val (list x))))))
720 (setq vec
721 (vconcat
722 vec (list key (mapconcat 'number-to-string val " "))))))
723
724 ((:operator :order)
725 ;; It shouldn't happen in a phrase condition.
726 (if phrase-cond
727 (error "Wrong keyword: %s" kw))
728 (setq attr-cond t val (pop elt))
729 ;; Value is a number.
730 (if (stringp val)
731 (setq vec (vconcat vec (list key val)))
732 (error "Wrong %s: %s" key val)))
733
734 (t (error "Unknown key: %s" kw))))
735
736 (setq args (vconcat args (list vec)))))
737
738 (setq result
739 (car (soap-invoke debbugs-wsdl debbugs-port "search_est" args)))
740 ;; The result contains lists (key value). We transform it into
741 ;; cons cells (key . value).
742 (dolist (elt1 result result)
743 (dolist (elt2 elt1)
744 (setcdr elt2 (cadr elt2))))))
745
746 (defun debbugs-get-attribute (bug-or-message attribute)
747 "Return the value of key ATTRIBUTE.
748
749 BUG-OR-MESSAGE must be list element returned by either
750 `debbugs-get-status' or `debbugs-get-bug-log'.
751
752 Example: Return the originator of last submitted bug.
753
754 \(debbugs-get-attribute
755 \(car \(apply 'debbugs-get-status \(debbugs-newest-bugs 1))) 'originator)"
756 (cdr (assoc attribute bug-or-message)))
757
758 (defun debbugs-get-message-numbers (messages)
759 "Return the message numbers of MESSAGES.
760 MESSAGES must be the result of a `debbugs-get-bug-log' call."
761 (mapcar (lambda (x) (debbugs-get-attribute x 'msg_num)) messages))
762
763 (defun debbugs-get-message (messages message-number)
764 "Return the message MESSAGE-NUMBER of MESSAGES.
765 MESSAGES must be the result of a `debbugs-get-bug-log' call.
766
767 The returned message is a list of strings. The first element are
768 the header lines of the message, the second element is the body
769 of the message. Further elements of the list, if any, are
770 attachments of the message.
771
772 If there is no message with MESSAGE-NUMBER, the function returns `nil'.
773
774 Example: Return the first message of last submitted bug.
775
776 \(let \(\(messages \(apply 'debbugs-get-bug-log \(debbugs-newest-bugs 1))))
777 \(debbugs-get-message messages
778 \(car \(debbugs-get-message-numbers messages))))"
779 (while (and messages
780 (/= (debbugs-get-attribute (car messages) 'msg_num)
781 message-number))
782 (setq messages (cdr messages)))
783 (when messages
784 (append (list (debbugs-get-attribute (car messages) 'header)
785 (debbugs-get-attribute (car messages) 'body))
786 (debbugs-get-attribute (car messages) 'attachments))))
787
788 (defun debbugs-get-mbox (bug-number mbox-type &optional filename)
789 "Download mbox with messages of bug BUG-NUMBER from Debbugs server.
790 BUG-NUMBER is a number of bug. It must be of integer type.
791
792 MBOX-TYPE specifies a type of mbox and can be one of the
793 following symbols:
794
795 `mboxfolder': Download mbox folder.
796
797 `mboxmaint': Download maintainer's mbox.
798
799 `mboxstat', `mboxstatus': Download status mbox. The use of
800 either symbol depends on actual Debbugs server configuration.
801 For gnu.org, use the former; for debian.org - the latter.
802
803 FILENAME, if non-`nil', is the name of file to store mbox. If
804 FILENAME is `nil', the downloaded mbox is inserted into the
805 current buffer."
806 (let (url (mt "") bn)
807 (unless (setq url (plist-get
808 (cdr (assoc debbugs-port debbugs-servers))
809 :bugreport-url))
810 (error "URL of bugreport script for port %s is not specified"
811 debbugs-port))
812 (setq bn (format "bug=%s;" (number-to-string bug-number)))
813 (unless (eq mbox-type 'mboxfolder)
814 (if (memq mbox-type '(mboxmaint mboxstat mboxstatus))
815 (setq mt (concat (symbol-name mbox-type) "=yes;"))
816 (error "Unknown mbox type: %s" mbox-type)))
817 (setq url (concat url (format "?%s%smbox=yes" bn mt)))
818 (if filename
819 (url-copy-file url filename t)
820 (url-insert-file-contents url))))
821
822 (provide 'debbugs)
823
824 ;;; TODO:
825
826 ;; * SOAP interface extensions (wishlist).
827 ;; - Server-side sorting.
828 ;; - Regexp and/or wildcards search.
829 ;; - Returning message attachments.
830
831 ;;; debbugs.el ends here