Synch with Oort Gnus.
[elisp/gnus.git-] / lisp / gnus-util.el
index 8c46c94..93d2c57 100644 (file)
@@ -1,5 +1,5 @@
 ;;; gnus-util.el --- utility functions for Semi-gnus
 ;;; gnus-util.el --- utility functions for Semi-gnus
-;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001
+;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002
 ;;        Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;;        Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 (require 'custom)
 (require 'nnheader)
 (require 'time-date)
 (require 'custom)
 (require 'nnheader)
 (require 'time-date)
+(require 'netrc)
 
 (eval-and-compile
   (autoload 'message-fetch-field "message")
 
 (eval-and-compile
   (autoload 'message-fetch-field "message")
+  (autoload 'gnus-get-buffer-window "gnus-win")
   (autoload 'rmail-insert-rmail-file-header "rmail")
   (autoload 'rmail-count-new-messages "rmail")
   (autoload 'rmail-show-message "rmail"))
 
   (autoload 'rmail-insert-rmail-file-header "rmail")
   (autoload 'rmail-count-new-messages "rmail")
   (autoload 'rmail-show-message "rmail"))
 
+(eval-and-compile
+  (cond
+   ((fboundp 'replace-in-string)
+    (defalias 'gnus-replace-in-string 'replace-in-string))
+   ((fboundp 'replace-regexp-in-string)
+    (defun gnus-replace-in-string  (string regexp newtext &optional literal)
+      (replace-regexp-in-string regexp newtext string nil literal)))
+   (t
+    (defun gnus-replace-in-string (string regexp newtext &optional literal)
+      (let ((start 0) tail)
+       (while (string-match regexp string start)
+         (setq tail (- (length string) (match-end 0)))
+         (setq string (replace-match newtext nil literal string))
+         (setq start (- (length string) tail))))
+      string))))
+
+;;; bring in the netrc functions as aliases
+(defalias 'gnus-netrc-get 'netrc-get)
+(defalias 'gnus-netrc-machine 'netrc-machine)
+(defalias 'gnus-parse-netrc 'netrc-parse)
+
 (defun gnus-boundp (variable)
   "Return non-nil if VARIABLE is bound and non-nil."
   (and (boundp variable)
 (defun gnus-boundp (variable)
   "Return non-nil if VARIABLE is bound and non-nil."
   (and (boundp variable)
 (defmacro gnus-eval-in-buffer-window (buffer &rest forms)
   "Pop to BUFFER, evaluate FORMS, and then return to the original window."
   (let ((tempvar (make-symbol "GnusStartBufferWindow"))
 (defmacro gnus-eval-in-buffer-window (buffer &rest forms)
   "Pop to BUFFER, evaluate FORMS, and then return to the original window."
   (let ((tempvar (make-symbol "GnusStartBufferWindow"))
-        (w (make-symbol "w"))
-        (buf (make-symbol "buf"))
+       (w (make-symbol "w"))
+       (buf (make-symbol "buf"))
        (frame (make-symbol "frame")))
     `(let* ((,tempvar (selected-window))
        (frame (make-symbol "frame")))
     `(let* ((,tempvar (selected-window))
-            (,buf ,buffer)
-            (,w (get-buffer-window ,buf 'visible))
+           (,buf ,buffer)
+           (,w (gnus-get-buffer-window ,buf 'visible))
            ,frame)
        (unwind-protect
            ,frame)
        (unwind-protect
-           (progn
-             (if ,w
-                 (progn
-                   (select-window ,w)
-                   (set-buffer (window-buffer ,w)))
-               (pop-to-buffer ,buf))
-             ,@forms)
+          (progn
+            (if ,w
+                (progn
+                  (select-window ,w)
+                  (set-buffer (window-buffer ,w)))
+              (pop-to-buffer ,buf))
+            ,@forms)
         (setq ,frame (selected-frame))
         (setq ,frame (selected-frame))
-         (select-window ,tempvar)
+        (select-window ,tempvar)
         (select-frame ,frame)))))
 
 (put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
         (select-frame ,frame)))))
 
 (put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
         (set symbol nil))
      symbol))
 
         (set symbol nil))
      symbol))
 
-;; Avoid byte-compile warning.
-;; In Mule, this function will be redefined to `truncate-string',
-;; which takes 3 or 4 args.
-(defun gnus-truncate-string (str width &rest ignore)
-  (substring str 0 width))
-
 ;; Added by Geoffrey T. Dairiki <dairiki@u.washington.edu>.  A safe way
 ;; to limit the length of a string.  This function is necessary since
 ;; `(substr "abc" 0 30)' pukes with "Args out of range".
 ;; Added by Geoffrey T. Dairiki <dairiki@u.washington.edu>.  A safe way
 ;; to limit the length of a string.  This function is necessary since
 ;; `(substr "abc" 0 30)' pukes with "Args out of range".
         (string-match (concat "[ \t]*<" (regexp-quote address) ">") from)
         (and (setq name (substring from 0 (match-beginning 0)))
              ;; Strip any quotes from the name.
         (string-match (concat "[ \t]*<" (regexp-quote address) ">") from)
         (and (setq name (substring from 0 (match-beginning 0)))
              ;; Strip any quotes from the name.
-             (string-match "\".*\"" name)
+             (string-match "^\".*\"$" name)
              (setq name (substring name 1 (1- (match-end 0))))))
     ;; If not, then "address (name)" is used.
     (or name
              (setq name (substring name 1 (1- (match-end 0))))))
     ;; If not, then "address (name)" is used.
     (or name
 
 (defun gnus-goto-colon ()
   (beginning-of-line)
 
 (defun gnus-goto-colon ()
   (beginning-of-line)
-  (search-forward ":" (gnus-point-at-eol) t))
+  (let ((eol (gnus-point-at-eol)))
+    (goto-char (or (text-property-any (point) eol 'gnus-position t)
+                  (search-forward ":" eol t)
+                  (point)))))
+
+(defun gnus-decode-newsgroups (newsgroups group &optional method)
+  (let ((method (or method (gnus-find-method-for-group group))))
+    (mapconcat (lambda (group)
+                (gnus-group-name-decode group (gnus-group-name-charset
+                                               method group)))
+              (message-tokenize-header newsgroups)
+              ",")))
 
 (defun gnus-remove-text-with-property (prop)
   "Delete all text in the current buffer with text property PROP."
 
 (defun gnus-remove-text-with-property (prop)
   "Delete all text in the current buffer with text property PROP."
          (define-key keymap key (pop plist))
        (pop plist)))))
 
          (define-key keymap key (pop plist))
        (pop plist)))))
 
-(defun gnus-completing-read (default prompt &rest args)
+(defun gnus-completing-read-with-default (default prompt &rest args)
   ;; Like `completing-read', except that DEFAULT is the default argument.
   (let* ((prompt (if default
                     (concat prompt " (default " default ") ")
   ;; Like `completing-read', except that DEFAULT is the default argument.
   (let* ((prompt (if default
                     (concat prompt " (default " default ") ")
       (yes-or-no-p prompt)
     (message "")))
 
       (yes-or-no-p prompt)
     (message "")))
 
+;; By Frank Schmitt <ich@Frank-Schmitt.net>. Allows to have
+;; age-depending date representations. (e.g. just the time if it's
+;; from today, the day of the week if it's within the last 7 days and
+;; the full date if it's older)
+(defun gnus-seconds-today ()
+  "Returns the number of seconds passed today"
+  (let ((now (decode-time (current-time))))
+    (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600))))
+
+(defun gnus-seconds-month ()
+  "Returns the number of seconds passed this month"
+  (let ((now (decode-time (current-time))))
+    (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600)
+       (* (- (car (nthcdr 3 now)) 1) 3600 24))))
+
+(defun gnus-seconds-year ()
+  "Returns the number of seconds passed this year"
+  (let ((now (decode-time (current-time)))
+       (days (format-time-string "%j" (current-time))))
+    (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600)
+       (* (- (string-to-number days) 1) 3600 24))))
+
+(defvar gnus-user-date-format-alist
+  '(((gnus-seconds-today) . "%k:%M")
+    (604800 . "%a %k:%M")                   ;;that's one week
+    ((gnus-seconds-month) . "%a %d")
+    ((gnus-seconds-year) . "%b %d")
+    (t . "%b %d '%y"))                      ;;this one is used when no
+                                           ;;other does match
+  "Alist of time in seconds and format specification used to display dates not older.
+The first element must be a number or a function returning a
+number. The second element is a format-specification as described in
+the documentation for format-time-string.  The list must be ordered
+smallest number up. When there is an element, which is not a number,
+the corresponding format-specification will be used, disregarding any
+following elements.  You can use the functions gnus-seconds-today,
+gnus-seconds-month, gnus-seconds-year which will return the number of
+seconds which passed today/this month/this year.")
+
+(defun gnus-user-date (messy-date)
+  "Format the messy-date acording to gnus-user-date-format-alist.
+Returns \"  ?  \" if there's bad input or if an other error occurs.
+Input should look like this: \"Sun, 14 Oct 2001 13:34:39 +0200\"."
+  (condition-case ()
+      (let* ((messy-date (safe-date-to-time messy-date))
+            (now (current-time))
+            ;;If we don't find something suitable we'll use this one
+            (my-format "%b %m '%y")
+            (high (lsh (- (car now) (car messy-date)) 16)))
+       (if (and (> high -1) (= (logand high 65535) 0))
+           ;;overflow and bad input
+           (let* ((difference (+ high (- (car (cdr now))
+                                         (car (cdr messy-date)))))
+                  (templist gnus-user-date-format-alist)
+                  (top (eval (caar templist))))
+             (while (if (numberp top) (< top difference) (not top))
+               (progn
+                 (setq templist (cdr templist))
+                 (setq top (eval (caar templist)))))
+             (if (stringp (cdr (car templist)))
+                 (setq my-format (cdr (car templist))))))
+       (format-time-string (eval my-format) messy-date))
+    (error "  ?   ")))
+;;end of Frank's code
+
 (defun gnus-dd-mmm (messy-date)
   "Return a string like DD-MMM from a big messy string."
   (condition-case ()
 (defun gnus-dd-mmm (messy-date)
   "Return a string like DD-MMM from a big messy string."
   (condition-case ()
@@ -352,13 +445,7 @@ Cache the result as a text property stored in DATE."
 
 (defun gnus-mode-string-quote (string)
   "Quote all \"%\"'s in STRING."
 
 (defun gnus-mode-string-quote (string)
   "Quote all \"%\"'s in STRING."
-  (save-excursion
-    (gnus-set-work-buffer)
-    (insert string)
-    (goto-char (point-min))
-    (while (search-forward "%" nil t)
-      (insert "%"))
-    (buffer-string)))
+  (gnus-replace-in-string string "%" "%%"))
 
 ;; Make a hash table (default and minimum size is 256).
 ;; Optional argument HASHSIZE specifies the table size.
 
 ;; Make a hash table (default and minimum size is 256).
 ;; Optional argument HASHSIZE specifies the table size.
@@ -414,7 +501,7 @@ jabbering all the time."
   "Return a list of Message-IDs in REFERENCES."
   (let ((beg 0)
        ids)
   "Return a list of Message-IDs in REFERENCES."
   (let ((beg 0)
        ids)
-    (while (string-match "<[^> \t]+>" references beg)
+    (while (string-match "<[^<]+[^< \t]" references beg)
       (push (substring references (match-beginning 0) (setq beg (match-end 0)))
            ids))
     (nreverse ids)))
       (push (substring references (match-beginning 0) (setq beg (match-end 0)))
            ids))
     (nreverse ids)))
@@ -422,13 +509,17 @@ jabbering all the time."
 (defsubst gnus-parent-id (references &optional n)
   "Return the last Message-ID in REFERENCES.
 If N, return the Nth ancestor instead."
 (defsubst gnus-parent-id (references &optional n)
   "Return the last Message-ID in REFERENCES.
 If N, return the Nth ancestor instead."
-  (when references
-    (let ((ids (inline (gnus-split-references references))))
-      (while (nthcdr (or n 1) ids)
-       (setq ids (cdr ids)))
-      (car ids))))
-
-(defsubst gnus-buffer-live-p (buffer)
+  (when (and references
+            (not (zerop (length references))))
+    (if n
+       (let ((ids (inline (gnus-split-references references))))
+         (while (nthcdr n ids)
+           (setq ids (cdr ids)))
+         (car ids))
+      (when (string-match "\\(<[^<]+>\\)[ \t]*\\'" references)
+       (match-string 1 references)))))
+
+(defun gnus-buffer-live-p (buffer)
   "Say whether BUFFER is alive or not."
   (and buffer
        (get-buffer buffer)
   "Say whether BUFFER is alive or not."
   (and buffer
        (get-buffer buffer)
@@ -437,9 +528,9 @@ If N, return the Nth ancestor instead."
 (defun gnus-horizontal-recenter ()
   "Recenter the current buffer horizontally."
   (if (< (current-column) (/ (window-width) 2))
 (defun gnus-horizontal-recenter ()
   "Recenter the current buffer horizontally."
   (if (< (current-column) (/ (window-width) 2))
-      (set-window-hscroll (get-buffer-window (current-buffer) t) 0)
+      (set-window-hscroll (gnus-get-buffer-window (current-buffer) t) 0)
     (let* ((orig (point))
     (let* ((orig (point))
-          (end (window-end (get-buffer-window (current-buffer) t)))
+          (end (window-end (gnus-get-buffer-window (current-buffer) t)))
           (max 0))
       (when end
        ;; Find the longest line currently displayed in the window.
           (max 0))
       (when end
        ;; Find the longest line currently displayed in the window.
@@ -453,10 +544,10 @@ If N, return the Nth ancestor instead."
        ;; Scroll horizontally to center (sort of) the point.
        (if (> max (window-width))
            (set-window-hscroll
        ;; Scroll horizontally to center (sort of) the point.
        (if (> max (window-width))
            (set-window-hscroll
-            (get-buffer-window (current-buffer) t)
+            (gnus-get-buffer-window (current-buffer) t)
             (min (- (current-column) (/ (window-width) 3))
                  (+ 2 (- max (window-width)))))
             (min (- (current-column) (/ (window-width) 3))
                  (+ 2 (- max (window-width)))))
-         (set-window-hscroll (get-buffer-window (current-buffer) t) 0))
+         (set-window-hscroll (gnus-get-buffer-window (current-buffer) t) 0))
        max))))
 
 (defun gnus-read-event-char ()
        max))))
 
 (defun gnus-read-event-char ()
@@ -509,8 +600,9 @@ If N, return the Nth ancestor instead."
    ;; A list of functions.
    ((or (cdr funs)
        (listp (car funs)))
    ;; A list of functions.
    ((or (cdr funs)
        (listp (car funs)))
-    `(lambda (t1 t2)
-       ,(gnus-make-sort-function-1 (reverse funs))))
+    (gnus-byte-compile
+     `(lambda (t1 t2)
+       ,(gnus-make-sort-function-1 (reverse funs)))))
    ;; A list containing just one function.
    (t
     (car funs))))
    ;; A list containing just one function.
    (t
     (car funs))))
@@ -614,6 +706,19 @@ Bind `print-quoted' and `print-readably' to t while printing."
          (setq beg (point)))
        (gnus-put-text-property beg (point) prop val)))))
 
          (setq beg (point)))
        (gnus-put-text-property beg (point) prop val)))))
 
+(defsubst gnus-put-overlay-excluding-newlines (beg end prop val)
+  "The same as `put-text-property', but don't put this prop on any newlines in the region."
+  (save-match-data
+    (save-excursion
+      (save-restriction
+       (goto-char beg)
+       (while (re-search-forward gnus-emphasize-whitespace-regexp end 'move)
+         (gnus-overlay-put
+          (gnus-make-overlay beg (match-beginning 0))
+          prop val)
+         (setq beg (point)))
+       (gnus-overlay-put (gnus-make-overlay beg (point)) prop val)))))
+
 (defun gnus-put-text-property-excluding-characters-with-faces (beg end
                                                                   prop val)
   "The same as `put-text-property', but don't put props on characters with the `gnus-face' property."
 (defun gnus-put-text-property-excluding-characters-with-faces (beg end
                                                                   prop val)
   "The same as `put-text-property', but don't put props on characters with the `gnus-face' property."
@@ -622,9 +727,10 @@ Bind `print-quoted' and `print-readably' to t while printing."
       (when (get-text-property b 'gnus-face)
        (setq b (next-single-property-change b 'gnus-face nil end)))
       (when (/= b end)
       (when (get-text-property b 'gnus-face)
        (setq b (next-single-property-change b 'gnus-face nil end)))
       (when (/= b end)
-       (gnus-put-text-property
-        b (setq b (next-single-property-change b 'gnus-face nil end))
-        prop val)))))
+       (inline
+         (gnus-put-text-property
+          b (setq b (next-single-property-change b 'gnus-face nil end))
+          prop val))))))
 
 ;;; Protected and atomic operations.  dmoore@ucsd.edu 21.11.1996
 ;;; The primary idea here is to try to protect internal datastructures
 
 ;;; Protected and atomic operations.  dmoore@ucsd.edu 21.11.1996
 ;;; The primary idea here is to try to protect internal datastructures
@@ -734,7 +840,10 @@ with potentially long computations."
       ;; Decide whether to append to a file or to an Emacs buffer.
       (let ((outbuf (get-file-buffer filename)))
        (if (not outbuf)
       ;; Decide whether to append to a file or to an Emacs buffer.
       (let ((outbuf (get-file-buffer filename)))
        (if (not outbuf)
-           (write-region-as-binary (point-min) (point-max) filename 'append)
+           (let ((file-name-coding-system nnmail-pathname-coding-system)
+                 (pathname-coding-system nnmail-pathname-coding-system))
+             (write-region-as-binary (point-min) (point-max)
+                                     filename 'append))
          ;; File has been visited, in buffer OUTBUF.
          (set-buffer outbuf)
          (let ((buffer-read-only nil)
          ;; File has been visited, in buffer OUTBUF.
          (set-buffer outbuf)
          (let ((buffer-read-only nil)
@@ -748,10 +857,10 @@ with potentially long computations."
            (when msg
              (goto-char (point-min))
              (widen)
            (when msg
              (goto-char (point-min))
              (widen)
-             (search-backward "\n\^_")
-             (narrow-to-region (point) (point-max))
-             (rmail-count-new-messages t)
-             (when (rmail-summary-exists)
+             (search-backward "\n\^_")
+             (narrow-to-region (point) (point-max))
+             (rmail-count-new-messages t)
+             (when (rmail-summary-exists)
                (rmail-select-summary
                 (rmail-update-summary)))
              (rmail-count-new-messages t)
                (rmail-select-summary
                 (rmail-update-summary)))
              (rmail-count-new-messages t)
@@ -803,8 +912,10 @@ with potentially long computations."
                    (insert "\n"))
                  (insert "\n"))
                (goto-char (point-max))
                    (insert "\n"))
                  (insert "\n"))
                (goto-char (point-max))
-               (write-region-as-binary (point-min) (point-max)
-                                       filename 'append)))
+               (let ((file-name-coding-system nnmail-pathname-coding-system)
+                     (pathname-coding-system nnmail-pathname-coding-system))
+                 (write-region-as-binary (point-min) (point-max)
+                                         filename 'append))))
          ;; File has been visited, in buffer OUTBUF.
          (set-buffer outbuf)
          (let ((buffer-read-only nil))
          ;; File has been visited, in buffer OUTBUF.
          (set-buffer outbuf)
          (let ((buffer-read-only nil))
@@ -841,93 +952,6 @@ ARG is passed to the first function."
        (apply 'run-hooks funcs)
       (set-buffer buf))))
 
        (apply 'run-hooks funcs)
       (set-buffer buf))))
 
-;;;
-;;; .netrc and .authinforc parsing
-;;;
-
-(defun gnus-parse-netrc (file)
-  "Parse FILE and return an list of all entries in the file."
-  (when (file-exists-p file)
-    (with-temp-buffer
-      (let ((tokens '("machine" "default" "login"
-                     "password" "account" "macdef" "force"
-                     "port"))
-           alist elem result pair)
-       (insert-file-contents file)
-       (goto-char (point-min))
-       ;; Go through the file, line by line.
-       (while (not (eobp))
-         (narrow-to-region (point) (gnus-point-at-eol))
-         ;; For each line, get the tokens and values.
-         (while (not (eobp))
-           (skip-chars-forward "\t ")
-           ;; Skip lines that begin with a "#".
-           (if (eq (char-after) ?#)
-               (goto-char (point-max))
-             (unless (eobp)
-               (setq elem
-                     (if (= (following-char) ?\")
-                         (read (current-buffer))
-                       (buffer-substring
-                        (point) (progn (skip-chars-forward "^\t ")
-                                       (point)))))
-               (cond
-                ((equal elem "macdef")
-                 ;; We skip past the macro definition.
-                 (widen)
-                 (while (and (zerop (forward-line 1))
-                             (looking-at "$")))
-                 (narrow-to-region (point) (point)))
-                ((member elem tokens)
-                 ;; Tokens that don't have a following value are ignored,
-                 ;; except "default".
-                 (when (and pair (or (cdr pair)
-                                     (equal (car pair) "default")))
-                   (push pair alist))
-                 (setq pair (list elem)))
-                (t
-                 ;; Values that haven't got a preceding token are ignored.
-                 (when pair
-                   (setcdr pair elem)
-                   (push pair alist)
-                   (setq pair nil)))))))
-         (when alist
-           (push (nreverse alist) result))
-         (setq alist nil
-               pair nil)
-         (widen)
-         (forward-line 1))
-       (nreverse result)))))
-
-(defun gnus-netrc-machine (list machine &optional port defaultport)
-  "Return the netrc values from LIST for MACHINE or for the default entry.
-If PORT specified, only return entries with matching port tokens.
-Entries without port tokens default to DEFAULTPORT."
-  (let ((rest list)
-       result)
-    (while list
-      (when (equal (cdr (assoc "machine" (car list))) machine)
-       (push (car list) result))
-      (pop list))
-    (unless result
-      ;; No machine name matches, so we look for default entries.
-      (while rest
-       (when (assoc "default" (car rest))
-         (push (car rest) result))
-       (pop rest)))
-    (when result
-      (setq result (nreverse result))
-      (while (and result
-                 (not (equal (or port defaultport "nntp")
-                             (or (gnus-netrc-get (car result) "port")
-                                 defaultport "nntp"))))
-       (pop result))
-      (car result))))
-
-(defun gnus-netrc-get (alist type)
-  "Return the value of token TYPE from ALIST."
-  (cdr (assoc type alist)))
-
 ;;; Various
 
 (defvar gnus-group-buffer)             ; Compiler directive
 ;;; Various
 
 (defvar gnus-group-buffer)             ; Compiler directive
@@ -963,7 +987,7 @@ Entries without port tokens default to DEFAULTPORT."
 Return the modified alist."
     (let (entry)
       (while (setq entry (assq key alist))
 Return the modified alist."
     (let (entry)
       (while (setq entry (assq key alist))
-        (setq alist (delq entry alist)))
+       (setq alist (delq entry alist)))
       alist)))
 
 (defmacro gnus-pull (key alist &optional assoc-p)
       alist)))
 
 (defmacro gnus-pull (key alist &optional assoc-p)
@@ -981,7 +1005,7 @@ Return the modified alist."
 
 (defun gnus-set-window-start (&optional point)
   "Set the window start to POINT, or (point) if nil."
 
 (defun gnus-set-window-start (&optional point)
   "Set the window start to POINT, or (point) if nil."
-  (let ((win (get-buffer-window (current-buffer) t)))
+  (let ((win (gnus-get-buffer-window (current-buffer) t)))
     (when win
       (set-window-start win (or point (point))))))
 
     (when win
       (set-window-start win (or point (point))))))
 
@@ -1073,6 +1097,220 @@ Return the modified alist."
        (or (string-equal x y)
           (string-equal (downcase x) (downcase y)))))
 
        (or (string-equal x y)
           (string-equal (downcase x) (downcase y)))))
 
+(defcustom gnus-use-byte-compile t
+  "If non-nil, byte-compile crucial run-time codes.
+Setting it to `nil' has no effect after first time running
+`gnus-byte-compile'."
+  :type 'boolean
+  :version "21.1"
+  :group 'gnus-various)
+
+(defun gnus-byte-compile (form)
+  "Byte-compile FORM if `gnus-use-byte-compile' is non-nil."
+  (if gnus-use-byte-compile
+      (progn
+       (condition-case nil
+           ;; Work around a bug in XEmacs 21.4
+           (require 'byte-optimize)
+         (error))
+       (require 'bytecomp)
+       (defalias 'gnus-byte-compile 'byte-compile)
+       (byte-compile form))
+    form))
+
+(defun gnus-remassoc (key alist)
+  "Delete by side effect any elements of LIST whose car is `equal' to KEY.
+The modified LIST is returned.  If the first member
+of LIST has a car that is `equal' to KEY, there is no way to remove it
+by side effect; therefore, write `(setq foo (remassoc key foo))' to be
+sure of changing the value of `foo'."
+  (when alist
+    (if (equal key (caar alist))
+       (cdr alist)
+      (setcdr alist (gnus-remassoc key (cdr alist)))
+      alist)))
+
+(defun gnus-update-alist-soft (key value alist)
+  (if value
+      (cons (cons key value) (gnus-remassoc key alist))
+    (gnus-remassoc key alist)))
+
+(defun gnus-create-info-command (node)
+  "Create a command that will go to info NODE."
+  `(lambda ()
+     (interactive)
+     ,(concat "Enter the info system at node " node)
+     (Info-goto-node ,node)
+     (setq gnus-info-buffer (current-buffer))
+     (gnus-configure-windows 'info)))
+
+(defun gnus-not-ignore (&rest args)
+  t)
+
+(defvar gnus-directory-sep-char-regexp "/"
+  "The regexp of directory separator character.
+If you find some problem with the directory separator character, try
+\"[/\\\\\]\" for some systems.")
+
+(defun gnus-url-unhex (x)
+  (if (> x ?9)
+      (if (>= x ?a)
+         (+ 10 (- x ?a))
+       (+ 10 (- x ?A)))
+    (- x ?0)))
+
+(defun gnus-url-unhex-string (str &optional allow-newlines)
+  "Remove %XXX embedded spaces, etc in a url.
+If optional second argument ALLOW-NEWLINES is non-nil, then allow the
+decoding of carriage returns and line feeds in the string, which is normally
+forbidden in URL encoding."
+  (setq str (or (mm-subst-char-in-string ?+ ?  str) ""))
+  (let ((tmp "")
+       (case-fold-search t))
+    (while (string-match "%[0-9a-f][0-9a-f]" str)
+      (let* ((start (match-beginning 0))
+            (ch1 (gnus-url-unhex (elt str (+ start 1))))
+            (code (+ (* 16 ch1)
+                     (gnus-url-unhex (elt str (+ start 2))))))
+       (setq tmp (concat
+                  tmp (substring str 0 start)
+                  (cond
+                   (allow-newlines
+                    (char-to-string code))
+                   ((or (= code ?\n) (= code ?\r))
+                    " ")
+                   (t (char-to-string code))))
+             str (substring str (match-end 0)))))
+    (setq tmp (concat tmp str))
+    tmp))
+
+(defun gnus-make-predicate (spec)
+  "Transform SPEC into a function that can be called.
+SPEC is a predicate specifier that contains stuff like `or', `and',
+`not', lists and functions.  The functions all take one parameter."
+  `(lambda (elem) ,(gnus-make-predicate-1 spec)))
+
+(defun gnus-make-predicate-1 (spec)
+  (cond
+   ((symbolp spec)
+    `(,spec elem))
+   ((listp spec)
+    (if (memq (car spec) '(or and not))
+       `(,(car spec) ,@(mapcar 'gnus-make-predicate-1 (cdr spec)))
+      (error "Invalid predicate specifier: %s" spec)))))
+
+(defun gnus-local-map-property (map)
+  "Return a list suitable for a text property list specifying keymap MAP."
+  (cond
+   ((featurep 'xemacs)
+    (list 'keymap map))
+   ((>= emacs-major-version 21)
+    (list 'keymap map))
+   (t
+    (list 'local-map map))))
+
+(defun gnus-completing-read (prompt table &optional predicate require-match
+                                   history)
+  (when (and history
+            (not (boundp history)))
+    (set history nil))
+  (completing-read
+   (if (symbol-value history)
+       (concat prompt " (" (car (symbol-value history)) "): ")
+     (concat prompt ": "))
+   table
+   predicate
+   require-match
+   nil
+   history
+   (car (symbol-value history))))
+
+(defun gnus-graphic-display-p ()
+  (or (and (fboundp 'display-graphic-p)
+          (display-graphic-p))
+      ;;;!!!This is bogus.  Fixme!
+      (and (featurep 'xemacs)
+          t)))
+
+(put 'gnus-parse-without-error 'lisp-indent-function 0)
+(put 'gnus-parse-without-error 'edebug-form-spec '(body))
+
+(defmacro gnus-parse-without-error (&rest body)
+  "Allow continuing onto the next line even if an error occurs."
+  `(while (not (eobp))
+     (condition-case ()
+        (progn
+          ,@body
+          (goto-char (point-max)))
+       (error
+       (gnus-error 4 "Invalid data on line %d"
+                   (count-lines (point-min) (point)))
+       (forward-line 1)))))
+
+(defun gnus-cache-file-contents (file variable function)
+  "Cache the contents of FILE in VARIABLE.  The contents come from FUNCTION."
+  (let ((time (nth 5 (file-attributes file)))
+       contents value)
+    (if (or (null (setq value (symbol-value variable)))
+           (not (equal (car value) file))
+           (not (equal (nth 1 value) time)))
+       (progn
+         (setq contents (funcall function file))
+         (set variable (list file time contents))
+         contents)
+      (nth 2 value))))
+
+(defun gnus-multiple-choice (prompt choice &optional idx)
+  "Ask user a multiple choice question.
+CHOICE is a list of the choice char and help message at IDX."
+  (let (tchar buf)
+    (save-window-excursion
+      (save-excursion
+       (while (not tchar)
+         (message "%s (%s?): "
+                  prompt
+                  (mapconcat (lambda (s) (char-to-string (car s)))
+                             choice ""))
+         (setq tchar (read-char))
+         (when (not (assq tchar choice))
+           (setq tchar nil)
+           (setq buf (get-buffer-create "*Gnus Help*"))
+           (pop-to-buffer buf)
+           (fundamental-mode)          ; for Emacs 20.4+
+           (buffer-disable-undo)
+           (erase-buffer)
+           (insert prompt ":\n\n")
+           (let ((max -1)
+                 (list choice)
+                 (alist choice)
+                 (idx (or idx 1))
+                 (i 0)
+                 n width pad format)
+             ;; find the longest string to display
+             (while list
+               (setq n (length (nth idx (car list))))
+               (unless (> max n)
+                 (setq max n))
+               (setq list (cdr list)))
+             (setq max (+ max 4))      ; %c, `:', SPACE, a SPACE at end
+             (setq n (/ (1- (window-width)) max)) ; items per line
+             (setq width (/ (1- (window-width)) n)) ; width of each item
+             ;; insert `n' items, each in a field of width `width'
+             (while alist
+               (if (< i n)
+                   ()
+                 (setq i 0)
+                 (delete-char -1)              ; the `\n' takes a char
+                 (insert "\n"))
+               (setq pad (- width 3))
+               (setq format (concat "%c: %-" (int-to-string pad) "s"))
+               (insert (format format (caar alist) (nth idx (car alist))))
+               (setq alist (cdr alist))
+               (setq i (1+ i))))))))
+    (if (buffer-live-p buf)
+       (kill-buffer buf))
+    tchar))
+
 (provide 'gnus-util)
 
 ;;; gnus-util.el ends here
 (provide 'gnus-util)
 
 ;;; gnus-util.el ends here