;;; gnus-util.el --- utility functions for Semi-gnus
-;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003
+;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004
;; Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; used by Gnus and may be used by any other package without loading
;; Gnus first.
+;; [Unfortunately, it does depend on other parts of Gnus, e.g. the
+;; autoloads and defvars below...]
+
;;; Code:
(eval-when-compile
(require 'cl)
;; Fixme: this should be a gnus variable, not nnmail-.
- (defvar nnmail-pathname-coding-system))
-(eval-when-compile (require 'static))
+ (defvar nnmail-pathname-coding-system)
+
+ ;; Inappropriate references to other parts of Gnus.
+ (defvar gnus-emphasize-whitespace-regexp)
+ )
-(require 'custom)
(require 'time-date)
(require 'netrc)
+(eval-when-compile (require 'static))
+
(eval-and-compile
(autoload 'message-fetch-field "message")
(autoload 'gnus-get-buffer-window "gnus-win")
(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)
+ (replace-regexp-in-string regexp newtext string nil literal)))))
(defun gnus-boundp (variable)
"Return non-nil if VARIABLE is bound and non-nil."
(funcall (if (stringp buffer) 'get-buffer 'buffer-name)
buffer))))
-(static-cond
- ((fboundp 'point-at-bol)
- (defalias 'gnus-point-at-bol 'point-at-bol))
- ((fboundp 'line-beginning-position)
- (defalias 'gnus-point-at-bol 'line-beginning-position))
- (t
- (defun gnus-point-at-bol ()
- "Return point at the beginning of the line."
- (let ((p (point)))
- (beginning-of-line)
- (prog1
- (point)
- (goto-char p))))
- ))
-(static-cond
- ((fboundp 'point-at-eol)
- (defalias 'gnus-point-at-eol 'point-at-eol))
- ((fboundp 'line-end-position)
- (defalias 'gnus-point-at-eol 'line-end-position))
- (t
- (defun gnus-point-at-eol ()
- "Return point at the end of the line."
- (let ((p (point)))
- (end-of-line)
- (prog1
- (point)
- (goto-char p))))
- ))
-
;; The LOCAL arg to `add-hook' is interpreted differently in Emacs and
;; XEmacs. In Emacs we don't need to call `make-local-hook' first.
;; It's harmless, though, so the main purpose of this alias is to shut
;; up the byte compiler.
(defalias 'gnus-make-local-hook
- (if (eq (get 'make-local-hook 'byte-compile)
+ (if (eq (get 'make-local-hook 'byte-compile)
'byte-compile-obsolete)
'ignore ; Emacs
'make-local-hook)) ; XEmacs
;; Delete the current line (and the next N lines).
(defmacro gnus-delete-line (&optional n)
- `(delete-region (gnus-point-at-bol)
+ `(delete-region (point-at-bol)
(progn (forward-line ,(or n 1)) (point))))
(defun gnus-byte-code (func)
(cons 'progn (cddr fval)))))
(defun gnus-extract-address-components (from)
+ "Extract address components from a From header.
+Given an RFC-822 address FROM, extract full name and canonical address.
+Returns a list of the form (FULL-NAME CANONICAL-ADDRESS). Much more simple
+solution than `mail-extract-address-components', which works much better, but
+is slower."
(let (name address)
;; First find the address - the thing with the @ in it. This may
;; not be accurate in mail addresses, but does the trick most of
"Return the value of the header FIELD of current article."
(save-excursion
(save-restriction
- (let ((case-fold-search t)
- (inhibit-point-motion-hooks t))
+ (let ((inhibit-point-motion-hooks t))
(nnheader-narrow-to-headers)
(message-fetch-field field)))))
(defun gnus-goto-colon ()
(beginning-of-line)
- (let ((eol (gnus-point-at-eol)))
+ (let ((eol (point-at-eol)))
(goto-char (or (text-property-any (point) eol 'gnus-position t)
(search-forward ":" eol t)
(point)))))
(defun gnus-remove-text-with-property (prop)
"Delete all text in the current buffer with text property PROP."
- (save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (while (get-text-property (point) prop)
- (delete-char 1))
- (goto-char (next-single-property-change (point) prop nil (point-max))))))
+ (let ((start (point-min))
+ end)
+ (unless (get-text-property start prop)
+ (setq start (next-single-property-change start prop)))
+ (while start
+ (setq end (text-property-any start (point-max) prop nil))
+ (delete-region start (or end (point-max)))
+ (setq start (when end
+ (next-single-property-change start prop))))))
(defun gnus-newsgroup-directory-form (newsgroup)
"Make hierarchical directory name from NEWSGROUP name."
;; 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"
+ "Return 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"
+ "Return 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"
+ "Return 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)
respectively.")
(defun gnus-user-date (messy-date)
- "Format the messy-date acording to gnus-user-date-format-alist.
+ "Format the messy-date according 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))
+ (let* ((messy-date (time-to-seconds (safe-date-to-time messy-date)))
+ (now (time-to-seconds (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))
+ (my-format "%b %d '%y"))
+ (let* ((difference (- now 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) (seconds-to-time 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."
(defun gnus-split-references (references)
"Return a list of Message-IDs in REFERENCES."
(let ((beg 0)
+ (references (or references ""))
ids)
(while (string-match "<[^<]+[^< \t]" references beg)
(push (substring references (match-beginning 0) (setq beg (match-end 0)))
ids))
(nreverse ids)))
+(defun gnus-extract-references (references)
+ "Return a list of Message-IDs in REFERENCES (in In-Reply-To
+ format), trimmed to only contain the Message-IDs."
+ (let ((ids (gnus-split-references references))
+ refs)
+ (dolist (id ids)
+ (when (string-match "<[^<>]+>" id)
+ (push (match-string 0 id) refs)))
+ refs))
+
(defsubst gnus-parent-id (references &optional n)
"Return the last Message-ID in REFERENCES.
If N, return the Nth ancestor instead."
(defun gnus-read-event-char (&optional prompt)
"Get the next event."
- (let ((event (condition-case nil
- (read-event prompt)
- ;; `read-event' doesn't allow arguments in Mule 2.3
- (wrong-number-of-arguments
- (when prompt
- (message "%s" prompt))
- (read-event)))))
+ (let ((event (read-event prompt)))
;; should be gnus-characterp, but this can't be called in XEmacs anyway
(cons (and (numberp event) event) event)))
(set-buffer gnus-work-buffer)
(erase-buffer))
(set-buffer (gnus-get-buffer-create gnus-work-buffer))
- (kill-all-local-variables)))
+ (kill-all-local-variables)
+ (set-buffer-multibyte t)))
(defmacro gnus-group-real-name (group)
"Find the real name of a foreign newsgroup."
(substring gname (match-end 0))
gname)))
+(defmacro gnus-group-server (group)
+ "Find the server name of a foreign newsgroup.
+For example, (gnus-group-server \"nnimap+yxa:INBOX.foo\") would
+yield \"nnimap:yxa\"."
+ `(let ((gname ,group))
+ (if (string-match "^\\([^+]+\\).\\([^:]+\\):" gname)
+ (format "%s:%s" (match-string 1 gname) (match-string 2 gname))
+ (format "%s:%s" (car gnus-select-method) (cadr gnus-select-method)))))
+
(defun gnus-make-sort-function (funs)
- "Return a composite sort condition based on the functions in FUNC."
+ "Return a composite sort condition based on the functions in FUNS."
(cond
;; Just a simple function.
((functionp funs) funs)
(car funs))))
(defun gnus-make-sort-function-1 (funs)
- "Return a composite sort condition based on the functions in FUNC."
+ "Return a composite sort condition based on the functions in FUNS."
(let ((function (car funs))
(first 't1)
(last 't2))
(define-key (symbol-value (intern (format "gnus-%s-mode-map" type)))
[menu-bar edit] 'undefined))
+(defmacro gnus-bind-print-variables (&rest forms)
+ "Bind print-* variables and evaluate FORMS.
+This macro is used with `prin1', `pp', etc. in order to ensure printed
+Lisp objects are loadable. Bind `print-quoted' and `print-readably'
+to t, and `print-escape-multibyte', `print-escape-newlines',
+`print-escape-nonascii', `print-length', `print-level' and
+`print-string-length' to nil."
+ `(let ((print-quoted t)
+ (print-readably t)
+ ;;print-circle
+ ;;print-continuous-numbering
+ print-escape-multibyte
+ print-escape-newlines
+ print-escape-nonascii
+ ;;print-gensym
+ print-length
+ print-level
+ print-string-length)
+ ,@forms))
+
(defun gnus-prin1 (form)
"Use `prin1' on FORM in the current buffer.
-Bind `print-quoted' and `print-readably' to t while printing."
- (let ((print-quoted t)
- (print-readably t)
- (print-escape-multibyte nil)
- print-level print-length)
- (prin1 form (current-buffer))))
+Bind `print-quoted' and `print-readably' to t, and `print-length' and
+`print-level' to nil. See also `gnus-bind-print-variables'."
+ (gnus-bind-print-variables (prin1 form (current-buffer))))
(defun gnus-prin1-to-string (form)
"The same as `prin1'.
-Bind `print-quoted' and `print-readably' to t, and `print-length'
-and `print-level' to nil."
- (let ((print-quoted t)
- (print-readably t)
- (print-length nil)
- (print-level nil))
- (prin1-to-string form)))
+Bind `print-quoted' and `print-readably' to t, and `print-length' and
+`print-level' to nil. See also `gnus-bind-print-variables'."
+ (gnus-bind-print-variables (prin1-to-string form)))
+
+(defun gnus-pp (form &optional stream)
+ "Use `pp' on FORM in the current buffer.
+Bind `print-quoted' and `print-readably' to t, and `print-length' and
+`print-level' to nil. See also `gnus-bind-print-variables'."
+ (gnus-bind-print-variables (pp form (or stream (current-buffer)))))
+
+(defun gnus-pp-to-string (form)
+ "The same as `pp-to-string'.
+Bind `print-quoted' and `print-readably' to t, and `print-length' and
+`print-level' to nil. See also `gnus-bind-print-variables'."
+ (gnus-bind-print-variables (pp-to-string form)))
(defun gnus-make-directory (directory)
"Make DIRECTORY (and all its parents) if it doesn't exist."
(require 'nnmail)
- (let ((file-name-coding-system nnmail-pathname-coding-system)
- (pathname-coding-system nnmail-pathname-coding-system))
+ (let ((file-name-coding-system nnmail-pathname-coding-system))
(when (and directory
(not (file-exists-p directory)))
(make-directory directory t)))
"Write the current buffer's contents to FILE."
;; Make sure the directory exists.
(gnus-make-directory (file-name-directory file))
- (let ((file-name-coding-system nnmail-pathname-coding-system)
- (pathname-coding-system nnmail-pathname-coding-system))
+ (let ((file-name-coding-system nnmail-pathname-coding-system))
;; Write the buffer.
(write-region (point-min) (point-max) file nil 'quietly)))
;;; Functions for saving to babyl/mail files.
-(defvar rmail-default-rmail-file)
+(eval-when-compile
+ (condition-case nil
+ (progn
+ (require 'rmail)
+ (autoload 'rmail-update-summary "rmailsum"))
+ (error
+ (define-compiler-macro rmail-select-summary (&rest body)
+ ;; Rmail of the XEmacs version is supplied by the package, and
+ ;; requires tm and apel packages. However, there may be those
+ ;; who haven't installed those packages. This macro helps such
+ ;; people even if they install those packages later.
+ `(eval '(rmail-select-summary ,@body)))
+ ;; If there's rmail but there's no tm (or there's apel of the
+ ;; mainstream, not the XEmacs version), loading rmail of the XEmacs
+ ;; version fails halfway, however it provides the rmail-select-summary
+ ;; macro which uses the following functions:
+ (autoload 'rmail-summary-displayed "rmail")
+ (autoload 'rmail-maybe-display-summary "rmail")))
+ (defvar rmail-default-rmail-file)
+ (defvar mm-text-coding-system))
+
(defun gnus-output-to-rmail (filename &optional ask)
"Append the current article to an Rmail file named FILENAME."
(require 'rmail)
+ (require 'mm-util)
;; Most of these codes are borrowed from rmailout.el.
(setq filename (expand-file-name filename))
(setq rmail-default-rmail-file filename)
;; Decide whether to append to a file or to an Emacs buffer.
(let ((outbuf (get-file-buffer filename)))
(if (not outbuf)
- (let ((file-name-coding-system nnmail-pathname-coding-system)
- (pathname-coding-system nnmail-pathname-coding-system))
+ (let ((file-name-coding-system nnmail-pathname-coding-system))
(write-region-as-binary (point-min) (point-max)
filename 'append))
;; File has been visited, in buffer OUTBUF.
(insert "\n"))
(insert "\n"))
(goto-char (point-max))
- (let ((file-name-coding-system nnmail-pathname-coding-system)
- (pathname-coding-system nnmail-pathname-coding-system))
+ (let ((file-name-coding-system nnmail-pathname-coding-system))
(write-region-as-binary (point-min) (point-max)
filename 'append))))
;; File has been visited, in buffer OUTBUF.
(insert "\^_")))
(defun gnus-map-function (funs arg)
- "Applies the result of the first function in FUNS to the second, and so on.
+ "Apply the result of the first function in FUNS to the second, and so on.
ARG is passed to the first function."
(while funs
(setq arg (funcall (pop funs) arg)))
`(setq ,alist (delq (,fun ,key ,alist) ,alist))))
(defun gnus-globalify-regexp (re)
- "Returns a regexp that matches a whole line, iff RE matches a part of it."
+ "Return a regexp that matches a whole line, iff RE matches a part of it."
(concat (unless (string-match "^\\^" re) "^.*")
re
(unless (string-match "\\$$" re) ".*$")))
t))
(defun gnus-write-active-file (file hashtb &optional full-names)
- (let ((output-coding-system nnmail-active-file-coding-system)
- (coding-system-for-write nnmail-active-file-coding-system))
+ (let ((coding-system-for-write nnmail-active-file-coding-system))
(with-temp-file file
(mapatoms
(lambda (sym)
Setting it to nil has no effect after the first time `gnus-byte-compile'
is run."
:type 'boolean
- :version "21.1"
+ :version "21.4"
:group 'gnus-various)
(defun gnus-byte-compile (form)
"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
+by side effect; therefore, write `(setq foo (gnus-remassoc key foo))' to be
sure of changing the value of `foo'."
(when alist
(if (equal key (caar alist))
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) "")) ; why `or'?
(let ((tmp "")
(case-fold-search t))
(while (string-match "%[0-9a-f][0-9a-f]" str)
`(,(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))))
-
-(defmacro gnus-completing-read-maybe-default (prompt table &optional predicate
- require-match initial-contents
- history default)
- "Like `completing-read', allowing for non-existent 7th arg in older XEmacsen."
- `(completing-read ,prompt ,table ,predicate ,require-match
- ,initial-contents ,history
- ,@(if (and (featurep 'xemacs) (< emacs-minor-version 2))
- ()
- (list default))))
-
(defun gnus-completing-read (prompt table &optional predicate require-match
history)
(when (and history
(not (boundp history)))
(set history nil))
- (gnus-completing-read-maybe-default
+ (completing-read
(if (symbol-value history)
(concat prompt " (" (car (symbol-value history)) "): ")
(concat prompt ": "))
(while (not tchar)
(message "%s (%s): "
prompt
- (mapconcat (lambda (s) (char-to-string (car s)))
- choice ", "))
+ (concat
+ (mapconcat (lambda (s) (char-to-string (car s)))
+ choice ", ") ", ?"))
(setq tchar (read-char))
(when (not (assq tchar choice))
(setq tchar nil)
(x-focus-frame frame))
((eq window-system 'w32)
(w32-focus-frame frame)))
- (when (or (not (boundp 'focus-follows-mouse))
- (symbol-value 'focus-follows-mouse))
+ (when focus-follows-mouse
(set-mouse-position frame (1- (frame-width frame)) 0)))))
-(unless (fboundp 'frame-parameter)
- (defalias 'frame-parameter
- (lambda (frame parameter)
- "Return FRAME's value for parameter PARAMETER.
-If FRAME is nil, describe the currently selected frame."
- (cdr (assq parameter (frame-parameters frame))))))
-
(defun gnus-frame-or-window-display-name (object)
"Given a frame or window, return the associated display name.
Return nil otherwise."
(push (pop list1) res)))
(nconc (nreverse res) list1 list2))))
+(eval-when-compile
+ (defvar xemacs-codename))
+
+(defun gnus-emacs-version ()
+ "Stringified Emacs version."
+ (let ((system-v
+ (cond
+ ((eq gnus-user-agent 'emacs-gnus-config)
+ system-configuration)
+ ((eq gnus-user-agent 'emacs-gnus-type)
+ (symbol-name system-type))
+ (t nil))))
+ (cond
+ ((eq gnus-user-agent 'gnus)
+ nil)
+ ((string-match "^\\(\\([.0-9]+\\)*\\)\\.[0-9]+$" emacs-version)
+ (concat "Emacs/" (match-string 1 emacs-version)
+ (if system-v
+ (concat " (" system-v ")")
+ "")))
+ ((string-match
+ "\\([A-Z]*[Mm][Aa][Cc][Ss]\\)[^(]*\\(\\((beta.*)\\|'\\)\\)?"
+ emacs-version)
+ (concat
+ (match-string 1 emacs-version)
+ (format "/%d.%d" emacs-major-version emacs-minor-version)
+ (if (match-beginning 3)
+ (match-string 3 emacs-version)
+ "")
+ (if (boundp 'xemacs-codename)
+ (concat
+ " (" xemacs-codename
+ (if system-v
+ (concat ", " system-v ")")
+ ")"))
+ "")))
+ (t emacs-version))))
+
+(defun gnus-rename-file (old-path new-path &optional trim)
+ "Rename OLD-PATH as NEW-PATH. If TRIM, recursively delete
+empty directories from OLD-PATH."
+ (when (file-exists-p old-path)
+ (let* ((old-dir (file-name-directory old-path))
+ (old-name (file-name-nondirectory old-path))
+ (new-dir (file-name-directory new-path))
+ (new-name (file-name-nondirectory new-path))
+ temp)
+ (gnus-make-directory new-dir)
+ (rename-file old-path new-path t)
+ (when trim
+ (while (progn (setq temp (directory-files old-dir))
+ (while (member (car temp) '("." ".."))
+ (setq temp (cdr temp)))
+ (= (length temp) 0))
+ (delete-directory old-dir)
+ (setq old-dir (file-name-as-directory
+ (file-truename
+ (concat old-dir "..")))))))))
+
+(defun gnus-set-file-modes (filename mode)
+ "Wrapper for set-file-modes."
+ (ignore-errors
+ (set-file-modes filename mode)))
+
(provide 'gnus-util)
;;; gnus-util.el ends here