]> code.delx.au - gnu-emacs-elpa/blobdiff - packages/debbugs/debbugs.el
Release debbugs 0.9.6.
[gnu-emacs-elpa] / packages / debbugs / debbugs.el
index f14528008ed709eb2f0eea52cfb72d8ee9e2feb3..ad5147444fa4f2e9e7ecdd6549e086d3b1f54727 100644 (file)
@@ -1,12 +1,12 @@
-;;; debbugs.el --- SOAP library to access debbugs servers
+;;; debbugs.el --- SOAP library to access debbugs servers  -*- lexical-binding:t -*-
 
 ;; Copyright (C) 2011-2016 Free Software Foundation, Inc.
 
 ;; Author: Michael Albinus <michael.albinus@gmx.de>
 ;; Keywords: comm, hypermedia
 ;; Package: debbugs
-;; Version: 0.9
-;; Package-Requires: ((async "1.6"))
+;; Version: 0.9.6
+;; Package-Requires: ((soap-client "3.1.1") (cl-lib "0.5"))
 
 ;; This file is not part of GNU Emacs.
 
 
 ;(setq soap-debug t message-log-max t)
 (require 'soap-client)
-(eval-when-compile (require 'cl))
-
-(declare-function soap-invoke-async "soap-client")
-(declare-function async-start "async")
-(declare-function async-get "async")
+(eval-when-compile (require 'cl-lib))
 
 (defgroup debbugs nil
   "Debbugs library"
@@ -122,29 +118,13 @@ This corresponds to the Debbugs server to be accessed, either
   "The object manipulated by `debbugs-soap-invoke-async'.")
 
 (defun debbugs-soap-invoke-async (operation-name &rest parameters)
-  "Invoke the SOAP connection asynchronously.
-If possible, it uses `soap-invoke-async' from soapclient 3.0.
-Otherwise, `async-start' from the async package is used."
-  (if (fboundp 'soap-invoke-async)
-      ;; This is soap-client 3.0.
-      (apply
-       'soap-invoke-async
-       (lambda (response &rest args)
-        (setq debbugs-soap-invoke-async-object
-              (append debbugs-soap-invoke-async-object (car response))))
-       nil
-       debbugs-wsdl debbugs-port operation-name parameters)
-    ;; Fallback with async.
-    (async-start
-     `(lambda ()
-       (load ,(locate-library "soap-client"))
-       (apply
-        'soap-invoke
-        (soap-load-wsdl
-         ,(expand-file-name
-           "Debbugs.wsdl"
-           (file-name-directory (locate-library "debbugs"))))
-        ,debbugs-port ,operation-name ',parameters)))))
+  "Invoke the SOAP connection asynchronously."
+  (apply
+   #'soap-invoke-async
+   (lambda (response &rest _args)
+     (setq debbugs-soap-invoke-async-object
+          (append debbugs-soap-invoke-async-object (car response))))
+   nil debbugs-wsdl debbugs-port operation-name parameters))
 
 (defun debbugs-get-bugs (&rest query)
   "Return a list of bug numbers which match QUERY.
@@ -201,8 +181,8 @@ Valid keywords are:
   the value of field `affects' in bug's status.  The returned bugs
   do not necessary belong to this package.
 
-  :status -- Status of bug.  Valid values are \"done\",
-  \"forwarded\" and \"open\".
+  :status -- Status of bug.  Valid values are \"pending\",
+  \"forwarded\", \"fixed\" and \"done\".
 
   :archive -- A keyword to filter for bugs which are already
   archived, or not.  Valid values are \"0\" (not archived),
@@ -228,7 +208,7 @@ patch:
       (unless (and (keywordp kw) (stringp val))
        (error "Wrong query: %s %s" kw val))
       (setq key (substring (symbol-name kw) 1))
-      (case kw
+      (cl-case kw
        ((:package :severity :tag :src :affects)
         ;; Value shall be one word.
         (if (string-match "\\`\\S-+\\'" val)
@@ -245,8 +225,8 @@ patch:
               (setq vec (vconcat vec (list key val))))
           (error "Wrong %s: %s" key val)))
        (:status
-        ;; Possible values: "done", "forwarded" and "open"
-        (if (string-match "\\`\\(done\\|forwarded\\|open\\)\\'" val)
+        ;; Possible values: "pending", "forwarded", "fixed" and "done".
+        (if (string-match "\\`\\(pending\\|forwarded\\|fixed\\|done\\)\\'" val)
             (setq vec (vconcat vec (list key val)))
           (error "Wrong %s: %s" key val)))
        (:archive
@@ -264,6 +244,27 @@ patch:
   "Return the list of bug numbers, according to AMOUNT (a number) latest bugs."
   (sort (car (soap-invoke debbugs-wsdl debbugs-port "newest_bugs" amount)) '<))
 
+(defun debbugs-convert-soap-value-to-string (string-value)
+  "If STRING-VALUE is unibyte, decode its contents as a UTF-8 string.
+If STRING-VALUE is a multibyte string, then `soap-client'
+received an xsd:string for this value, and will have decoded it
+already.
+
+If STRING-VALUE is a unibyte string, then `soap-client' received
+an xsd:base64Binary, and ran `base64-decode-string' on it to
+produce a unibyte string of bytes.
+
+For some reason, the Debbugs server code base64-encodes strings
+that contain UTF-8 characters, and returns them as
+xsd:base64Binary, instead of just returning them as xsd:string.
+Therefore, when STRING-VALUE is a unibyte string, we assume its
+bytes represent a UTF-8 string and decode them accordingly."
+  (if (stringp string-value)
+      (if (not (multibyte-string-p string-value))
+         (decode-coding-string string-value 'utf-8)
+       string-value)
+    (error "Invalid string value")))
+
 (defun debbugs-get-status (&rest bug-numbers)
   "Return a list of status entries for the bugs identified by BUG-NUMBERS.
 
@@ -281,7 +282,7 @@ Every returned entry is an association list with the following attributes:
   can be \"fixed\", \"notabug\", \"wontfix\", \"unreproducible\",
   \"moreinfo\" or \"patch\".
 
-  `pending': The string \"pending\", \"forwarded\" or \"done\".
+  `pending': The string \"pending\", \"forwarded\", \"fixed\" or \"done\".
 
   `subject': Subject/Title of the bugreport.
 
@@ -379,7 +380,7 @@ Example:
                  (debbugs-soap-invoke-async
                   "get_status"
                   (apply
-                   'vector
+                   #'vector
                    (butlast
                     bug-ids (- (length bug-ids)
                                debbugs-max-hits-per-request))))))
@@ -389,14 +390,8 @@ Example:
                                 debbugs-max-hits-per-request))))
 
        (dolist (res results)
-         (if (bufferp res)
-             ;; This is soap-client 3.0.
-             (while (buffer-live-p res)
-               (accept-process-output (get-buffer-process res) 0.1))
-           ;; Fallback with async.
-           (dolist (status (async-get res))
-             (setq debbugs-soap-invoke-async-object
-                   (append debbugs-soap-invoke-async-object status)))))))
+         (while (buffer-live-p res)
+           (accept-process-output (get-buffer-process res) 0.1)))))
 
     (append
      cached-bugs
@@ -420,7 +415,13 @@ Example:
            (setq y (assoc attribute (cdr (assoc 'value x))))
            (when (stringp (cdr y))
              (setcdr y (mapcar
-                        'string-to-number (split-string (cdr y) " " t)))))
+                        #'string-to-number (split-string (cdr y) " " t)))))
+         ;; "subject", "originator", "owner" and "summary" may be an
+         ;; xsd:base64Binary value containing a UTF-8-encoded string.
+         (dolist (attribute '(subject originator owner summary))
+           (setq y (assoc attribute (cdr (assoc 'value x))))
+           (when (stringp (cdr y))
+             (setcdr y (debbugs-convert-soap-value-to-string (cdr y)))))
          ;; "package" is a string, containing comma separated
          ;; package names.  "keywords" and "tags" are strings,
          ;; containing blank separated package names.
@@ -433,7 +434,7 @@ Example:
              (puthash
               (cdr (assoc 'key x))
               ;; Put also a time stamp.
-              (cons (cons 'cache_time (floor (float-time)))
+              (cons (cons 'cache_time (float-time))
                     (cdr (assoc 'value x)))
               debbugs-cache-data)
            ;; Don't cache.
@@ -480,7 +481,7 @@ Example:
       (unless (and (keywordp kw) (stringp val))
        (error "Wrong query: %s %s" kw val))
       (setq key (substring (symbol-name kw) 1))
-      (case kw
+      (cl-case kw
        ((:user)
         ;; Value shall be one word.  Extract email address, if existing.
         (if (string-match "\\`\\S-+\\'" val)
@@ -489,12 +490,12 @@ Example:
                 (setq val user-mail-address))
               (when (string-match "<\\(.+\\)>" val)
                 (setq val (match-string 1 val)))
-              (pushnew val user :test #'equal))
+              (cl-pushnew val user :test #'equal))
           (error "Wrong %s: %s" key val)))
        ((:tag)
         ;; Value shall be one word.
         (if (string-match "\\`\\S-+\\'" val)
-            (pushnew val tags :test #'equal)
+            (cl-pushnew val tags :test #'equal)
           (error "Wrong %s: %s" key val)))
        (t (error "Unknown key: %s" kw))))
 
@@ -564,17 +565,15 @@ The following conditions are possible:
 
   ATTRIBUTE is one of the following keywords:
 
-  :status --  Status of bug.  Valid values are \"done\",
-  \"forwarded\" and \"open\".
-
   :subject, :@title -- The subject of a message or the title of
   the bug, a string.
 
   :date, :@cdate -- The submission or modification dates of a
   message, a number.
 
-  :submitter, :@author -- The email address of the submitter of a
-  bug or the author of a message belonging to this bug, a string.
+  :@author -- The email address of the author of a message
+  belonging to this bug, a string.  It may be different than
+  the email of the person submitting the bug.
   The special email address \"me\" is used as pattern, replaced
   with `user-mail-address'.
 
@@ -660,7 +659,7 @@ Examples:
 
   (let ((phrase (assoc :phrase query))
        args result)
-    (if (and phrase (not (member :skip phrase)) (not (member :skip phrase)))
+    (if (and phrase (not (member :skip phrase)) (not (member :max phrase)))
        ;; We loop, until we have all results.
        (let ((skip 0)
              (query (delete phrase query))
@@ -668,7 +667,7 @@ Examples:
          (while skip
            (setq result1
                  (apply
-                  'debbugs-search-est
+                  #'debbugs-search-est
                   (append
                    (list
                     (append
@@ -682,6 +681,9 @@ Examples:
 
       ;; Compile search arguments.
       (dolist (elt query)
+        ;; FIXME: `vec' is used in an O(N²) way.  It should be a list instead,
+        ;; on which we push elements, and we only convert it to a vector at
+        ;; the end.
        (let (vec kw key val
                  phrase-cond attr-cond)
 
@@ -720,8 +722,9 @@ Examples:
 
              ;; Attribute condition.
              ((:submitter :@author)
-              ;; It shouldn't happen in a phrase condition.
-              (if phrase-cond
+              ;; It shouldn't happen.
+              (if (or (and (eq kw :submitter) phrase-cond)
+                      (and (eq kw :@author) attr-cond))
                   (error "Wrong keyword: %s" kw))
               (if (not (stringp (car elt)))
                   (setq vec (vconcat vec (list key "")))
@@ -736,7 +739,8 @@ Examples:
                     (unless (member x val)
                       (setq val (append val (list x))))))
                 (setq vec
-                      (vconcat vec (list key (mapconcat 'identity val " "))))))
+                      (vconcat
+                       vec (list key (mapconcat #'identity val " "))))))
 
              (:status
               ;; It shouldn't happen in a phrase condition.
@@ -745,15 +749,17 @@ Examples:
               (setq attr-cond t)
               (if (not (stringp (car elt)))
                   (setq vec (vconcat vec (list key "")))
-                ;; Possible values: "done", "forwarded" and "open"
+                ;; Possible values: "pending", "forwarded", "fixed" and "done".
                 (while  (and (stringp (car elt))
                              (string-match
-                              "\\`\\(done\\|forwarded\\|open\\)\\'" (car elt)))
+                              "\\`\\(pending\\|forwarded\\|fixed\\|done\\)\\'"
+                              (car elt)))
                   (let ((x (pop elt)))
                     (unless (member x val)
                       (setq val (append val (list x))))))
                 (setq vec
-                      (vconcat vec (list key (mapconcat 'identity val " "))))))
+                      (vconcat
+                       vec (list key (mapconcat #'identity val " "))))))
 
              ((:subject :package :tags :severity :@title)
               ;; It shouldn't happen in a phrase condition.
@@ -768,7 +774,8 @@ Examples:
                     (unless (member x val)
                       (setq val (append val (list x))))))
                 (setq vec
-                      (vconcat vec (list key (mapconcat 'identity val " "))))))
+                      (vconcat
+                       vec (list key (mapconcat #'identity val " "))))))
 
              ((:date :@cdate)
               ;; It shouldn't happen in a phrase condition.
@@ -784,7 +791,8 @@ Examples:
                       (setq val (append val (list x))))))
                 (setq vec
                       (vconcat
-                       vec (list key (mapconcat 'number-to-string val " "))))))
+                       vec
+                       (list key (mapconcat #'number-to-string val " "))))))
 
              ((:operator :order)
               ;; It shouldn't happen in a phrase condition.
@@ -817,7 +825,7 @@ BUG-OR-MESSAGE must be list element returned by either
 Example: Return the originator of last submitted bug.
 
 \(debbugs-get-attribute
-  \(car \(apply 'debbugs-get-status \(debbugs-newest-bugs 1))) 'originator)"
+  \(car \(apply #'debbugs-get-status \(debbugs-newest-bugs 1))) 'originator)"
   (cdr (assoc attribute bug-or-message)))
 
 (defun debbugs-get-message-numbers (messages)
@@ -838,7 +846,7 @@ If there is no message with MESSAGE-NUMBER, the function returns `nil'.
 
 Example: Return the first message of last submitted bug.
 
-\(let \(\(messages \(apply 'debbugs-get-bug-log \(debbugs-newest-bugs 1))))
+\(let \(\(messages \(apply #'debbugs-get-bug-log \(debbugs-newest-bugs 1))))
   \(debbugs-get-message messages
                       \(car \(debbugs-get-message-numbers messages))))"
   (while (and messages