Mercurial > hg > xemacs-beta
diff lisp/gnats/gnats.el @ 110:fe104dbd9147 r20-1b7
Import from CVS: tag r20-1b7
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:19:45 +0200 |
parents | |
children | b980b6286996 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/gnats/gnats.el Mon Aug 13 09:19:45 2007 +0200 @@ -0,0 +1,2034 @@ +;;;; -*-emacs-lisp-*- +;;;; EMACS interface for GNATS. +;;;; Copyright (C) 1992, 1993, 1994, 1995, 1996 Free Software Foundation, Inc. +;;;; Contributed by Brendan Kehoe (brendan@cygnus.com) +;;;; based on an original version by Heinz G. Seidl (hgs@ide.com). +;;;; +;;;; This file is part of GNU GNATS. +;;;; +;;;; GNU GNATS is free software; you can redistribute it and/or modify +;;;; it under the terms of the GNU General Public License as published by +;;;; the Free Software Foundation; either version 2, or (at your option) +;;;; any later version. +;;;; +;;;; GNU GNATS is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with GNU GNATS; see the file COPYING. If not, write to +;;;; the Free Software Foundation, 59 Temple Place - Suite 330, +;;;; Boston, MA 02111, USA. */ + +;;;; This file provides `edit-pr', `view-pr' `query-pr', for changing and +;;;; searching problem reports that are part of the GNATS database. See the +;;;; gnats(1) man page or the GNATS documentation for further information. + +(provide 'gnats) +(require 'send-pr) ;Shared stuff defined there + +;;;;--------------------------------------------------------------------------- +;;;; Customization: put the following forms into your default.el file +;;;; (or into your .emacs) and the whole file into your EMACS library. +;;;;--------------------------------------------------------------------------- + +;(autoload 'edit-pr "gnats" +; "Command to edit a problem report." t) + +;(autoload 'view-pr "gnats" +; "Command to view a problem report." t) + +;(autoload 'gnats-mode "gnats" +; "Major mode for editing of problem reports." t) + +;(autoload 'query-pr "gnats" +; "Command to query information about problem reports." t) + +;(autoload 'summ-pr "gnats" +; "Command to display a summary listing of problem reports." t) + +;;;;--------------------------------------------------------------------------- +;;;; To reply by mail within gnats-mode +;;;;--------------------------------------------------------------------------- + +(defvar gnats-mailer 'mail + "*Specifiles either `mail' or `mhe' as mailer for GNATS") + +;; Provides mail reply and mail other window command using usual mail +;; interface and mh-e interface. +;; +;; To use MAIL: set the variable +;; `gnats-mailer' to `mail' +;; +;; To use MH-E: set the variable +;; `gnats-mailer' to `mhe' + +(autoload 'mail "sendmail") +(autoload 'mail-other-window "sendmail") +(autoload 'mail-fetch-field "mail-utils") +(autoload 'rmail-dont-reply-to "mail-utils") +(autoload 'mail-strip-quoted-names "mail-utils") +(autoload 'mail-send "sendmail") +(autoload 'mail-send-and-exit "sendmail") + +(autoload 'mh-send "mh-e") +(autoload 'mh-send-other-window "mh-e") +(autoload 'mh-find-path "mh-e") +(autoload 'mh-get-field "mh-e") +(autoload 'mh-insert-fields "mh-e") +(defvar mh-show-buffer nil) +(defvar mh-sent-from-folder nil) +(defvar mh-sent-from-msg nil) + +;;; User options + +(defvar gnats:keep-edited-buffers t + "*If non-nil, then PR buffers are kept with distinct names after +editing. Otherwise, only the the most recently edited PR is kept.") + +(defvar gnats:keep-sent-messages 1 + "*Non-nil value N causes GNATS to keep the last N messages sent from GNATS. +A value of 0 or nil causes GNATS never to keep such buffers. A value of t +causes GNATS to keep all such buffers.") + +(defvar gnats:network-server nil + "*If non-nil, names the GNATS network server for remote queries and editing.") + +(defvar gnats:run-in-background t + "*If non-nil, make GNATS programs run in the background allowing the emacs to continue to other things.") + +(defvar gnats:bury-edited-prs t + "*If non-nil, then PR buffers are buried after editing. Otherwise, they are left at the top of the buffer list.") + +;;; emacs 19 uses compile-internal, emacs 18 uses compile1 +(if gnats::emacs-19p + (autoload 'compile-internal "compile") + (autoload 'compile1 "compile") + (fset 'compile-internal 'compile1)) + +;;; Misc constants. + +;;(defvar gnats:root "/usr/share/gnats/gnats-db" +;; "*The top of the tree containing the GNATS database.") + +(defvar gnats:libdir (or (gnats::get-config "LIBDIR") "/usr/lib") + "*Where the `gnats' subdirectory lives for things like pr-edit.") + +(defvar gnats:addr (or (gnats::get-config "GNATS_ADDR") "bugs") + "*Where bug reports are sent.") + +(defvar gnats::version + (concat "Version " (or (gnats::get-config "VERSION") "3.101"))) + +(defvar gnats::diffopt (or (gnats::get-config "DIFFOPT") "-u") + "How to get human-friendly output from diff(1).") + +(defvar gnats::categories nil + "List of GNATS categories; computed at runtime.") + +(defvar gnats::responsibles nil + "List of GNATS responsibles; Computed at runtime.") + +(defvar gnats::submitters nil + "List of GNATS submitters; Computed at runtime.") + +;;;###autoload +(defvar gnats::mode-name nil + "Name of the GNATS mode.") + +(defconst gnats::err-buffer "*gnats-error*" + "Name of the temporary buffer, where gnats error messages appear.") + +;;(defconst gnats::indent 17 "Indent for formatting the value.") + +(defvar gnats:::pr-locked nil + "Buffer local flag representing whether the associated pr is locked.") + +(defvar gnats:::pr-errors nil + "Buffer local buffer holding any errors from attempting to file this pr.") + +(defvar gnats:::buffer-pr nil + "Buffer local name of this pr.") + +(defvar gnats:::current-pr nil + "Buffer local value of the current pr.") + +(defvar gnats:::do-file-pr nil + "Buffer local value; if T, file the current pr.") + +(defvar gnats:::force nil + "Buffer local value; if T, ignore errors unlocking the current pr.") + +(defvar gnats:::pr-buffer nil + "Buffer local value of the buffer containing the pr.") + +(defvar gnats:::audit-trail nil + "Buffer local audit trail for the current pr.") + +(defvar gnats:::backupname nil + "Buffer local name of the backup file for this pr.") + +(defvar gnats:::start-of-PR-fields nil + "Buffer position of the beginning of the PR fields.") + +(defvar gnats:::newfile nil + "File used to store the results of npr-edit.") + +(defvar gnats:::query-pr "query-pr" + "The program name used to query problem reports.") + +(defvar gnats:::nquery-pr "nquery-pr" + "The program name used to query problem reports over the network.") + +(defvar gnats:::query-regexp "n?query-pr:" + "The regular expression to use to recognize a message from the query program.") + +;; For example: +;; (setq gnats:::types '( ( "Games" ( "/gnats/games" "/usr/gamesdb/H-sun4/lib ") +;; ( "Tools" ( "/usr/toolsdb" "/usr/local/lib" )))) +(defvar gnats:::types nil + "Alist of each type of GNATS database and its root and libdir settings.") + +(defconst gnats::fields + (let (fields) + (setq + fields + ;; Duplicate send-pr::fields, don't just include it. + ;; is there a better way than this? + (append (read (prin1-to-string send-pr::fields)) + '(("Arrival-Date" nil nil text) + ("Customer-Id") + ("Number" nil nil number) + ("Responsible" gnats::set-responsibles nil enum + gnats::update-audit-trail) + ("State" + (("open") ("analyzed") ("feedback") ("suspended") ("closed")) + (lambda (x) (or (cdr (assoc x gnats::state-following)) "")) + enum gnats::update-audit-trail)))) + ;; (setf (second (assoc "Category" fields)) 'gnats::set-categories) + (setcar (cdr (assoc "Category" fields)) 'gnats::set-categories) + (setcdr (nthcdr 3 (assoc "Category" fields)) + '(gnats::update-responsible)) + (setcar (cdr (assoc "Class" fields)) + '(("sw-bug") ("doc-bug") ("change-request") ("support") + ("mistaken") ("duplicate"))) + (setcdr (assoc "Submitter-Id" fields) '(gnats::set-submitters t enum)) + (setcdr (assoc "Customer-Id" fields) (cdr (assoc "Submitter-Id" fields))) + fields) + "AList of one-line PR fields and their possible values.") + +(defconst gnats::state-following + '(("open" . "analyzed") + ("analyzed" . "feedback") + ("feedback" . "closed") + ("suspended" . "analyzed")) + "A list of states and possible following states (does not describe all +possibilities).") + +(defvar gnats::query-pr-history nil + "Past arguments passed to the query-pr program.") + +(defvar gnats::tmpdir (or (getenv "TMPDIR") "/tmp/") + "Directory to use for temporary files.") + +;;;;--------------------------------------------------------------------------- +;;;; hooks +;;;;--------------------------------------------------------------------------- + +;; we define it here in case it's not defined +(or (boundp 'text-mode-hook) (setq text-mode-hook nil)) +(defvar gnats-mode-hook text-mode-hook "Called when gnats mode is switched on.") + +;;;;--------------------------------------------------------------------------- +;;;; Error conditions +;;;;--------------------------------------------------------------------------- + +(put 'gnats::error 'error-conditions '(error gnats::error)) +(put 'gnats::error 'error-message "GNATS error") + +;; pr-edit --check was unhappy +(put 'gnats::invalid-fields 'error-conditions + '(error gnats::error gnats::invalid-fields)) +(put 'gnats::invalid-fields 'error-message "invalid fields in PR") +(put 'gnats::invalid-date 'error-conditions + '(error gnats::error gnats::invalid-date)) +(put 'gnats::invalid-date 'error-message "invalid date value") + +;; pr-addr couldn't find an appropriate address +(put 'gnats::invalid-name 'error-conditions + '(error gnats::error gnats::invalid-name)) +(put 'gnats::invalid-name 'error-message "could not find the requested address") + +;; what pr? +(put 'gnats::no-such-pr 'error-conditions '(error gnats::error gnats::no-such-pr)) +(put 'gnats::no-such-pr 'error-message "PR does not exist") + +;; +(put 'gnats::no-such-category 'error-conditions + '(error gnats::error gnats::no-such-category)) +(put 'gnats::no-such-category 'error-message "No such category") + +;; there is no lock on that pr +(put 'gnats::pr-not-locked 'error-conditions + '(error gnats::error gnats::pr-not-locked)) +(put 'gnats::pr-not-locked 'error-message "No one is locking the PR") + +;; there is a lock on that pr +(put 'gnats::locked-pr 'error-conditions '(error gnats::error gnats::locked-pr)) +(put 'gnats::locked-pr 'error-message "PR locked by") + +;; GNATS is locked +(put 'gnats::locked 'error-conditions '(error gnats::error gnats::locked)) +(put 'gnats::locked 'error-message "GNATS is locked by another process---try again later.") + +;; We can't lock GNATS. +(put 'gnats::cannot-lock 'error-conditions '(error gnats::error gnats::locked)) +(put 'gnats::cannot-lock 'error-message "cannot lock GNATS; try again later.") + +;;;;--------------------------------------------------------------------------- +;;;; GNATS mode +;;;;--------------------------------------------------------------------------- + +(defvar gnats-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\M-n" 'gnats:next-field) + (define-key map "\M-p" 'gnats:previous-field) + (define-key map "\C-\M-b" 'gnats:backward-field) + (define-key map "\C-\M-f" 'gnats:forward-field) + (define-key map "\C-c\C-a" 'gnats:mail-reply) + (define-key map "\C-c\C-c" 'gnats:submit-pr) + (define-key map "\C-c\C-e" 'gnats:edit-pr) + (define-key map "\C-c\C-f" 'gnats:change-field) + (define-key map "\C-c\C-m" 'gnats:mail-other-window) + (define-key map "\C-c\C-q" 'gnats:unlock-buffer-force) + (define-key map "\C-c\C-r" 'gnats:responsible-change-from-to) + (define-key map "\C-c\C-s" 'gnats:state-change-from-to) + (define-key map "\C-c\C-t" 'gnats:category-change-from-to) + (define-key map "\C-c\C-u" 'gnats:unlock-pr) + (or gnats::emacs-19p + (define-key map "\C-xk" 'gnats:kill-buffer)) + map) + "Keymap for gnats mode.") + +(defsubst gnats::get-header (field) + "Fetch the contents of mail header FIELD." + (funcall (nth 4 (assoc gnats-mailer gnats::mail-functions)) field)) + +(defun gnats:submit-pr () + "Save the current PR into the database and unlock it. + +This function has no effect if the PR is not locked. + +Three cases: + State change + Responsibility change + Other change (only interesting if the PR was changed by somebody + other than the Reponsible person) + +State changes are sent to the originator +Responsibility changes are sent to the new responsible person +Other changes are sent to the responsible person." + ;; + (interactive) + (cond ((not (eq major-mode 'gnats:gnats-mode)) + (error "edit-pr: not in GNATS mode.")) + (gnats:::pr-locked + (gnats::check-pr-background t) + (if gnats:run-in-background (bury-buffer))) + ;; If not locked, do nothing + (t + (message "edit-pr: PR not locked.")))) + +;;;###autoload +(setq gnats::mode-name 'gnats:gnats-mode) + +(defun gnats::rename-buffer () + (let ((category (gnats::field-contents "Category")) + (number (gnats::field-contents "Number")) + buf) + (setq gnats:::buffer-pr (format "%s/%s" category number)) + (and (setq buf (get-buffer gnats:::buffer-pr)) + (save-excursion + (set-buffer buf) + (set-buffer-modified-p nil) + (kill-buffer buf))) + (rename-buffer gnats:::buffer-pr))) + +;; FIXME allow re-lock of unlocked PR +;; FIXME too many assumptions -- make more independent of edit-pr +;;;###autoload +(fset 'gnats-mode gnats::mode-name) +;;;###autoload +(defun gnats:gnats-mode () + "Major mode for editing problem reports. +For information about the form see gnats(1) and pr_form(5). + +When you are finished editing the buffer, type \\[gnats:submit-pr] to commit +your changes to the PR database. To abort the edit, type +\\[gnats:unlock-buffer]. + +Special commands: +\\{gnats-mode-map} +Turning on gnats-mode calls the value of the variable gnats-mode-hook, +if it is not nil." + (gnats::patch-exec-path) ;Why is this necessary? --jason + (gnats::set-categories) + (gnats::set-responsibles) + (gnats::set-submitters) + (put gnats::mode-name 'mode-class 'special) + (kill-all-local-variables) + (setq major-mode gnats::mode-name) + (setq mode-name "gnats") + (use-local-map gnats-mode-map) + (set-syntax-table text-mode-syntax-table) + (setq local-abbrev-table text-mode-abbrev-table) + (make-local-variable 'gnats:::start-of-PR-fields) + (make-local-variable 'gnats:::pr-errors) + (make-local-variable 'gnats:::buffer-pr) + (gnats::rename-buffer) + + ;; we do this in gnats:edit-pr for the network version + (if (not gnats:network-server) + (progn + (setq gnats:::backupname (gnats::make-temp-name)) + (copy-file (format "%s/%s" gnats:root gnats:::buffer-pr) + gnats:::backupname))) + + ;; edit-pr locks it for us + (make-local-variable 'gnats:::pr-locked) + (setq gnats:::pr-locked t) + + (cond (gnats::emacs-19p + (make-local-variable 'kill-buffer-hook) + (add-hook 'kill-buffer-hook 'gnats::kill-buffer-hook))) + + ; If they do C-x C-c, unlock all of the PRs they've edited. + (if (fboundp 'add-hook) + (add-hook 'kill-emacs-hook 'gnats::unlock-all-buffers) + (setq kill-emacs-hook 'gnats::unlock-all-buffers)) + + (make-local-variable 'paragraph-separate) + (setq paragraph-separate (concat (default-value 'paragraph-separate) + "\\|" gnats::keyword "$")) + (make-local-variable 'paragraph-start) + (setq paragraph-start (concat (default-value 'paragraph-start) + "\\|" gnats::keyword)) + (make-local-variable 'gnats:::audit-trail) + (goto-char (point-min)) + (search-forward "\n>Number:") + (beginning-of-line) + (setq gnats:::start-of-PR-fields (point-marker)) + (run-hooks 'gnats-mode-hook)) + +;;;;--------------------------------------------------------------------------- +;;;; Mail customization +;;;;--------------------------------------------------------------------------- + +(or (string-match mail-yank-ignored-headers "X-mode:") + (setq mail-yank-ignored-headers + (concat "^X-mode:" "\\|" mail-yank-ignored-headers))) + +(defconst gnats::mail-functions + '((mail gnats::mail-other-window-using-mail + gnats::mail-reply-using-mail + gnats::mail-PR-changed-mail-setup + gnats::get-header-using-mail-fetch-field) + (mhe gnats::mail-other-window-using-mhe + gnats::mail-reply-using-mhe + gnats::mail-PR-changed-mhe-setup + gnats::get-header-using-mhe)) + "An association list of mailers and the functions that use them. +The functions are supposed to implement, respectively: +gnats::mail-other-window +gnats::mail-reply +gnats::mail-PR-changed-setup +gnats::get-header") + +;;;;--------------------------------------------------------------------------- +;;;; Toplevel functions and vars, to reply with mail within gnats-mode +;;;;--------------------------------------------------------------------------- + +(defun gnats:mail-other-window () + "Compose mail in other window. +Customize the variable `gnats-mailer' to use another mailer." + ;; + (interactive) + (funcall (nth 1 (assoc gnats-mailer gnats::mail-functions)))) + +(defun gnats:mail-reply (&optional just-sender) + "Reply mail to PR Originator. +Customize the variable `gnats-mailer' to use another mailer. +If optional argument JUST-SENDER is non-nil, send response only to +original submitter of problem report." + ;; + (interactive "P") + (funcall (nth 2 (assoc gnats-mailer gnats::mail-functions)) just-sender)) + +;;;; common (and suppport) functions + +(defun gnats::isme (addr) + (setq addr (mail-strip-quoted-names addr)) + (or (string= addr (user-login-name)) + (string= addr (concat (user-login-name) "@" (system-name))))) + +(defsubst gnats::mail-PR-changed-setup (to subject cc buffer) + (funcall (nth 3 (assoc gnats-mailer gnats::mail-functions)) + to subject cc buffer)) + +(defun gnats::mail-PR-changed-mail-setup (to subject cc buffer) + (or (gnats::vmish-mail + (format "notification of changes to PR %s" gnats:::buffer-pr) + nil to subject nil cc buffer) + (error "Submit aborted; PR is still locked."))) + +(defun gnats::mail-PR-changed-mhe-setup (to subject cc buffer) + (let ((config (current-window-configuration)) + (pop-up-windows t) + draft) + (mh-find-path) + (let ((pop-up-windows t)) + (mh-send-sub to (or cc "") subject config)) + (switch-to-buffer (current-buffer)) + (setq mh-sent-from-folder buffer + mh-sent-from-msg 1 + mh-show-buffer buffer))) + +(defun gnats::mail-PR-changed (user responsible resp-change state-change notify) + "- Send mail to the responsible person if the PR has been changed + by someone else +- Send mail to the originator when the state is changed. +- Send mail to old and new responsible people when the responsibility is + transferred. + `resp-change' is the list (old-resp new-resp start end) +- Send mail to any other parties in NOTIFY." + ;; This function is really ugly ! + ;; + (let ((to nil) + (cc nil) + (subn nil) (subm nil) + (subject (gnats::get-reply-subject)) + (buffer (current-buffer)) + (pr-change (not (or resp-change state-change))) + (pr-backupname gnats:::backupname) + ) + ;; Here we find out where to send the mail to + (let (to-resp to-new-resp to-submitter to-bugs resp-addr new-resp-addr) + (if pr-change (setq to-resp t to-bugs t) + (if resp-change (setq to-resp t to-new-resp t)) + (if state-change (setq to-submitter t to-resp t))) + (cond (to-new-resp + (setq new-resp-addr (gnats::pr-addr (car resp-change))) + (if (gnats::isme new-resp-addr) + (setq to-new-resp nil)))) + (cond (to-resp + (setq resp-addr (gnats::pr-addr responsible)) + (if (gnats::isme resp-addr) + (setq to-resp nil)))) + (cond (to-submitter + (setq cc to) + (setq to (list (gnats::get-reply-to))))) + (if to-resp (gnats::push resp-addr to)) + (if to-new-resp (gnats::push new-resp-addr to)) + (setq subm (or (gnats::field-contents "Customer-Id") + (gnats::field-contents "Submitter-Id"))) + (if subm + (progn + (setq subn (nth 5 (assoc subm gnats::submitters))) + (if (not (string= subn "")) + (gnats::push subn cc)))) + (if to-bugs (gnats::push gnats:addr cc)) + (if notify (gnats::push notify cc)) + (setq to (mapconcat 'identity to ", ") + cc (mapconcat 'identity cc ", ")) + (if (string= cc "") (setq cc nil))) + (gnats::mail-PR-changed-setup to subject cc buffer) + ;; now we assume that the current buffer is the mail buffer + (goto-char (point-max)) + (if pr-change + (progn + (insert + (format "\n\t`%s' made changes to this PR.\n\n" (user-full-name))) + (if (and pr-backupname (file-readable-p pr-backupname)) + (let ((file (gnats::make-temp-name)) + (default-directory (gnats::find-safe-default-directory))) + (save-excursion + (set-buffer buffer) + (write-region (point-min) (point-max) file)) + (call-process "diff" nil t t gnats::diffopt + pr-backupname file) + (delete-file file)))) + (if resp-change + (progn + (insert (format "\n\t`%s' changed the responsibility to `%s'.\n" + (user-full-name) responsible)) + (insert-buffer-substring buffer + (nth 2 resp-change) + (nth 3 resp-change))) + (if state-change + (progn + (insert (format "\n\t`%s' changed the state to `%s'.\n" + (user-full-name) (nth 1 state-change))) + (insert-buffer-substring buffer + (nth 2 state-change) + (nth 3 state-change)))))) + )) + +(defsubst gnats::bm (num) + (buffer-substring (match-beginning num) (match-end num))) + +(defun gnats::real-pr-addr (name) + (if (zerop (length name)) + nil + (let ((buf (generate-new-buffer gnats::err-buffer))) + (unwind-protect + (save-excursion + (let ((default-directory (gnats::find-safe-default-directory))) + (call-process (format "%s/gnats/pr-addr" gnats:libdir) + nil buf nil "-F" name)) + (set-buffer buf) + (goto-char (point-min)) + (cond ((looking-at "pr-addr: could not find the requested address") + nil) + ((looking-at "^\\([^:]*\\):\\([^:]*\\):\\([^:]*\\)\n") + (list (gnats::bm 1) (gnats::bm 2) (gnats::bm 3))) + (t (signal 'gnats::error + (list (buffer-substring (point-min) + (1- (point-max)))))))) + (kill-buffer buf))))) + +(defun gnats::pr-addr (name) + "Find the e-mail address corresponding to maintainer NAME." + (let (entry addr) + (or (setq entry (assoc name gnats::responsibles)) + (and (setq entry (gnats::real-pr-addr name)) + (gnats::push entry gnats::responsibles)) + (signal 'gnats::invalid-name (list name))) + (setq addr (if (zerop (length (nth 2 entry))) + name + (nth 2 entry))) + (if (zerop (length (nth 1 entry))) + addr + (concat (nth 1 entry) " <" addr ">")))) + +(defun gnats::get-header-using-mail-fetch-field (field) + (save-excursion + (save-restriction + (goto-char (point-min)) + (re-search-forward "^$" nil 'move) + (narrow-to-region (point-min) (point)) + (mail-fetch-field field)))) + +(defun gnats::get-header-using-mhe (field) + (save-excursion + (let ((ret (mh-get-field (concat field ":")))) + (if (string= ret "") + nil + ret)))) + +(defun gnats::get-reply-to () + (or (gnats::get-header "Reply-To") + (gnats::get-header "From"))) + +(defun gnats::get-reply-subject () + (save-excursion + (save-restriction + (widen) + (let ((category (gnats::field-contents "Category")) + (number (gnats::field-contents "Number")) + (synopsis (gnats::field-contents "Synopsis" 0)) + (subject)) + (goto-char (point-min)) + (narrow-to-region (point-min) + (progn (search-forward "\n\n" nil 'move) + (point-marker))) + (setq subject (mail-fetch-field "subject")) + (if (and synopsis (not (equal synopsis ""))) + (format "Re: %s/%s: %s" category number synopsis) + (format "Re: %s/%s: %s" category number subject)))))) + +(defun gnats::make-in-reply-to-field (from date msg-id) + (concat + (substring from 0 (string-match " *at \\| *@ \\| *(\\| *<" from)) + "'s message of " date + (if (not (equal msg-id "")) + (concat "\n " msg-id) + ""))) + +;;;; Send mail using sendmail mail mode. + +(defun gnats::mail-reply-using-mail (just-sender) + ;; + "Mail a reply to the originator of the PR. +Normally include CC: to all other recipients of original message; +argument means ignore them. +While composing the reply, use \\[mail-yank-original] to yank the +original message into it." + ;; + (let (from cc subject date to reply-to msg-id) + (save-excursion + (save-restriction + (widen) + (narrow-to-region (point-min) (progn (goto-char (point-min)) + (search-forward "\n\n") + (- (point) 1))) + (setq from (mail-fetch-field "from" nil t) + subject (gnats::get-reply-subject) + reply-to (or (mail-fetch-field "reply-to" nil t) + from) + date (mail-fetch-field "date" nil t) + cc (cond (just-sender nil) + (t (mail-fetch-field "cc" nil t))) + to (or (mail-fetch-field "to" nil t) + (mail-fetch-field "apparently-to" nil t) + "") + msg-id (mail-fetch-field "message-id") + ))) + (gnats::vmish-mail-other-window + (format "reply to PR %s" gnats:::buffer-pr) + nil (mail-strip-quoted-names reply-to) subject + (gnats::make-in-reply-to-field from date msg-id) + (if just-sender + nil + (let* ((cc-list (rmail-dont-reply-to (mail-strip-quoted-names + (if (null cc) to + (concat to ", " cc)))))) + (if (string= cc-list "") nil cc-list))) + (current-buffer)))) + +(defun gnats::mail-other-window-using-mail () + "Send mail in another window. +While composing the message, use \\[mail-yank-original] to yank the +original message into it." + (gnats::vmish-mail-other-window + (format "mail regarding PR %s" gnats:::buffer-pr) + nil nil (gnats::get-reply-subject) nil nil (current-buffer))) + +;; This must be done in two toplevel forms because of a 19.19 byte-compiler +;; bug. +(defun gnats::generate-new-buffer-name (prefix) + (let ((name prefix) (n 1)) + (while (get-buffer name) + (setq name (format "%s<%d>" prefix n)) + (setq n (1+ n))) + name)) + +(if (fboundp 'generate-new-buffer-name) + (fset 'gnats::generate-new-buffer-name 'generate-new-buffer-name)) + +(defvar gnats::kept-mail-buffers nil + "Sent mail buffers waiting to be killed.") + +(defun gnats::vmish-rename-after-send () + (or (string-match "^sent " (buffer-name)) + (rename-buffer (gnats::generate-new-buffer-name + (format "sent %s" (buffer-name))))) + + ;; Mostly lifted from vm-reply.el 5.35 + (setq gnats::kept-mail-buffers + (cons (current-buffer) gnats::kept-mail-buffers)) + (if (not (eq gnats:keep-sent-messages t)) + (let ((extras (nthcdr (or gnats:keep-sent-messages 0) + gnats::kept-mail-buffers))) + (mapcar (function (lambda (b) (and (buffer-name b) (kill-buffer b)))) + extras) + (and gnats::kept-mail-buffers extras + (setcdr (memq (car extras) gnats::kept-mail-buffers) nil))))) + +(if gnats::emacs-19p + (defun gnats::vmish-mail-bindings ()) + (defun gnats::vmish-mail-send () + (interactive) + (gnats::vmish-rename-after-send) + (mail-send)) + (defun gnats::vmish-mail-send-and-exit (arg) + (interactive "P") + (gnats::vmish-rename-after-send) + (mail-send-and-exit arg)) + (defun gnats::vmish-mail-bindings () + (use-local-map (copy-keymap (current-local-map))) + (local-set-key "\C-c\C-s" 'gnats::vmish-mail-send) + (local-set-key "\C-c\C-c" 'gnats::vmish-mail-send-and-exit)) + (defun string-to-number (str) (string-to-int str))) + +;; ignore 'free variable' warnings about buf. +(defsubst gnats::vmish-rename-mail-buffer (buf) + (save-excursion + (set-buffer buf) + (setq buf (gnats::generate-new-buffer-name "*not mail*")) + (rename-buffer buf))) + +;; ignore 'free variable' warnings about buf. +(defsubst gnats::vmish-restore-mail-buffer (buf) + (save-excursion + (let ((mbuf (get-buffer "*mail*"))) + (cond (mbuf ;maybe left over from m-o-w failure + (set-buffer mbuf) + (set-buffer-modified-p nil) + (kill-buffer mbuf)))) + (cond (buf + (set-buffer buf) + (rename-buffer "*mail*"))))) + +(defun gnats::vmish-mail-other-window + (&optional buffer-name noerase to subject in-reply-to cc replybuffer actions) + (let ((buf (get-buffer "*mail*"))) + (if buf (gnats::vmish-rename-mail-buffer buf)) + (or buffer-name (setq buffer-name "GNATS mail")) + (unwind-protect + (prog1 + (if gnats::emacs-19p + (mail-other-window + noerase to subject in-reply-to cc replybuffer + (cons '(gnats::vmish-rename-after-send) actions)) + (prog1 + (mail-other-window noerase to subject in-reply-to + cc replybuffer) + (gnats::vmish-mail-bindings))) + (rename-buffer (gnats::generate-new-buffer-name buffer-name))) + (gnats::vmish-restore-mail-buffer buf)))) + +(defun gnats::vmish-mail + (&optional buffer-name noerase to subject in-reply-to cc replybuffer actions) + (let (buf (get-buffer "*mail*")) + (if buf (gnats::vmish-rename-mail-buffer buf)) + (or buffer-name (setq buffer-name "GNATS mail")) + (unwind-protect + (prog1 + (if gnats::emacs-19p + (mail noerase to subject in-reply-to cc replybuffer + (cons '(gnats::vmish-rename-after-send) actions)) + (prog1 + (mail noerase to subject in-reply-to cc replybuffer) + (gnats::vmish-mail-bindings))) + (rename-buffer (gnats::generate-new-buffer-name buffer-name))) + (gnats::vmish-restore-mail-buffer buf)))) + +;;;; Send mail using mh-e. + +(defun gnats::mail-other-window-using-mhe () + "Compose mail other window using mh-e. +While composing the message, use \\[mh-yank-cur-msg] to yank the +original message into it." + (let ((subject (gnats::get-reply-subject))) + (setq mh-show-buffer (current-buffer)) + (mh-find-path) + (mh-send-other-window "" "" subject) + (setq mh-sent-from-folder (current-buffer)) + (setq mh-sent-from-msg 1))) + + +(defun gnats::mail-reply-using-mhe (just-sender) + "Compose reply mail using mh-e. +The command \\[mh-yank-cur-msg] yanks the original message into current buffer. +If optional argument JUST-SENDER is non-nil, send response only to +original submitter of problem report." + ;; First of all, prepare mhe mail buffer. + (let (from cc subject date to reply-to (buffer (current-buffer)) msg-id) + (save-restriction + (setq from (mh-get-field "From:") + subject (gnats::get-reply-subject) + reply-to (or (mh-get-field "Reply-To:") from) + to (or (mh-get-field "To:") + (mh-get-field "Apparently-To:") + "") + cc (mh-get-field "Cc:") + date (mh-get-field "Date:") + msg-id (mh-get-field "Message-Id:") + ) + (setq mh-show-buffer buffer) + (mh-find-path) + (mh-send reply-to (or (and just-sender "") + (if (null cc) to + (concat to ", " cc))) + subject) + (save-excursion + (mh-insert-fields + "In-Reply-To:" (gnats::make-in-reply-to-field from date msg-id))) + (setq mh-sent-from-folder buffer) + (setq mh-sent-from-msg 1) + ))) + + +;;;;--------------------------------------------------------------------------- +;;;; Functions to change specific fields +;;;;--------------------------------------------------------------------------- + +(defun gnats:state-change-from-to () + "Change the value of the `>State:' field and update the audit trail." + (interactive) + (gnats:change-field "State")) + +(defun gnats:responsible-change-from-to () + "Change the value of the `>Responsible:' field and update the audit trail." + (interactive) + (gnats:change-field "Responsible")) + +(defun gnats:category-change-from-to () + "Change the value of the `>Category:' field and the responsible party." + (interactive) + (gnats:change-field "Category")) + +(defun gnats::update-audit-trail (field old new) + (if (gnats::position-on-field "Audit-Trail") + (let (start end) + (gnats::forward-eofield) + (setq start (point-marker)) + (if (eq old t) (setq old "????")) + (if (string= field "Responsible") + (insert (format "\n\n%s-Changed-From-To: %s->%s" field + (gnats::nth-word old) + (gnats::nth-word new))) + (insert (format "\n\n%s-Changed-From-To: %s-%s" field + (gnats::nth-word old) + (gnats::nth-word new)))) + (insert (format "\n%s-Changed-By: %s" field (user-login-name))) + (insert (format "\n%s-Changed-When: %s" field (current-time-string))) + (insert (format "\n%s-Changed-Why:\n" field)) + (save-excursion + (gnats::before-keyword t) + (setq end (point-marker))) + ;; here we record the changes in a assoc list + (setq gnats:::audit-trail (cons (list field + (gnats::nth-word old) + (gnats::nth-word new) + start end) + gnats:::audit-trail))) + (error "Field `>Audit-Trail:' missing."))) + +(defun gnats::category-responsible (category) + (let ((entry (assoc category gnats::categories))) + (if entry + (nth 2 entry) + (signal 'gnats::no-such-category (list category))))) + +(defun gnats::update-responsible (ignore1 ignore2 new) + "Modify the responsible field of the current PR to match the new category." + (and (y-or-n-p "Update the >Responsible: field? ") + (gnats:change-field "Responsible" (gnats::category-responsible new)))) + +;;;;--------------------------------------------------------------------------- + +(defsubst gnats::rw (buf retval) + (or + retval ; call-process is broken under 19.19.2 + (save-excursion (set-buffer buf) (buffer-size)))) + +(defun gnats::handle-results (pr exit-status) + "Handle the results of running pr-edit or npr-edit, giving a signal +if needed." + (cond + ((looking-at "n?pr-edit: cannot create lock file") + (signal 'gnats::cannot-lock nil)) + ((looking-at "n?pr-edit: lock file exists") + (signal 'gnats::locked nil)) + ((or (looking-at "n?pr-edit: no such PR") + (looking-at "n?pr-edit: couldn.t find PR.*")) + (signal 'gnats::no-such-pr nil)) + ((looking-at "n?pr-edit: PR \\(.*\\) locked by \\(.*\\)") + (let* ((msg (gnats::bm 2)) + (pr-path + (buffer-substring-no-properties (match-beginning 1) (match-end 1))) + (pr-name (progn (if (string-match gnats:root pr-path) + (substring pr-path (1+ (match-end 0))) + pr-path))) + (buf (get-buffer pr-name)) + win) + (if buf + ;; If we're already editing the PR, just go to that + ;; buffer and be done with it. + (progn + (if (setq win (get-buffer-window buf)) + (select-window win) + (switch-to-buffer buf)) + (message "Already editing PR %s." pr-name)) + ;; kick it to the next level + (signal 'gnats::locked-pr (list msg))))) + ((looking-at "n?pr-edit: PR is not locked") + (if (not gnats:::force) (signal 'gnats::pr-not-locked nil) + t)) + ((looking-at "n?pr-edit: invalid fields") + (signal 'gnats::invalid-fields nil)) + ((looking-at "n?pr-edit: cannot parse the date") + (signal 'gnats::invalid-date nil)) + ((looking-at "n?pr-edit: lock file .* does not exist")) + (t (signal 'gnats::error + (list (if (eq (point-min) (point-max)) + (format "unknown error (exit status %d)" + exit-status) + (buffer-substring (point-min) (- (point-max) 1)))))))) + +(if gnats::emacs-19p + (require 'env)) +(defun gnats::start-background (pr proctype sentinel &optional outfile filep args) + (let ((buf (get-buffer-create gnats::err-buffer)) + inbuf proc-name proc-action proc-send-buffer) + (save-excursion + (setq inbuf (current-buffer)) + (set-buffer buf) + (erase-buffer) + (make-variable-buffer-local 'gnats:::force) + (setq gnats:::force nil) + (cond ((eq proctype 'check) + (progn + (setq proc-name "check-pr" + proc-action "Checking" + proc-send-buffer t) + (setq args (append (list "--check") args)) + (make-variable-buffer-local 'gnats:::pr-buffer) + (setq gnats:::pr-buffer inbuf) + (make-variable-buffer-local 'gnats:::do-file-pr) + (setq gnats:::do-file-pr filep))) + ((eq proctype 'file) + (setq proc-name "file-pr" + proc-action "Filing" + proc-send-buffer t)) + ((eq proctype 'unlock) + (progn + (setq proc-name "unlock-pr" + proc-action "Unlocking") + (make-variable-buffer-local 'gnats:::current-pr) + (setq gnats:::current-pr pr) + (setq args (append (list "--unlock" pr) args)))) + ((eq proctype 'unlock-force) + (progn + (setq proc-name "unlock-pr" + proc-action "Unlocking" + gnats:::force t) + (make-variable-buffer-local 'gnats:::current-pr) + (setq gnats:::current-pr pr) + (setq args (append (list "--unlock" pr) args)))) + ((eq proctype 'edit) + (progn + (setq proc-name "edit-pr" + proc-action "Fetching") + (make-variable-buffer-local 'gnats:::current-pr) + (setq gnats:::current-pr pr) + (make-variable-buffer-local 'gnats:::newfile) + (setq gnats:::newfile outfile))) + (t + (error "Invalid PROCTYPE for background GNATS process."))) + (let ((process-environment + (if gnats::emacs-19p (copy-sequence process-environment))) + proc) + (setq proc + (apply 'start-process + (concat " *" proc-name "-" (random t)) + buf + (format (if gnats:network-server + "%s/gnats/npr-edit" + "%s/gnats/pr-edit") + gnats:libdir) +; (if gnats:network-server (list "--host" gnats:network-server)) + args + )) + + ;; Only set up the sentinel if they want stuff done in the background. + (if gnats:run-in-background + (progn + (set-process-sentinel proc sentinel) + (message "%s PR %s in background." proc-action pr)) + (message "%s PR %s..." proc-action pr)) + (if proc-send-buffer + (progn + (set-buffer inbuf) + (goto-char (point-min)) + (process-send-region proc (point-min) (point-max)) + (if (and (/= (point-min) (point-max)) + (/= (char-after (- (point-max) 1)) ?\n)) + (process-send-string proc "\n")) + (process-send-eof proc))) + ;; if they don't want it in the background, just sit and twiddle... + (if (not gnats:run-in-background) + (save-excursion + (set-buffer (process-buffer proc)) + (while (memq (process-status proc) '(run open)) + (accept-process-output proc)) + (funcall sentinel proc nil))))))) + +(defun gnats::handle-pr-edit (process event) + (let ((buf (process-buffer process)) + result pr newfile nbuf) + (if (null (buffer-name buf)) ;; deleted buffer + (set-process-buffer process nil) + (save-excursion + (set-buffer buf) + (setq pr gnats:::current-pr) + (setq result (process-exit-status process)) + (and (/= 0 result) + (goto-char (point-min)) + (gnats::handle-results gnats:::current-pr result)) + (setq nbuf (generate-new-buffer + (concat "*edit-pr " gnats:::current-pr "*"))) + (setq newfile gnats:::newfile) + (set-buffer nbuf) + (insert-file-contents newfile) + (make-local-variable 'gnats:::backupname) + (put 'gnats:::backupname 'permanent-local t) + ;; we do this in gnats:gnats-mode for non-network + (if gnats:network-server (setq gnats:::backupname newfile)) + (set-buffer-modified-p nil) + (setq buffer-undo-list nil) ;flush undo list + (gnats:gnats-mode) + (make-variable-buffer-local 'gnats:::current-pr) + (setq gnats:::current-pr pr) + (goto-char gnats:::start-of-PR-fields)) + (message "Fetching PR %s done." pr) + (if gnats:run-in-background + (display-buffer nbuf 'not-this-window) + (switch-to-buffer nbuf))))) + +(defun gnats::pr-edit-background (pr outfile args) + (gnats::start-background pr 'edit 'gnats::handle-pr-edit outfile nil args)) + +(defun gnats::handle-check-pr (process event) + (let ((buf (process-buffer process)) + result pr) + (if (null (buffer-name buf)) ;; deleted buffer + (set-process-buffer process nil) + (save-excursion + (set-buffer buf) + (setq result (process-exit-status process)) + (and (/= 0 result) + (goto-char (point-min)) + (gnats::handle-results gnats:::current-pr result)) + (message "Checked PR %s." gnats:::current-pr) + (if gnats:::do-file-pr + (progn + (set-buffer gnats:::pr-buffer) + (gnats::file-pr-background))))))) + +(defun gnats::check-pr-background (&optional filep) + (gnats::start-background gnats:::current-pr 'check + 'gnats::handle-check-pr nil filep)) + +(defun gnats::finish-filing () + (let (responsible user resp-change state-change buf) + (if gnats:network-server (setq gnats:::pr-locked nil)) + (setq buf (current-buffer)) + (set-buffer-modified-p nil) + (setq responsible (gnats::field-contents "Responsible") + user (user-login-name) + resp-change (cdr (assoc "Responsible" gnats:::audit-trail)) + state-change (cdr (assoc "State" gnats:::audit-trail))) + (if (or state-change + resp-change + (not (equal user responsible))) + (gnats::mail-PR-changed user responsible + resp-change state-change + (gnats::get-header "X-GNATS-Notify"))) + (gnats:unlock-buffer buf))) + +(defun gnats::handle-file-pr (process event) + (let ((buf (process-buffer process)) + result pr prbuf) + (if (null (buffer-name buf)) ;; deleted buffer + (set-process-buffer process nil) + (save-excursion + (set-buffer buf) + (setq result (process-exit-status process)) + (and (/= 0 result) + (goto-char (point-min)) + (gnats::handle-results gnats:::current-pr result)) + (message "Filed PR %s." gnats:::current-pr) + (set-buffer gnats:::pr-buffer) + (gnats::finish-filing))))) + +(defun gnats::file-pr-background () + (gnats::start-background gnats:::current-pr 'file 'gnats::handle-file-pr)) + +(defun gnats::lock (pr &optional outfile) + (let ((lockl (list "--lock" + (format "%s@%s" (user-login-name) (system-name)) + "-p" + (if (fboundp 'emacs-pid) + (concat "emacs pid " (int-to-string (emacs-pid))) + "emacs18") + pr))) + (if gnats:network-server + (setq lockl (append lockl (list "-o" outfile "--get-lists" + "--host" gnats:network-server)))) + (gnats::pr-edit-background pr outfile lockl))) + +(fset 'unlock-pr 'gnats:unlock-pr) +(fset 'gnats-unlock 'gnats:unlock-pr) ;backward compatibility +(defun gnats::handle-unlock-pr (process event) + (let ((buf (process-buffer process)) + result pr newfile nbuf) + (if (null (buffer-name buf)) ;; deleted buffer + (set-process-buffer process nil) + (save-excursion + (set-buffer buf) + (setq pr gnats:::current-pr) + (setq result (process-exit-status process)) + (and (/= 0 result) + (goto-char (point-min)) + (gnats::handle-results gnats:::current-pr result)) + (message "PR %s unlocked." gnats:::current-pr))))) + +(defun gnats:unlock-pr-force (pr) + (gnats::start-background pr 'unlock-force 'gnats::handle-unlock-pr)) + +(defun gnats:unlock-pr (pr) + (interactive "sPR number: ") + (gnats::start-background pr 'unlock 'gnats::handle-unlock-pr)) + +(defsubst gnats::buffer-major-mode (buffer) + (save-excursion (set-buffer buffer) major-mode)) + +(defun gnats::unlock-all-buffers () + (save-excursion + (mapcar + (function + (lambda (buffer) + (let ((gnats:run-in-background nil)) + (if (and (eq (gnats::buffer-major-mode buffer) gnats::mode-name)) + (progn (set-buffer buffer) + (gnats:unlock-buffer-force buffer)))))) + (buffer-list)))) + +(if gnats::emacs-19p + ;; Emacs 19 has kill-buffer-hook, v18 doesn't. + (defun gnats::kill-buffer-hook () + "Unlock a GNATS buffer that is being killed." + (gnats:unlock-buffer nil)) + (defun gnats:kill-buffer (buf) + "Safely kill a GNATS buffer." + (interactive "bKill buffer: ") + (if (equal buf (buffer-name)) + (gnats:unlock-buffer (get-buffer buf))) + (kill-buffer buf))) + +(defun gnats:unlock-buffer-force (&optional buf) + "Force a buffer to be unlocked, even if it isn't." + (interactive) + (if (null buf) + (setq buf (current-buffer)) + (set-buffer buf)) + (gnats:unlock-buffer buf t)) + +(defun gnats::delete-file (filename) + (if (file-readable-p filename) (delete-file filename))) + +(defun gnats:unlock-buffer (&optional buf force) + "Safely take a GNATS buffer out of gnats-mode." + (interactive) + (save-excursion + (if (null buf) + (setq buf (current-buffer)) + (set-buffer buf)) + (cond ((or force + (not (buffer-modified-p buf)) + (not gnats:::pr-locked) + (y-or-n-p "Buffer modified; still unlock? ")) + (if gnats:::pr-locked + (gnats:unlock-pr-force gnats:::buffer-pr)) + (if gnats:::pr-errors + (kill-buffer gnats:::pr-errors)) + (if gnats:::backupname + (progn + (gnats::delete-file gnats:::backupname) + (if gnats:network-server + (progn + (gnats::delete-file (concat gnats:::backupname ".cat")) + (gnats::delete-file (concat gnats:::backupname ".res")) + (gnats::delete-file (concat gnats:::backupname ".sub")))))) + (save-excursion + (set-buffer buf) + (let ((pr gnats:::buffer-pr)) + (kill-all-local-variables) + (text-mode) + (make-local-variable 'gnats:::buffer-pr) + (setq gnats:::buffer-pr pr) + (use-local-map (copy-keymap (current-local-map))) + (local-set-key + "e" (function (lambda () (interactive) + (gnats:edit-pr gnats:::buffer-pr)))) + (set-visited-file-name nil) + (setq buffer-read-only t) + ;; When GNATS:KEEP-EDITED-BUFFERS is nil, we always put the + ;; most recent PR in the *edited-pr* buffer. + (or gnats:keep-edited-buffers + (let ((old-buf (get-buffer (concat "*edited-pr*")))) + (cond (old-buf + (set-buffer old-buf) + (set-buffer-modified-p nil) + (kill-buffer old-buf))) + (set-buffer buf) + (rename-buffer (concat "*edited-pr*")))))) + (and gnats:bury-edited-prs + (if (get-buffer-window buf) + (let ((win (selected-window))) + (select-window (get-buffer-window buf)) + (bury-buffer) + (select-window win)) + (bury-buffer buf)))) + (t (error "PR unlock aborted."))))) + +(defun gnats::delete-backups (filename) + (let ((l (file-name-all-completions + (concat (file-name-nondirectory filename) ".~") + (file-name-directory filename))) + (dir (file-name-directory filename))) + (while l + (delete-file (concat dir (car l))) + (setq l (cdr l))))) + +(defun gnats::reset-variables () + (setq gnats::submitters nil + gnats::responsibles nil + gnats::categories nil)) + +(defun gnats::set-responsibles (&optional arg) + (or (and (null arg) gnats::responsibles) + (setq gnats::responsibles + (gnats::get-list-from-file + (if gnats:network-server + "res" + "responsible") "responsible"))) + 'gnats::try-responsible-completion) + +(defun gnats::try-responsible-completion (string predicate do-list) + (let (entry) + (and (not (assoc string gnats::responsibles)) + (setq entry (gnats::real-pr-addr string)) + (gnats::push entry gnats::responsibles))) + (let* ((completion-ignore-case t)) + (if do-list + (all-completions string gnats::responsibles predicate) + (try-completion string gnats::responsibles predicate)))) + +(defun gnats::set-categories (&optional arg) + (or (and (null arg) gnats::categories) + (setq gnats::categories + (gnats::get-list-from-file + (if gnats:network-server + "cat" + "categories") "categories")))) + +(defun gnats::set-submitters (&optional arg) + (or (and (null arg) gnats::submitters) + (setq gnats::submitters + (gnats::get-list-from-file + (if gnats:network-server + "sub" + "submitters") "submitters")))) + +(defun gnats::get-list (buffer) + (let (result) + (save-excursion + (set-buffer buffer) + (goto-char (point-min)) + (while (re-search-forward "^[^#:]+" nil t) + (gnats::push (list (gnats::bm 0)) result))) + (reverse result))) + +(defun gnats::parse-line () + (let ((end (progn (end-of-line) (point))) + (p (match-beginning 0)) + l) + (goto-char p) + (while (search-forward ":" end 'move) + (gnats::push (buffer-substring p (match-beginning 0)) l) + (skip-chars-forward " " end) + (setq p (point))) + (gnats::push (buffer-substring p end) l) + (reverse l))) + +(defun gnats::get-alist (buffer) + (let (result) + (save-excursion + (set-buffer buffer) + (goto-char (point-min)) + (while (re-search-forward "^[^#]" nil t) + (gnats::push (gnats::parse-line) result))) + (reverse result))) + +(defun gnats::get-list-from-file (filename desc) + (let ((buf nil) + (result nil)) + (message "Parsing the %s file..." desc) + (save-excursion + (let ((bn gnats:::backupname)) + (setq buf (get-buffer-create " *gnats-grok*")) + (set-buffer buf) + (setq buffer-read-only nil) + (erase-buffer) + (insert-file-contents + (if gnats:network-server + (concat bn "." filename) + (format "%s/gnats-adm/%s" gnats:root filename))) + (setq result (gnats::get-alist buf)) + (kill-buffer buf)) + (message "Parsing the %s file...done." desc) + result))) + +(defun gnats::get-pr-category (number) + "Return the category for the problem report NUMBER." + (let ((buf nil) + (result nil)) + (save-excursion + (setq buf (get-buffer-create " *gnats-index*")) + (set-buffer buf) + (setq buffer-read-only nil) + (erase-buffer) + (insert-file-contents (format "%s/gnats-adm/index" gnats:root)) + (goto-char (point-min)) + (setq result + (catch 'res + (while (search-forward (format "/%s:" number) nil t) + (beginning-of-line) + (if (looking-at (format "\\([^/]+\\)/%s:" number)) + (throw 'res (gnats::bm 1)) + (end-of-line))) + nil)) + (kill-buffer buf)) + (or result (signal 'gnats::no-such-pr (list number))))) + +(defsubst gnats::has-slash (string) + (memq t (mapcar (function (lambda (char) (= char ?/))) string))) + +(or (boundp 'view-hook) (setq view-hook nil)) + +;;;###autoload +(fset 'view-pr 'gnats:view-pr) +;;;###autoload +(defun gnats:view-pr (&optional id) + "Visit the problem report named by the string ID. While viewing, press +'e' to edit the currently viewed PR." + (interactive "sPR number: ") + (let (pr category temp-name buffer) + (if (string= id "") + (message "view-pr: must specify a PR") + (if (or (gnats::has-slash id) gnats:network-server) + (setq pr id) + (and (setq category (gnats::get-pr-category id)) + (setq pr (format "%s/%s" category id)))) + (let ((view-hook (default-value 'view-hook)) + buf func) + (if (and pr + (or gnats:network-server + (setq buf (get-buffer pr)) + (file-exists-p (format "%s/%s" gnats:root pr)))) + (if buf + (save-excursion + (set-buffer buf) + (goto-char (point-min)) + (view-buffer buf)) + (setq func + (function + (lambda () + (and gnats::emacs-19p (rename-buffer pr)) + (setq mode-line-buffer-identification + (format "Viewing %s" pr)) + (make-local-variable 'gnats:::buffer-pr) + (setq gnats:::buffer-pr pr) + (use-local-map (copy-keymap (current-local-map))) + (local-set-key + "e" + (function (lambda () (interactive) + (gnats:edit-pr gnats:::buffer-pr))))))) + (if (fboundp 'add-hook) + (add-hook 'view-hook func) + (setq view-hook func)) + (if gnats:network-server + (gnats:net-view-pr id buf) + (view-file (format "%s/%s" gnats:root pr)))) + (signal 'gnats::no-such-pr (list id))))))) + +(defun gnats:net-view-pr (id buf) + "Use the network query to view problem report ID." + (require 'view) + (let ((result nil) + (curr (current-buffer))) + (unwind-protect + (if (not buf) + (progn + ;; XXX fix this to include the category + (setq buf (get-buffer-create (concat "*view-pr " id "*"))) + (setq buffer-read-only nil))) + (let ((command (append (list 'funcall) + (list ''call-process) + (list gnats:::nquery-pr nil buf nil) + (list id "--full" "--host" + gnats:network-server)))) + (save-excursion + (set-buffer buf) + (erase-buffer)) + (let ((default-directory (gnats::find-safe-default-directory))) + (setq result (gnats::rw buf (eval command)))) + (save-excursion + (set-buffer buf) + (and (/= 0 result) + (goto-char (point-min)) + (cond + ((or (looking-at (concat gnats:::query-regexp " no PRs matched")) + (looking-at (concat gnats:::query-regexp " couldn.t find PR.*"))) + (signal 'gnats::no-such-pr nil)) + (t (signal 'gnats::error + (list (buffer-substring (point-min) + (- (point-max) 1))))) + )))) + (switch-to-buffer buf) + (if (fboundp 'view-mode-enter) + (view-mode-enter curr 'kill-buffer) + (view-mode curr 'kill-buffer)) + (set-buffer-modified-p nil) + (make-local-variable 'gnats:::buffer-pr) + (if (not (gnats::has-slash id)) (gnats::rename-buffer)) + (setq buffer-read-only t) + (setq buffer-undo-list nil) ;flush undo list + (goto-char (point-min))) + (zerop result))) + +(fset 'change-gnats 'gnats:change-type) +(fset 'gnats-change-type 'gnats:change-type) +(defun gnats:change-type (type) + "Change the GNATS database type in use." + (interactive + (list + (progn + (if (not gnats:::types) + (error "Value of gnats:::types has to be non-nil.")) + (let* ((completion-ignore-case t)) + (completing-read "Use GNATS database type: " gnats:::types nil t))))) + (let ((newlist (car (cdr (assoc type gnats:::types))))) + (setq gnats:root (car newlist) + gnats:libdir (car (cdr newlist)) + gnats:::query-pr (car (cdr (cdr newlist))) + gnats:::nquery-pr (car (cdr (cdr (cdr newlist)))) + gnats:::query-regexp (car (cdr (cdr (cdr (cdr newlist))))) + ) + (gnats::reset-variables))) + +(defun gnats::find-pr-buffer (pr) +"*Find the buffer currently editing PR, returning the buffer or nil." + (if (gnats::has-slash pr) + ;; return the buffer if it exists + (get-buffer pr) + (let (buflist buf + (name (concat "/" pr "$"))) + (setq buflist + (delq nil + (mapcar + (function (lambda (buf) + (if (string-match name (buffer-name buf)) + buf))) + (buffer-list)))) + ;; If we found one---and only one---then sanity-check some things + ;; about it before we try to use it. + (if (eq (length buflist) 1) + (progn + (setq buf (car buflist)) + (save-excursion + (set-buffer buf) + ;; We make sure that we have a value for the PR, it's in + ;; the right mode, and that the buffer's writable. If so, + ;; we'll return the buffer, otherwise the result of the if + ;; gets kicked back up to return nil. + (if (and gnats:::buffer-pr + (eq major-mode 'gnats:gnats-mode) + (eq buffer-read-only nil)) + buf))))))) + +;;;###autoload +(fset 'edit-pr 'gnats:edit-pr) +;;;###autoload +(defun gnats:edit-pr (&optional id) + "Edit the problem report named by the string ID." + (interactive "sPR number: ") + (if (string= id "") + (message "edit-pr: must specify a PR to edit") + (let (pr category newfile + (buf (gnats::find-pr-buffer id))) + (if buf + (progn + (switch-to-buffer buf) + (message "Already editing PR %s." id)) + (progn + (if (or (gnats::has-slash id) gnats:network-server) + (setq pr id) + (and (setq category (gnats::get-pr-category id)) + (setq pr (format "%s/%s" category id))))) + (if (and pr (or gnats:network-server + (file-exists-p (format "%s/%s" gnats:root pr)))) + (progn + (setq newfile (if gnats:network-server + (gnats::make-temp-name) + (format "%s/%s" gnats:root pr))) + (gnats::lock pr newfile)) + (signal 'gnats::no-such-pr (list id))))))) + +(defvar gnats:query-pr-default-options nil + "*Default options to pass to query-pr.") +(defsubst gnats::query-pr-default-options () + (or gnats:query-pr-default-options + (if (not gnats:network-server) + (concat " --directory=" gnats:root " --print-path ") + ""))) + +;;;###autoload +(fset 'query-pr 'gnats:query-pr) +;;;###autoload +(defun gnats:query-pr (options) + "Run query-pr, with user-specified args, and collect output in a buffer. +While query-pr runs asynchronously, you can use the \\[next-error] command +to find the text that the hits refer to." + (interactive + (list (apply + 'read-from-minibuffer "Run query-pr (with args): " + (if gnats::emacs-19p + (list (cons (gnats::query-pr-default-options) 1) + nil nil 'gnats::query-pr-history) + (list (gnats::query-pr-default-options) nil nil))))) + (require 'compile) + (compile-internal (concat + (if gnats:network-server + (format (concat gnats:::nquery-pr " --host %s ") + gnats:network-server) + (concat gnats:::query-pr " ")) + options) + "No more query-pr hits" (concat gnats:::query-pr " "))) + +(defun gnats::tr (string from to) + (let ((s (copy-sequence string)) + (len (length string))) + (while (>= (setq len (1- len)) 0) + (if (eq (aref s len) (string-to-char from)) + (aset s len (string-to-char to)))) + s)) + +;; Redefine so that buffers with, say, g++/1234 embedded in them can be +;; autosaved. This was mostly copied from the Emacs 19.19 version. +(defun gnats::make-auto-save-file-name () + "Return file name to use for auto-saves of current buffer. +Does not consider `auto-save-visited-file-name' as that variable is checked +before calling this function. You can redefine this for customization. +See also `auto-save-file-name-p'." + ; Since the user may have his own make-auto-save-file-name, try not to + ; use our custom one unless we have to. + (if (or (eq major-mode gnats::mode-name) + ; Heuristic for noticing a mail buffer based on a PR + (string-match " PR .*/" (buffer-name))) + (if buffer-file-name + (concat (file-name-directory buffer-file-name) + "#" + (file-name-nondirectory buffer-file-name) + "#") + ;; For non-file bfr, use bfr name and Emacs pid. + (expand-file-name (format "#%s#%s#" + (gnats::tr (buffer-name) "/" ":") + (make-temp-name "")))) + (gnats::real-make-auto-save-file-name))) + +(if (not (fboundp 'gnats::real-make-auto-save-file-name)) + (progn (fset 'gnats::real-make-auto-save-file-name + (symbol-function 'make-auto-save-file-name)) + (fset 'make-auto-save-file-name 'gnats::make-auto-save-file-name))) + +(defun gnats::make-temp-name () + (make-temp-name + (concat (expand-file-name (file-name-as-directory gnats::tmpdir)) "gnats"))) + +;; Below this is the GNATS summary mode I've written. Not quite 100% +;; integrated yet. + +;; Temporary variables which are made buffer-local, but which the byte +;; compiler complaints about if the defvars aren't here. +(defvar gnats:::PRs nil + "List of problem reports to be summarized. This variable is buffer local.") +(make-variable-buffer-local 'gnats:::PRs) +(defvar gnats::options nil + "Options used for nquery-pr in the current GNATS summary buffer. +This variable is buffer local.") +(make-variable-buffer-local 'gnats::options) + +;; Note: "release" stays out of this list. The "release" field is +;; unrestricted; the customer could put any old junk in there, and +;; often does. +(defvar gnats:::limited-fields '(category confidential severity priority responsible state class customer-id) + "PR fields for which the possible values are limited in range.") + +(defvar gnats::summary-sort-function nil + "Holds a function used to filter and sort PRs before displaying a report. +This filtering does not affect the stored PR information, so an invocation +of gnats:summary-redisplay after changing this variable will do the right thing.") + +(defun gnats:::prompt-for-pr-number (default) + (let ((val (read-input (if default + (format "PR number (default %d): " default) + "PR number: ")))) + (if (and default (string= val "")) + default + (setq val (string-to-number val)) + (if (and (integerp val) + (> val 0)) + val + (error "PR number must be a positive integer."))))) + +(defun gnats:summary-edit (num) + "Edit the PR referenced by the current text, or get a PR number from user. +If a numeric prefix is given, it is used as the PR number. +If a non-numeric prefix is given, or the text at (point) doesn't have the +gnats::pr-number property, the user is prompted for a PR number." + (interactive (list + (let ((x (get-text-property (point) 'gnats::pr-number))) + (cond ((numberp current-prefix-arg) current-prefix-arg) + (current-prefix-arg (gnats:::prompt-for-pr-number x)) + (x x) + (t (gnats:::prompt-for-pr-number nil)))))) + (message "Editing PR %d..." num) + (gnats:edit-pr (number-to-string num))) + +(defun gnats:summary-view (num) + "View the PR referenced by the current text, or get a PR number from user. +If a numeric prefix is given, it is used as the PR number. +If a non-numeric prefix is given, or the text at (point) doesn't have the +gnats::pr-number property, the user is prompted for a PR number." + (interactive (list + (let ((x (get-text-property (point) 'gnats::pr-number))) + (cond ((numberp current-prefix-arg) current-prefix-arg) + (current-prefix-arg (gnats:::prompt-for-pr-number x)) + (x x) + (t (gnats:::prompt-for-pr-number nil)))))) + (message "Viewing PR %d..." num) + (gnats:view-pr (number-to-string num))) + +(defun gnats:summary-quit nil + "Quit GNATS summary mode." + (interactive) + (kill-buffer nil)) + +(defun gnats:summary-revert nil + "Fetch PR data from server and rebuild the summary." + (interactive) + (gnats:summ-pr gnats::options)) + +;; Fetch field value from a PR. +(defsubst gnats:::fieldval (pr field) + (let ((x (assq field pr))) + (if x (cdr x) nil))) + +;; Taken from gnus-parse-simple-format in (ding)Gnus 0.88. +;; Extended to handle width-1 fields more efficiently. +;; Extended to permit "*" to flag truncation. +;; Modified to call kqpr* functions instead of gnus-*. +(defun gnats:::parse-summary-format (format spec-alist) + ;; This function parses the FORMAT string with the help of the + ;; SPEC-ALIST and returns a list that can be eval'ed to return the + ;; string. The list will consist of the symbol `format', a format + ;; specification string, and a list of forms depending on the + ;; SPEC-ALIST. + (let ((max-width 0) + spec flist fstring b newspec max-width elem beg trunc-noisy) + (save-excursion + (set-buffer (get-buffer-create " *qpr work*")) + (erase-buffer) + (insert format) + (goto-char (point-min)) + (while (re-search-forward "%-?[0-9]*\\([,*]-?[0-9]*\\)*\\(.\\)\\(.\\)?" nil t) + (setq spec (string-to-char (buffer-substring (match-beginning 2) + (match-end 2)))) + ;; First check if there are any specs that look anything like + ;; "%12,12A", ie. with a "max width specification". These have + ;; to be treated specially. + (if (setq beg (match-beginning 1)) + (setq max-width + (string-to-int + (buffer-substring (1+ (match-beginning 1)) (match-end 1))) + trunc-noisy (= ?* (char-after beg))) + (setq max-width 0) + (setq beg (match-beginning 2)) + (setq trunc-noisy nil)) + ;; Find the specification from `spec-alist'. + (if (not (setq elem (cdr (assq spec spec-alist)))) + (setq elem '("*" ?s))) + ;; Treat user defined format specifiers specially + (and (eq (car elem) 'user-defined) + (setq elem + (list + (list (intern (concat "gnats:user-format-function-" + (buffer-substring + (match-beginning 3) + (match-end 3)))) + 'pr) + ?s)) + (delete-region (match-beginning 3) (match-end 3))) + (if (not (zerop max-width)) + (if (and (= max-width 1) + (memq (car (cdr elem)) '(?c ?s))) + (let ((el (car elem))) + (cond ((= (car (cdr elem)) ?c) + (setq newspec ?c) + (setq flist (cons el flist))) + ((= (car (cdr elem)) ?s) + (setq newspec ?c) + (setq flist (cons (list 'string-to-char el) flist))) + (t + (error "eep!")))) + (let ((el (car elem))) + (cond ((= (car (cdr elem)) ?c) + (setq el (list 'char-to-string el))) + ((= (car (cdr elem)) ?d) + (numberp el) (setq el (list 'int-to-string el)))) + (setq flist (cons (list 'gnats:::format-max-width + el max-width trunc-noisy) + flist)) + (setq newspec ?s))) + (setq flist (cons (car elem) flist)) + (setq newspec (car (cdr elem)))) + ;; Remove the old specification (and possibly a ",12" string). + (delete-region beg (match-end 2)) + ;; Insert the new specification. + (goto-char beg) + (insert newspec)) + (setq fstring (buffer-substring 1 (point-max))) + (kill-buffer nil)) + (cons 'format (cons fstring (nreverse flist))))) + +;; Try to keep this list similar to the command-line options for nquery-pr, +;; just to avoid confusing people. If there are differences, it won't break +;; anything. +(defvar gnats::summary-format-alist + (list (list ?r '(symbol-name (gnats:::fieldval pr 'responsible)) ?s) + (list ?c '(symbol-name (gnats:::fieldval pr 'category)) ?s) + (list ?C '(symbol-name (gnats:::fieldval pr 'confidential)) ?s) + (list ?e '(symbol-name (gnats:::fieldval pr 'severity)) ?s) + (list ?O '(gnats:::fieldval pr 'originator) ?s) + (list ?p '(symbol-name (gnats:::fieldval pr 'priority)) ?s) + (list ?L '(symbol-name (gnats:::fieldval pr 'class)) ?s) + (list ?S '(symbol-name (gnats:::fieldval pr 'customer-id)) ?s) ; == submitter + (list ?s '(symbol-name (gnats:::fieldval pr 'state)) ?s) + + (list ?n '(gnats:::fieldval pr 'number) ?d) + (list ?R '(gnats:::fieldval pr 'release) ?s) + (list ?j '(gnats:::fieldval pr 'synopsis) ?s) + (list ?y '(gnats:::fieldval pr 'synopsis) ?s) + (list ?u 'user-defined ?s) + )) + +(defun gnats:::format-max-width (str len noisy) + (if (> (length str) (if gnats::emacs-19p (abs len) len)) + (if noisy + (if (< len 0) + (concat "*" (substring str (1+ len))) + (concat (substring str 0 (- len 1)) "*")) + (if (< len 0) + (substring str len) + (substring str 0 len))) + str)) + +;; Redisplay the summary in the current buffer. +(defvar gnats::format-string + "%5n %-4,4c %,1e%,1p %-8,8r %,2s %-10*10S %-10*-10R %j\n" + "Format string for PR summary text. + +If you've used format strings in (ding)Gnus, this will be familiar. + +Most text is copied straight through verbatim. Use \"%\" to indicate a +non-fixed field. + +It can be followed by a number, indicating minimum width, a separator character +(\",\" or \"*\"), and another number, indicating maximum width. These fields +are optional, except that the separator must be present if the maximum width is +specified. Whitespace padding will be on the left unless the first number is +negative. Truncation of the field will be done on the right, unless the second +number is negative. If the separator character is \"*\", a \"*\" will be used +to indicate that truncation has been done; otherwise, it will be done silently. + +After the \"%\" and optional width parameters, a letter is expected. Most of +the letters are chosen to match the command-line options of `nquery-pr'. + +%r \"Responsible\" field. +%c \"Category\" field. +%C \"Confidential\" field. +%e \"Severity\" field. +%O \"Originator\" field. +%p \"Priority\" field. +%L \"Class\" field. +%S \"Customer-id\" (\"submitter\") field. +%s \"State\" field. +%n \"Number\" field. +%R \"Release\" field. +%j, %y \"Synopsis\" field. (\"j\" as in \"subJect\") +%u Special: The next character is examined, and the function + gnats:user-format-function-<char> is invoked. One argument, the list + of (FIELD . VALUE) pairs, is passed. + +Any newlines you wish to have used must be included in this string; no +additional ones will be provided. + +If the value is not a string, it is assumed to be a function which can +be funcalled to return a format string, to be interpreted as above.") + +(defun gnats:summary-redisplay nil + "Redisplay summary of stored GNATS data. +This is useful if you change your filtering criteria or format string but +do not wish to update the GNATS data by contacting the server." + (interactive) + (let (prs + (buffer-read-only nil) + format-form fmt) + ;; Do this early, so if we're in the wrong buffer we blow up without + ;; trashing the user's data. + (setq prs (if gnats::summary-sort-function + (funcall gnats::summary-sort-function + (apply 'list gnats:::PRs)) + gnats:::PRs)) + ;; No wrapping -- ick! + (if gnats::emacs-19p + (buffer-disable-undo) + (buffer-flush-undo (current-buffer))) + (erase-buffer) + (setq fmt (if (stringp gnats::format-string) + gnats::format-string + (funcall gnats::format-string))) + (setq format-form (gnats:::parse-summary-format fmt + gnats::summary-format-alist)) + (mapcar (function + (lambda (pr) + (let ((start (point))) + (insert (eval format-form)) + ;; Magic. + (put-text-property start (point) 'gnats::pr-number + (gnats:::fieldval pr 'number)) + ))) + prs) + (goto-char (point-min)) + (buffer-enable-undo) + (set-buffer-modified-p nil) + )) + +(defvar gnats-summary-mode-map + (let ((map (copy-keymap text-mode-map))) + (if gnats::emacs-19p (suppress-keymap map)) + ;; basic mode stuff + (define-key map "g" 'gnats:summary-revert) + (define-key map "q" 'gnats:summary-quit) + (define-key map "r" 'gnats:summary-redisplay) + ;; do stuff to PRs + (define-key map "e" 'gnats:summary-edit) + (define-key map "v" 'gnats:summary-view) + ;; navigation + (define-key map "n" 'next-line) + (define-key map "p" 'previous-line) + map) + "Keymap for GNATS summary mode.") + +(defun gnats-summary-mode nil + "Major mode for problem report summary. + +You can use \\[gnats:summary-view] to view the PR specified by the +current line, or \\[gnats:summary-edit] to edit it. Typing +\\[gnats:summary-revert] will update the PR list. + +Special commands: +\\{gnats-summary-mode-map} + +Entering GNATS summary mode will invoke any hooks listed in the variable +gnats-summary-mode-hook. It will also use text-mode-hook, since the summary +mode is built on top of text mode." + (interactive) + (text-mode) +; (make-local-variable 'gnats:::PRs) +; (make-local-variable 'gnats::options) + (setq buffer-read-only t) + (setq truncate-lines t) + (setq major-mode 'gnats-summary-mode) + (setq mode-name "GNATS Summary") + (use-local-map gnats-summary-mode-map) + (run-hooks 'gnats-summary-mode-hook) + ) + +;;;###autoload +(fset 'summ-pr 'gnats:summ-pr) +;;;###autoload +(defun gnats:summ-pr (options) + "Run query-pr, with user-specified args, and display a pretty summary. +Well, display a summary, at least." + (interactive + (list + (if (not gnats::emacs-19p) + (error "GNATS summary mode will only work with emacs 19.") + (apply + 'read-from-minibuffer "Run query-pr (with args): " + (if gnats::emacs-19p + (list (cons (gnats::query-pr-default-options) 1) + nil nil 'gnats::query-pr-history) + (list (gnats::query-pr-default-options) nil nil)))))) + (let ((buf (get-buffer-create "*gnats-summ-pr-temp*")) + (prs nil) + pr fieldname value p) +; (save-excursion + (set-buffer buf) + (if gnats::emacs-19p + (buffer-disable-undo) + (buffer-flush-undo buf)) + (erase-buffer) + ;; calling nquery-pr directly would be better, but I'd need a "split" + ;; function of some sort to break apart the options string. + (message "Fetching GNATS data...") + (call-process "sh" nil buf nil "-c" + (concat + (if gnats:network-server + (format (concat gnats:::nquery-pr " --host %s ") + gnats:network-server) + (concat gnats:::query-pr " ")) + options)) + ;; um, okay, how to i check for errors? + (goto-char (point-min)) + (setq pr nil) + (while (looking-at "ld.so: warning:") + (forward-line 1)) + (while (not (eobp)) + (while (looking-at ">\\([a-zA-Z-]+\\):") + (setq fieldname (intern + (downcase + (buffer-substring (match-beginning 1) + (match-end 1))))) + (goto-char (match-end 0)) + (while (looking-at "[ \t]") + (forward-char 1)) + (setq p (point)) + (setq value (buffer-substring p (progn (end-of-line) (point)))) + (cond ((eq fieldname 'number) + (setq value (string-to-number value))) + ((memq fieldname gnats:::limited-fields) + (setq value (intern value)))) + (setq pr (cons (cons fieldname value) pr)) + (forward-char 1)) + (if (looking-at "\n") + (progn + (setq prs (cons (nreverse pr) prs) + pr nil) + (forward-char 1))) + ;; could be the result of --print-path + (if (looking-at "/.*:0:$") + (next-line 1)) + (if (looking-at gnats:::query-regexp) + ;; error message + (progn + (goto-char (match-end 0)) + (while (looking-at "[ \t]") + (forward-char 1)) + (setq p (point)) + (end-of-line) + (setq value (buffer-substring p (point))) + (error "Database query failed: %s" value))) + ) + (if pr + (setq prs (cons (nreverse pr) prs))) + (setq prs (nreverse prs)) + + ;; okay, now display it + (pop-to-buffer (get-buffer-create "*gnats:summ-pr*")) + (gnats-summary-mode) + (setq gnats:::PRs prs) + (setq gnats::options options) + (gnats:summary-redisplay) + (message "Fetching GNATS data...done.") +; ) + (kill-buffer buf) + )) + +;;;; end of gnats.el