Mercurial > hg > xemacs-beta
diff lisp/gnats/send-pr.el @ 110:fe104dbd9147 r20-1b7
Import from CVS: tag r20-1b7
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:19:45 +0200 |
parents | |
children | 48d667d6f17f |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/gnats/send-pr.el Mon Aug 13 09:19:45 2007 +0200 @@ -0,0 +1,804 @@ +;;;; -*-emacs-lisp-*- +;;;;--------------------------------------------------------------------------- +;;;; EMACS interface for send-pr (by Heinz G. Seidl, hgs@cygnus.com) +;;;; Slightly hacked by Brendan Kehoe (brendan@cygnus.com). +;;;; +;;;; This file is part of the Problem Report Management System (GNATS) +;;;; Copyright 1992, 1993 Cygnus Support +;;;; +;;;; This program 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 of the License, or (at your option) any later version. +;;;; +;;;; This program 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 Library General Public +;;;; License along with this program; if not, write to the Free +;;;; Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +;;;; +;;;;--------------------------------------------------------------------------- +;;;; +;;;; This file contains the EMACS interface to the Problem Report Management +;;;; System (GNATS): +;;;; +;;;; - The `send-pr' command and the `send-pr-mode' for sending +;;;; Problem Reports (PRs). +;;;; +;;;; For more information about how to send a PR see send-pr(1). +;;;; +;;;;--------------------------------------------------------------------------- +;;;; +;;;; Configuration: the symbol `DEFAULT-RELEASE' can be replaced by +;;;; site/release specific strings during the configuration/installation +;;;; process. +;;;; +;;;; Install this file in your EMACS library directory. +;;;; +;;;;--------------------------------------------------------------------------- + +(provide 'send-pr) + +;;;;--------------------------------------------------------------------------- +;;;; Customization: put the following forms into your default.el file +;;;; (or into your .emacs) +;;;;--------------------------------------------------------------------------- + +;(autoload 'send-pr-mode "send-pr" +; "Major mode for sending problem reports." t) + +;(autoload 'send-pr "send-pr" +; "Command to create and send a problem report." t) + +;;;;--------------------------------------------------------------------------- +;;;; End of Customization Section +;;;;--------------------------------------------------------------------------- + +(autoload 'server-buffer-done "server") +(defvar server-buffer-clients nil) +(defvar mail-self-blind nil) +(defvar mail-default-reply-to nil) + +(defconst send-pr::version "3.101") + +(defvar gnats:root "" + "*The top of the tree containing the GNATS database.") + +;;;;--------------------------------------------------------------------------- +;;;; hooks +;;;;--------------------------------------------------------------------------- + +(defvar text-mode-hook nil) ; we define it here in case it's not defined +(defvar send-pr-mode-hook text-mode-hook "Called when send-pr is invoked.") + +;;;;--------------------------------------------------------------------------- +;;;; Domains and default values for (some of) the Problem Report fields; +;;;; constants and definitions. +;;;;--------------------------------------------------------------------------- + +(defconst gnats::emacs-19p + (not (or (and (boundp 'epoch::version) epoch::version) + (string-lessp emacs-version "19"))) + "Is this emacs v19?") + +;;; This has to be here rather than at the bottom of this file with +;;; the other utility functions because it is used by +;;; gnats::get-config, which is called when send-pr.el is being +;;; loaded (see the "defconst" below), before the whole file has been +;;; loaded. + +(defun gnats::find-safe-default-directory (&optional buffer) +"If the directory referred to by `default-directory' for the current +buffer (or for optional argument BUFFER) does not exist, set it to the home +directory of the current user if that exists, or to `/'. + +Returns the final value of default-directory in the buffer." + (let ((homedir (expand-file-name "~/"))) + (save-excursion + (if buffer (set-buffer buffer)) + (if (not (file-exists-p default-directory)) + (if (file-exists-p homedir) + (setq default-directory homedir) + (setq default-directory "/"))) + default-directory))) + +;;; These may be changed during configuration/installation or by the individual +;;; user in his/her .emacs file. +;;; +(defun gnats::get-config (var) + (let ((shell-file-name "/bin/sh") + (buf (generate-new-buffer " *GNATS config*")) + ret) + (save-excursion + (set-buffer buf) + (shell-command-on-region + (point-min) (point-max) + (concat ". " gnats:root "/gnats-adm/config; echo $" var ) t) + (goto-char (point-min)) + ; We have to use get-buffer, since shell-command-on-region will wipe + ; out the buffer if there's no output from the command. + (if (or (not (get-buffer "*Shell Command Output*")) + (looking-at "/bin/sh:\\|\.:\\|\n")) + (setq ret nil) + (setq ret (buffer-substring (point-min) (- (point-max) 1))))) + (if (and ret (string-equal ret "")) (setq ret nil)) + (kill-buffer buf) + ret)) + +;; const because it must match the script's value +(defconst send-pr:datadir (or (gnats::get-config "DATADIR") "/usr/local/share") + "*Where the `gnats' subdirectory containing category lists lives.") + +(defvar send-pr::sites nil + "List of GNATS support sites; computed at runtime.") +(defvar send-pr:default-site + (or (gnats::get-config "GNATS_SITE") "cygnus") + "Default site to send bugs to.") +(defvar send-pr:::site send-pr:default-site + "The site to which a problem report is currently being submitted, or NIL +if using the default site (buffer local).") + +(defvar send-pr:::categories nil + "Buffer local list of available categories, derived at runtime from +send-pr:::site and send-pr::category-alist.") +(defvar send-pr::category-alist nil + "Alist of GNATS support sites and the categories supported at each; computed +at runtime.") + +;;; Ideally we would get all the following values from a central database +;;; during runtime instead of having them here in the code. +;;; +(defconst send-pr::fields + (` (("Category" send-pr::set-categories + (, (or (gnats::get-config "DEFAULT_CATEGORY") nil)) enum) + ("Class" (("sw-bug") ("doc-bug") ("change-request") ("support")) + (, (or (gnats::get-config "DEFAULT_CLASS") 0)) enum) + ("Confidential" (("yes") ("no")) + (, (or (gnats::get-config "DEFAULT_CONFIDENTIAL") 1)) enum) + ("Severity" (("non-critical") ("serious") ("critical")) + (, (or (gnats::get-config "DEFAULT_SEVERITY") 1)) enum) + ("Priority" (("low") ("medium") ("high")) + (, (or (gnats::get-config "DEFAULT_PRIORITY") 1)) enum) + ("Release" nil + (, (or (gnats::get-config "DEFAULT_RELEASE") "gnats-3.101")) + text) + ("Submitter-Id" nil + (, (or (gnats::get-config "SUBMITTER") "xSUBMITTERx")) text) + ("Synopsis" nil nil text + (lambda (a b c) (gnats::set-mail-field "Subject" c))))) + "AList, keyed on the name of the field, of: +1) The field name. +2) The list of completions. This can be a list, a function to call, or nil. +3) The default value. +4) The type of the field. +5) A sub-function to call when changed.") + +(defvar gnats::fields nil) + +(defmacro gnats::push (i l) + (` (setq (, l) (cons (,@ (list i l)))))) + +(defun send-pr::set-categories (&optional arg) + "Get the list of categories for the current site out of +send-pr::category-alist if there or from send-pr if not. With arg, force +update." + ;; + (let ((entry (assoc send-pr:::site send-pr::category-alist))) + (or (and entry (null arg)) + (let ((oldpr (getenv "GNATS_ROOT")) cats) + (send-pr::set-sites arg) + (setenv "GNATS_ROOT" gnats:root) + (setq cats (gnats::get-value-from-shell + "send-pr" "-CL" send-pr:::site)) + (setenv "GNATS_ROOT" oldpr) + (if entry (setcdr entry cats) + (setq entry (cons send-pr:::site cats)) + (gnats::push entry send-pr::category-alist)))) + (setq send-pr:::categories (cdr entry)))) + +(defun send-pr::set-sites (&optional arg) + "Get the list of sites (by listing the contents of DATADIR/gnats) and assign +it to send-pr::sites. With arg, force update." + (or (and (null arg) send-pr::sites) + (progn + (setq send-pr::sites nil) + (mapcar + (function + (lambda (file) + (or (memq t (mapcar (function (lambda (x) (string= x file))) + '("." ".." "pr-edit" "pr-addr"))) + (not (file-readable-p file)) + (gnats::push (list (file-name-nondirectory file)) + send-pr::sites)))) + (directory-files (format "%s/gnats" send-pr:datadir) t)) + (setq send-pr::sites (reverse send-pr::sites))))) + +(defconst send-pr::pr-buffer-name "*send-pr*" + "Name of the temporary buffer, where the problem report gets composed.") + +(defconst send-pr::err-buffer-name "*send-pr-error*" + "Name of the temporary buffer, where send-pr error messages appear.") + +(defvar send-pr:::err-buffer nil + "The error buffer used by the current PR buffer.") + +(defvar send-pr:::spawn-to-send nil + "Whether or not send-pr-mode should spawn a send-pr process to send the PR.") + +(defconst gnats::indent 17 "Indent for formatting the value.") + +;;;;--------------------------------------------------------------------------- +;;;; `send-pr' - command for creating and sending of problem reports +;;;;--------------------------------------------------------------------------- + +;;;###autoload +(fset 'send-pr 'send-pr:send-pr) +;;;###autoload +(defun send-pr:send-pr (&optional site) + "Create a buffer and read in the result of `send-pr -P'. +When finished with editing the problem report use \\[send-pr:submit-pr] +to send the PR with `send-pr -b -f -'." + ;; + (interactive + (if current-prefix-arg + (list (completing-read "Site: " (send-pr::set-sites 'recheck) nil t + send-pr:default-site)))) + (or site (setq site send-pr:default-site)) + (let ((buf (get-buffer send-pr::pr-buffer-name))) + (if (or (not buf) + (progn (switch-to-buffer buf) + (cond ((or (not (buffer-modified-p buf)) + (y-or-n-p "Erase previous problem report? ")) + (erase-buffer) t) + (t nil)))) + (send-pr::start-up site)))) + +(defun send-pr::start-up (site) + (switch-to-buffer (get-buffer-create send-pr::pr-buffer-name)) + (setq default-directory (expand-file-name "~/")) + (auto-save-mode auto-save-default) + (let ((oldpr (getenv "GNATS_ROOT")) + (case-fold-search nil)) + (setenv "GNATS_ROOT" gnats:root) + (send-pr::insert-template site) + (setenv "GNATS_ROOT" oldpr) + (goto-char (point-min)) + (if (looking-at "send-pr:") + (cond ((looking-at "send-pr: .* does not have a categories list") + (setq send-pr::sites nil) + (error "send-pr: the GNATS site %s does not have a categories list" site)) + (t (error (buffer-substring (point-min) (point-max))))) + (save-excursion + ;; Clear cruft inserted by bdamaged .cshrcs + (goto-char 1) + (re-search-forward "^SEND-PR:") + (delete-region 1 (match-beginning 0))))) + (set-buffer-modified-p nil) + (send-pr:send-pr-mode) + (setq send-pr:::site site) + (setq send-pr:::spawn-to-send t) + (send-pr::set-categories) + (if (null send-pr:::categories) + (progn + (and send-pr:::err-buffer (kill-buffer send-pr:::err-buffer)) + (kill-buffer nil) + (message "send-pr: no categories found")) + (or (stringp mail-default-reply-to) + (setq mail-default-reply-to (getenv "REPLYTO"))) + (and mail-default-reply-to + (gnats::set-mail-field "Reply-To" mail-default-reply-to)) + (and mail-self-blind + (gnats::set-mail-field "BCC" (user-login-name))) + (mapcar 'send-pr::maybe-change-field send-pr::fields) + (gnats::position-on-field "Description") + (message (substitute-command-keys + "To send the problem report use: \\[send-pr:submit-pr]")))) + +(defvar send-pr::template-alist nil + "An alist containing the output of send-pr -P <sitename> for various sites.") + +(defun send-pr::insert-template (site) + (let ((elt (assoc site send-pr::template-alist))) + (if elt + (save-excursion (insert (cdr elt))) + (call-process "send-pr" nil t nil "-P" site) + (save-excursion + (setq send-pr::template-alist + (cons (cons site (buffer-substring (point-min) (point-max))) + send-pr::template-alist)))))) + +(fset 'do-send-pr 'send-pr:submit-pr) ;backward compat +(defun send-pr:submit-pr () + "Pipe the contents of the buffer *send-pr* to `send-pr -f -.' unless this +buffer was loaded with emacsclient, in which case save the buffer and exit." + ;; + (interactive) + (cond + ((and (boundp 'server-buffer-clients) + server-buffer-clients) + (let ((buffer (current-buffer)) + (version-control nil) (buffer-backed-up nil)) + (save-buffer buffer) + (kill-buffer buffer) + (server-buffer-done buffer))) + (send-pr:::spawn-to-send + (or (and send-pr:::err-buffer + (buffer-name send-pr:::err-buffer)) + (setq send-pr:::err-buffer + (get-buffer-create send-pr::err-buffer-name))) + (let ((err-buffer send-pr:::err-buffer) mesg ok) + (save-excursion (set-buffer err-buffer) (erase-buffer)) + (message "running send-pr...") + (let ((oldpr (getenv "GNATS_ROOT"))) + (setenv "GNATS_ROOT" gnats:root) + (call-process-region (point-min) (point-max) "send-pr" + nil err-buffer nil send-pr:::site + "-b" "-f" "-") + (setenv "GNATS_ROOT" oldpr)) + (message "running send-pr...done") + ;; stupidly we cannot check the return value in EMACS 18.57, thus we need + ;; this kluge to find out whether send-pr succeeded. + (if (save-excursion + (set-buffer err-buffer) + (goto-char (point-min)) + (setq mesg (buffer-substring (point-min) (- (point-max) 1))) + (search-forward "problem report sent" nil t)) + (progn (message mesg) + (kill-buffer err-buffer) + (delete-auto-save-file-if-necessary) + (set-buffer-modified-p nil) + (bury-buffer)) + (pop-to-buffer err-buffer)) + )) + (t + (save-buffer) + (message "Exit emacs to send the PR.")))) + +;;;;--------------------------------------------------------------------------- +;;;; send-pr:send-pr-mode mode +;;;;--------------------------------------------------------------------------- + +(defvar send-pr-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-c\C-c" 'send-pr:submit-pr) + (define-key map "\C-c\C-f" 'gnats:change-field) + (define-key map "\M-n" 'gnats:next-field) + (define-key map "\M-p" 'gnats:previous-field) + (define-key map "\C-\M-f" 'gnats:forward-field) + (define-key map "\C-\M-b" 'gnats:backward-field) + map) + "Keymap for send-pr mode.") + +(defconst gnats::keyword "^>\\([-a-zA-Z]+\\):") +(defconst gnats::before-keyword "[ \t\n\f]*[\n\f]+>\\([-a-zA-Z]+\\):") +(defconst gnats::after-keyword "^>\\([-a-zA-Z]+\\):[ \t\n\f]+") + +;;;###autoload +(fset 'send-pr-mode 'send-pr:send-pr-mode) +;;;###autoload +(defun send-pr:send-pr-mode () + "Major mode for submitting problem reports. +For information about the form see gnats(1) and send-pr(1). +Special commands: \\{send-pr-mode-map} +Turning on send-pr-mode calls the value of the variable send-pr-mode-hook, +if it is not nil." + (interactive) + (gnats::patch-exec-path) + (put 'send-pr:send-pr-mode 'mode-class 'special) + (kill-all-local-variables) + (setq major-mode 'send-pr:send-pr-mode) + (setq mode-name "send-pr") + (use-local-map send-pr-mode-map) + (set-syntax-table text-mode-syntax-table) + (setq local-abbrev-table text-mode-abbrev-table) + (setq buffer-offer-save t) + (make-local-variable 'send-pr:::site) + (make-local-variable 'send-pr:::categories) + (make-local-variable 'send-pr:::err-buffer) + (make-local-variable 'send-pr:::spawn-to-send) + (make-local-variable 'paragraph-separate) + (setq paragraph-separate (concat (default-value 'paragraph-separate) + "\\|" gnats::keyword "[ \t\n\f]*$")) + (make-local-variable 'paragraph-start) + (setq paragraph-start (concat (default-value 'paragraph-start) + "\\|" gnats::keyword)) + (run-hooks 'send-pr-mode-hook) + t) + +;;;;--------------------------------------------------------------------------- +;;;; Functions to read and replace field values. +;;;;--------------------------------------------------------------------------- + +(defun gnats::position-on-field (field &optional quiet) + (goto-char (point-min)) + (if (not (re-search-forward (concat "^>" field ":") nil t)) + (if quiet + nil + (error "Field `>%s:' not found." field)) + (re-search-forward "[ \t\n\f]*") + (if (looking-at gnats::keyword) + (backward-char 1)) + t)) + +(defun gnats::mail-position-on-field (field) + (let (end + (case-fold-search t)) + (goto-char (point-min)) + (re-search-forward "^$") + (setq end (match-beginning 0)) + (goto-char (point-min)) + (if (not (re-search-forward (concat "^" field ":") end 'go-to-end)) + (insert field ": \n") + (re-search-forward "[ \t\n\f]*")) + (skip-chars-backward "\n") + t)) + +(defun gnats::field-contents (field &optional elem move) + (let (pos) + (unwind-protect + (save-excursion + (if (not (gnats::position-on-field field t)) + nil + (setq pos (point-marker)) + (if (or (looking-at "<.*>$") (eolp)) + t + (looking-at ".*$") ; to set match-{beginning,end} + (gnats::nth-word + (buffer-substring (match-beginning 0) (match-end 0)) + elem)))) + (and move pos (goto-char pos))))) + +(defun gnats::functionp (thing) + (or (and (symbolp thing) (fboundp thing)) + (and (listp thing) (eq (car thing) 'lambda)))) + +(defun gnats::field-values (field) + "Return the possible (known) values for field FIELD." + (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields + send-pr::fields)) + (thing (elt (assoc field fields) 1))) + (cond ((gnats::functionp thing) (funcall thing)) + ((listp thing) thing) + (t (error "ACK"))))) + +(defun gnats::field-default (field) + "Return the default value for field FIELD." + (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields + send-pr::fields)) + (thing (elt (assoc field fields) 2))) + (cond ((stringp thing) thing) + ((null thing) "") + ((numberp thing) (car (elt (gnats::field-values field) thing))) + ((gnats::functionp thing) + (funcall thing (gnats::field-contents field))) + ((eq thing t) (gnats::field-contents field)) + (t (error "ACK"))))) + +(defun gnats::field-type (field) + "Return the type of field FIELD." + (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields + send-pr::fields)) + (thing (elt (assoc field fields) 3))) + thing)) + +(defun gnats::field-action (field) + "Return the extra handling function for field FIELD." + (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields + send-pr::fields)) + (thing (elt (assoc field fields) 4))) + (cond ((null thing) 'ignore) + ((gnats::functionp thing) thing) + (t (error "ACK"))))) + +;;;;--------------------------------------------------------------------------- +;;;; Point movement functions +;;;;--------------------------------------------------------------------------- + +(or (fboundp 'defsubst) (fset 'defsubst 'defun)) + +(defun send-pr::maybe-change-field (field) + (setq field (car field)) + (let ((thing (gnats::field-contents field))) + (and thing (eq t thing) + (not (eq 'multi-text (gnats::field-type field))) + (gnats:change-field field)))) + +(defun gnats:change-field (&optional field default) + "Change the value of the field containing the cursor. With arg, ask the +user for the field to change. From a program, the function takes optional +arguments of the field to change and the default value to use." + (interactive) + (or field current-prefix-arg (setq field (gnats::current-field))) + (or field + (setq field + (completing-read "Field: " + (if (eq major-mode 'gnats:gnats-mode) + gnats::fields + send-pr::fields) + nil t))) + (gnats::position-on-field field) + (sit-for 0) + (let* ((old (gnats::field-contents field)) + new) + (if (null old) + (error "ACK") + (if (or (interactive-p) t) + (let ((prompt (concat ">" field ": ")) + (domain (gnats::field-values field)) + (type (gnats::field-type field))) + (or default (setq default (gnats::field-default field))) + (setq new + (if (eq type 'enum) + (completing-read prompt domain nil t + (if gnats::emacs-19p (cons default 0) + default)) + (read-string prompt (if gnats::emacs-19p (cons default 1) + default))))) + (setq new default)) + (gnats::set-field field new) + (funcall (gnats::field-action field) field old new) + new))) + +(defun gnats::set-field (field value) + (save-excursion + (gnats::position-on-field field) + (delete-horizontal-space) + (looking-at ".*$") + (replace-match + (concat (make-string (- gnats::indent (length field) 2) ?\40 ) value) t))) + +(defun gnats::set-mail-field (field value) + (save-excursion + (gnats::mail-position-on-field field) + (delete-horizontal-space) + (looking-at ".*$") + (replace-match (concat " " value) t))) + +(defun gnats::before-keyword (&optional where) + "Returns t if point is in some white space before a keyword. +If where is nil, then point is not changed; if where is t then point is moved +to the beginning of the keyword, otherwise it is moved to the beginning +of the white space it was in." + ;; + (if (looking-at gnats::before-keyword) + (prog1 t + (cond ((eq where t) + (re-search-forward "^>") (backward-char)) + ((not (eq where nil)) + (re-search-backward "[^ \t\n\f]") (forward-char)))) + nil)) + +(defun gnats::after-keyword (&optional where) + "Returns t if point is in some white space after a keyword. +If where is nil, then point is not changed; if where is t then point is moved +to the beginning of the keyword, otherwise it is moved to the end of the white +space it was in." + ;; + (if (gnats::looking-after gnats::after-keyword) + (prog1 t + (cond ((eq where t) + (re-search-backward "^>")) + ((not (eq where nil)) + (re-search-forward "[^ \t\n\f]") (backward-char)))) + nil)) + +(defun gnats::in-keyword (&optional where) + "Returns t if point is within a keyword. +If where is nil, then point is not changed; if where is t then point is moved +to the beginning of the keyword." + ;; + (let ((old-point (point-marker))) + (beginning-of-line) + (cond ((and (looking-at gnats::keyword) + (< old-point (match-end 0))) + (prog1 t + (if (eq where t) + t + (goto-char old-point)))) + (t (goto-char old-point) + nil)))) + +(defun gnats::forward-bofield () + "Moves point to the beginning of a field. Assumes that point is in the +keyword." + ;; + (if (re-search-forward "[ \t\n\f]+[^ \t\n\f]" (point-max) '-) + (backward-char) + t)) + +(defun gnats::backward-eofield () + "Moves point to the end of a field. Assumes point is in the keyword." + ;; + (if (re-search-backward "[^ \t\n\f][ \t\n\f]+" (point-min) '-) + (forward-char) + t)) + +(defun gnats::forward-eofield () + "Moves point to the end of a field. Assumes that point is in the field." + ;; + ;; look for the next field + (if (re-search-forward gnats::keyword (point-max) '-) + (progn (beginning-of-line) (gnats::backward-eofield)) + (re-search-backward "[^ \t\n\f][ \t\n\f]*" (point-min) '-) + (forward-char))) + +(defun gnats::backward-bofield () + "Moves point to the beginning of a field. Assumes that point is in the +field." + ;; + ;;look for previous field + (if (re-search-backward gnats::keyword (point-min) '-) + (gnats::forward-bofield) + t)) + + +(defun gnats:forward-field () + "Move point forward to the end of the field or to the beginning of the next +field." + ;; + (interactive) + (if (or (gnats::before-keyword t) (gnats::in-keyword t) + (gnats::after-keyword t)) + (gnats::forward-bofield) + (gnats::forward-eofield))) + +(defun gnats:backward-field () + "Move point backward to the beginning/end of a field." + ;; + (interactive) + (backward-char) + (if (or (gnats::before-keyword t) (gnats::in-keyword t) + (gnats::after-keyword t)) + (gnats::backward-eofield) + (gnats::backward-bofield))) + +(defun gnats:next-field () + "Move point to the beginning of the next field." + ;; + (interactive) + (if (or (gnats::before-keyword t) (gnats::in-keyword t) + (gnats::after-keyword t)) + (gnats::forward-bofield) + (if (re-search-forward gnats::keyword (point-max) '-) + (gnats::forward-bofield) + t))) + +(defun gnats:previous-field () + "Move point to the beginning of the previous field." + ;; + (interactive) + (backward-char) + (if (or (gnats::after-keyword t) (gnats::in-keyword t) + (gnats::before-keyword t)) + (progn (re-search-backward gnats::keyword (point-min) '-) + (gnats::forward-bofield)) + (gnats::backward-bofield))) + +(defun gnats:beginning-of-field () + "Move point to the beginning of the current field." + (interactive) + (cond ((gnats::in-keyword t) + (gnats::forward-bofield)) + ((gnats::after-keyword 0)) + (t + (gnats::backward-bofield)))) + +(defun gnats::current-field () + (save-excursion + (if (cond ((or (gnats::in-keyword t) (gnats::after-keyword t)) + (looking-at gnats::keyword)) + ((re-search-backward gnats::keyword nil t))) + (buffer-substring (match-beginning 1) (match-end 1)) + nil))) + +;;;;--------------------------------------------------------------------------- +;;;; Support functions +;;;;--------------------------------------------------------------------------- + +(defun gnats::looking-after (regex) + "Returns t if point is after regex." + ;; + (let* ((old-point (point)) + (start (if (eobp) + old-point + (forward-char) (point)))) + (cond ((re-search-backward regex (point-min) t) + (goto-char old-point) + (cond ((eq (match-end 0) start) + t)))))) + +(defun gnats::nth-word (string &optional elem) + "Returns the elem-th word of the string. +If elem is nil, then the first wort is returned, if elem is 0 then +the whole string is returned." + ;; + (if (integerp elem) + (cond ((eq elem 0) string) + ((eq elem 1) (gnats::first-word string)) + ((equal string "") "") + ((>= elem 2) + (let ((i 0) (value "")) + (setq string ; strip leading blanks + (substring string (or (string-match "[^ \t]" string) 0))) + (while (< i elem) + (setq value + (substring string 0 + (string-match "[ \t]*$\\|[ \t]+" string))) + (setq string + (substring string (match-end 0))) + (setq i (+ i 1))) + value))) + (gnats::first-word string))) + +(defun gnats::first-word (string) + (setq string + (substring string (or (string-match "[^ \t]" string) 0))) + (substring string 0 (string-match "[ \t]*$\\|[ \t]+" string))) + +;;;;--------------------------------------------------------------------------- + +(defun gnats::patch-exec-path () + ;; + "Replaces `//' by `/' in `exec-path'." + ;; + ;(make-local-variable 'exec-path) + (let ((err-buffer (get-buffer-create " *gnats::patch-exec-path*")) + (ret)) + (setq exec-path (save-excursion (set-buffer err-buffer) + (prin1 exec-path err-buffer) + (goto-char (point-min)) + (while (search-forward "//" nil t) + (replace-match "/" nil t)) + (goto-char (point-min)) + (setq ret (read err-buffer)) + (kill-buffer err-buffer) + ret + )))) + +(defun gnats::get-value-from-shell (&rest command) + "Execute shell command to get a list of valid values for `variable'." + ;; + (let ((err-buffer (get-buffer-create " *gnats::get-value-from-shell*"))) + (save-excursion + (set-buffer err-buffer) + (unwind-protect + (condition-case var + (progn + (apply 'call-process + (car command) nil err-buffer nil (cdr command)) + (goto-char (point-min)) + (if (looking-at "[-a-z]+: ") + (error (buffer-substring (point-min) (point-max)))) + (read err-buffer)) + (error nil)) + (kill-buffer err-buffer))))) + +(or (fboundp 'setenv) + (defun setenv (variable &optional value) + "Set the value of the environment variable named VARIABLE to VALUE. +VARIABLE should be a string. VALUE is optional; if not provided or is +`nil', the environment variable VARIABLE will be removed. +This function works by modifying `process-environment'." + (interactive "sSet environment variable: \nsSet %s to value: ") + (if (string-match "=" variable) + (error "Environment variable name `%s' contains `='" variable) + (let ((pattern (concat "\\`" (regexp-quote (concat variable "=")))) + (case-fold-search nil) + (scan process-environment)) + (while scan + (cond + ((string-match pattern (car scan)) + (if (eq nil value) + (setq process-environment (delq (car scan) + process-environment)) + (setcar scan (concat variable "=" value))) + (setq scan nil)) + ((null (setq scan (cdr scan))) + (setq process-environment + (cons (concat variable "=" value) + process-environment))))))))) + +;;;; end of send-pr.el