1 ;;; debbugs.el --- SOAP library to access debbugs servers
3 ;; Copyright (C) 2011 Free Software Foundation, Inc.
5 ;; Author: Michael Albinus <michael.albinus@gmx.de>
6 ;; Keywords: comm, hypermedia
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs 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.
17 ;; GNU Emacs 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.
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/>.
27 ;; This package provides some basic functions to access a debbugs SOAP
28 ;; server (see <http://wiki.debian.org/DebbugsSoapInterface>).
30 ;; The SOAP functions "get_usertag" and "get_versions" are not
35 ;(setq soap-debug t message-log-max t)
36 (require 'soap-client)
37 (eval-when-compile (require 'cl))
43 (defcustom debbugs-servers
45 :wsdl "http://debbugs.gnu.org/cgi/soap.cgi?WSDL"
46 :bugreport-url "http://debbugs.gnu.org/cgi/bugreport.cgi")
48 :wsdl "http://bugs.debian.org/cgi-bin/soap.cgi?WSDL"
49 :bugreport-url "http://bugs.debian.org/cgi-bin/bugreport.cgi"))
50 "*List of Debbugs server specifiers.
51 Each entry is a list that contains a string identifying the port
52 name and the server parameters in keyword-value form. Allowed
55 `:wsdl' -- Location of WSDL. The value is a string with URL that
56 should return the WSDL specification of Debbugs/SOAP service.
58 `:bugreport-url' -- URL of the server script that returns mboxes
61 The list initially contains two predefined and configured Debbugs
62 servers: \"gnu.org\" and \"debian.org\"."
64 :link '(custom-manual "(debbugs)Debbugs server specifiers")
69 (string :tag "Port name")
70 (checklist :tag "Options" :greedy t
72 (const :format "" :value :wsdl)
75 (const :format "" :value :bugreport-url)
76 (string :tag "Bugreport URL")))))))
78 (defcustom debbugs-port "gnu.org"
79 "The port instance to be applied from `debbugs-wsdl'.
80 This corresponds to the Debbugs server to be accessed, either
81 \"gnu.org\", or \"debian.org\", or user defined port name."
82 ;; Maybe we should create an own group?
84 :type '(choice :tag "Debbugs server" (const "gnu.org") (const "debian.org")
85 (string :tag "user defined port name")))
87 ;; It would be nice if we could retrieve it from the debbugs server.
89 (defconst debbugs-wsdl
94 (file-name-directory load-file-name)
96 "The WSDL object to be used describing the SOAP interface.")
98 (defun debbugs-get-bugs (&rest query)
99 "Return a list of bug numbers which match QUERY.
101 QUERY is a sequence of keyword-value pairs where the values are
102 strings, i. e. :KEYWORD \"VALUE\" [:KEYWORD \"VALUE\"]*
104 The keyword-value pair is a subquery. The keywords are allowed to
105 have multiple occurrence within the query at any place. The
106 subqueries with the same keyword form the logical subquery, which
107 returns the union of bugs of every subquery it contains.
109 The result of the QUERY is an intersection of results of all
114 :package -- The value is the name of the package a bug belongs
115 to, like \"emacs\", \"coreutils\", \"gnus\", or \"tramp\".
117 :src -- This is used to retrieve bugs that belong to source
120 :severity -- This is the severity of the bug. The exact set of
121 allowed values depends on the Debbugs port. Examples are
122 \"normal\", \"minor\", \"wishlist\" etc.
124 :tag -- An arbitrary string the bug is annotated with.
125 Usually, this is used to mark the status of the bug, like
126 \"fixed\", \"moreinfo\", \"notabug\", \"patch\",
127 \"unreproducible\" or \"wontfix\". The exact set of tags
128 depends on the Debbugs port.
130 :owner -- This is used to identify bugs by the owner's email
131 address. The special email address \"me\" is used as pattern,
132 replaced with `user-mail-address'.
134 :submitter -- With this keyword it is possible to filter bugs
135 by the submitter's email address. The special email address
136 \"me\" is used as pattern, replaced with `user-mail-address'.
138 :maint -- This is used to find bugs of the packages which are
139 maintained by the person with the given email address. The
140 special email address \"me\" is used as pattern, replaced with
143 :correspondent -- This allows to find bug reports where the
144 person with the given email address has participated. The
145 special email address \"me\" is used as pattern, replaced with
148 :affects -- With this keyword it is possible to find bugs which
149 affect the package with the given name. The bugs are chosen by
150 the value of field `affects' in bug's status. The returned bugs
151 do not necessary belong to this package.
153 :status -- Status of bug. Valid values are \"done\",
154 \"forwarded\" and \"open\".
156 :archive -- A keyword to filter for bugs which are already
157 archived, or not. Valid values are \"0\" (not archived),
158 \"1\" (archived) or \"both\". If this keyword is not given in
159 the query, `:archive \"0\"' is assumed by default.
161 Example. Get all opened and forwarded release critical bugs for
162 the packages which are maintained by \"me\" and which have a
165 \(debbugs-get-bugs :maint \"me\" :tag \"patch\"
166 :severity \"critical\"
169 :status \"forwarded\"
170 :severity \"serious\"))"
172 (let (vec kw key val)
174 (while (and (consp query) (<= 2 (length query)))
177 (unless (and (keywordp kw) (stringp val))
178 (error "Wrong query: %s %s" kw val))
179 (setq key (substring (symbol-name kw) 1))
181 ((:package :severity :tag :src :affects)
182 ;; Value shall be one word.
183 (if (string-match "\\`\\S-+\\'" val)
184 (setq vec (vconcat vec (list key val)))
185 (error "Wrong %s: %s" key val)))
186 ((:owner :submitter :maint :correspondent)
187 ;; Value is an email address.
188 (if (string-match "\\`\\S-+\\'" val)
190 (when (string-equal "me" val)
191 (setq val user-mail-address))
192 (when (string-match "<\\(.+\\)>" val)
193 (setq val (match-string 1 val)))
194 (setq vec (vconcat vec (list key val))))
195 (error "Wrong %s: %s" key val)))
197 ;; Possible values: "done", "forwarded" and "open"
198 (if (string-match "\\`\\(done\\|forwarded\\|open\\)\\'" val)
199 (setq vec (vconcat vec (list key val)))
200 (error "Wrong %s: %s" key val)))
202 ;; Value is `0' or `1' or `both'.
203 (if (string-match "\\`\\(0\\|1\\|both\\)\\'" val)
204 (setq vec (vconcat vec (list key val)))
205 (error "Wrong %s: %s" key val)))
206 (t (error "Unknown key: %s" kw))))
209 (error "Unknown key: %s" (car query)))
210 (sort (car (soap-invoke debbugs-wsdl debbugs-port "get_bugs" vec)) '<)))
212 (defun debbugs-newest-bugs (amount)
213 "Return the list of bug numbers, according to AMOUNT (a number) latest bugs."
214 (sort (car (soap-invoke debbugs-wsdl debbugs-port "newest_bugs" amount)) '<))
216 (defun debbugs-get-status (&rest bug-numbers)
217 "Return a list of status entries for the bugs identified by BUG-NUMBERS.
219 Every returned entry is an association list with the following attributes:
221 `bug_num': The bug number.
223 `package': A list of package names the bug belongs to.
225 `severity': The severity of the bug report. This can be
226 \"important\", \"grave\", \"normal\", \"minor\" or \"wishlist\".
228 `tags': The status of the bug report, a list of strings. This
229 can be \"fixed\", \"notabug\", \"wontfix\", \"unreproducible\",
230 \"moreinfo\" or \"patch\".
232 `pending': The string \"pending\", \"forwarded\" or \"done\".
234 `subject': Subject/Title of the bugreport.
236 `originator': Submitter of the bugreport.
238 `mergedwith': A list of bug numbers this bug was merged with.
240 `source': Source package name of the bug report.
242 `date': Date of bug creation.
244 `log_modified', `last_modified': Date of last update.
246 `found_date', `fixed_date': Date of bug report / bug fix
249 `done': The email address of the worker who has closed the bug (if done).
251 `archived': `t' if the bug is archived, `nil' otherwise.
253 `unarchived': The date the bug has been unarchived, if ever.
255 `found_versions', `fixed_versions': List of version strings.
257 `forwarded': A URL or an email address.
259 `blocks': A list of bug numbers this bug blocks.
261 `blockedby': A list of bug numbers this bug is blocked by.
263 `msgid': The message id of the initial bug report.
265 `owner': Who is responsible for fixing.
267 `location': Always the string \"db-h\" or \"archive\".
269 `affects': A list of package names.
271 `summary': Arbitrary text.
275 \(debbugs-get-status 10)
277 => ;; Attributes with empty values are not show
279 \(source . \"unknown\")
280 \(date . 1203606305.0)
281 \(msgid . \"<87zltuz7eh.fsf@freemail.hu>\")
282 \(severity . \"wishlist\")
283 \(owner . \"Magnus Henoch <mange@freemail.hu>\")
284 \(log_modified . 1261079402.0)
285 \(location . \"db-h\")
286 \(subject . \"url-gw should support HTTP CONNECT proxies\")
287 \(originator . \"Magnus Henoch <mange@freemail.hu>\")
288 \(last_modified . 1271200046.0)
289 \(pending . \"pending\")
290 \(package \"emacs\")))"
294 debbugs-wsdl debbugs-port "get_status"
295 (apply 'vector bug-numbers)))))
299 ;; "archived" is the number 1 or 0.
300 (setq y (assoc 'archived (cdr (assoc 'value x))))
301 (setcdr y (= (cdr y) 1))
302 ;; "found_versions" and "fixed_versions" are lists,
303 ;; containing strings or numbers.
304 (dolist (attribute '(found_versions fixed_versions))
305 (setq y (assoc attribute (cdr (assoc 'value x))))
307 (lambda (z) (if (numberp z) (number-to-string z) z))
309 ;; "mergedwith" is a string, containing blank separated bug numbers.
310 (setq y (assoc 'mergedwith (cdr (assoc 'value x))))
311 (when (stringp (cdr y))
312 (setcdr y (mapcar 'string-to-number (split-string (cdr y) " " t))))
313 ;; "package" is a string, containing comma separated package names.
314 ;; "keywords" and "tags" are strings, containing blank
315 ;; separated package names.
316 (dolist (attribute '(package keywords tags))
317 (setq y (assoc attribute (cdr (assoc 'value x))))
318 (when (stringp (cdr y))
319 (setcdr y (split-string (cdr y) ",\\| " t))))
320 (cdr (assoc 'value x))))
323 (defun debbugs-get-bug-log (bug-number)
324 "Return a list of messages related to BUG-NUMBER.
326 Every message is an association list with the following attributes:
328 `msg_num': The number of the message inside the bug log. The
329 numbers are ascending, newer messages have a higher number.
331 `header': The message header lines, as arrived at the bug tracker.
333 `body': The message body.
335 `attachments' A list of possible attachments, or `nil'. Not
336 implemented yet server side."
337 (car (soap-invoke debbugs-wsdl debbugs-port "get_bug_log" bug-number)))
339 (defun debbugs-get-attribute (bug-or-message attribute)
340 "Return the value of key ATTRIBUTE.
342 BUG-OR-MESSAGE must be list element returned by either
343 `debbugs-get-status' or `debbugs-get-bug-log'.
345 Example: Return the originator of last submitted bug.
347 \(debbugs-get-attribute
348 \(car \(apply 'debbugs-get-status \(debbugs-newest-bugs 1))) 'originator)"
349 (cdr (assoc attribute bug-or-message)))
351 (defun debbugs-get-message-numbers (messages)
352 "Return the message numbers of MESSAGES.
353 MESSAGES must be the result of a `debbugs-get-bug-log' call."
354 (mapcar (lambda (x) (debbugs-get-attribute x 'msg_num)) messages))
356 (defun debbugs-get-message (messages message-number)
357 "Return the message MESSAGE-NUMBER of MESSAGES.
358 MESSAGES must be the result of a `debbugs-get-bug-log' call.
360 The returned message is a list of strings. The first element are
361 the header lines of the message, the second element is the body
362 of the message. Further elements of the list, if any, are
363 attachments of the message.
365 If there is no message with MESSAGE-NUMBER, the function returns `nil'.
367 Example: Return the first message of last submitted bug.
369 \(let \(\(messages \(apply 'debbugs-get-bug-log \(debbugs-newest-bugs 1))))
370 \(debbugs-get-message messages
371 \(car \(debbugs-get-message-numbers messages))))"
373 (/= (debbugs-get-attribute (car messages) 'msg_num)
375 (setq messages (cdr messages)))
377 (append (list (debbugs-get-attribute (car messages) 'header)
378 (debbugs-get-attribute (car messages) 'body))
379 (debbugs-get-attribute (car messages) 'attachments))))
381 (defun debbugs-get-mbox (bug-number mbox-type &optional filename)
382 "Download mbox with messages of bug BUG-NUMBER from Debbugs server.
383 BUG-NUMBER is a number of bug. It must be of integer type.
385 MBOX-TYPE specifies a type of mbox and can be one of the
388 `mboxfolder': Download mbox folder.
390 `mboxmaint': Download maintainer's mbox.
392 `mboxstat', `mboxstatus': Download status mbox. The use of
393 either symbol depends on actual Debbugs server
394 configuration. For gnu.org, use the former; for debian.org -
397 FILENAME, if non-nil, is the name of file to store mbox. If
398 FILENAME is nil, the downloaded mbox is inserted into the current
400 (let (url (mt "") bn)
401 (unless (setq url (plist-get
402 (cdr (assoc debbugs-port debbugs-servers))
404 (error "URL of bugreport script for port %s is not specified"
406 (setq bn (format "bug=%s;" (number-to-string bug-number)))
407 (unless (eq mbox-type 'mboxfolder)
408 (if (memq mbox-type '(mboxmaint mboxstat mboxstatus))
409 (setq mt (concat (symbol-name mbox-type) "=yes;"))
410 (error "Unknown mbox type: %s" mbox-type)))
411 (setq url (concat url (format "?%s%smbox=yes" bn mt)))
413 (url-copy-file url filename t)
414 (url-insert-file-contents url))))
420 ;; * SOAP interface extensions (wishlist).
421 ;; - Server-side sorting.
422 ;; - Regexp and/or wildcards search.
423 ;; - Fulltext search.
424 ;; - Returning message attachments.
426 ;;; debbugs.el ends here