;;; udev.el --- Helper functions for updating from dev sources ;; ;; Author: Lennart Borgman (lennart O borgman A gmail O com) ;; Created: 2008-08-24 (defconst udev:version "0.5");; Version: ;; Last-Updated: 2009-01-06 Tue ;; URL: ;; Keywords: ;; Compatibility: ;; ;; Features that might be required by this library: ;; ;; `cus-edit', `cus-face', `cus-load', `cus-start', `wid-edit'. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: ;; ;; When you want to fetch and install sources from a repository you ;; may have to call several async processes and wait for the answer ;; before calling the next function. These functions may help you with ;; this. ;; ;; See `udev-call-first-step' for more information. Or look in the ;; file udev-cedet.el for examples. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Change log: ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; 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, 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 General Public License ;; along with this program; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth ;; Floor, Boston, MA 02110-1301, USA. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Code: (eval-when-compile (require 'cl)) (require 'cus-edit) ;;; Control/log buffer (defvar udev-log-buffer nil "Log buffer pointer for sentinel function.") (make-variable-buffer-local 'udev-log-buffer) (defvar udev-is-log-buffer nil "This is t if this is an udev log/control buffer.") (make-variable-buffer-local 'udev-is-log-buffer) (defun udev-check-is-log-buffer (buffer) "Check that BUFFER is an udev log/control buffer." (with-current-buffer buffer (unless udev-is-log-buffer (error "Internal error, not a log buffer: %s" buffer)))) (defvar udev-this-chain nil) (make-variable-buffer-local 'udev-this-chain) (defvar udev-last-error nil "Error found during last step.") (make-variable-buffer-local 'udev-last-error) (defun udev-set-last-error (log-buffer msg) (with-current-buffer log-buffer (setq udev-last-error msg))) ;;; Chain utils (defun udev-chain (log-buffer) "Return value of `udev-this-chain' in buffer LOG-BUFFER." (udev-check-is-log-buffer log-buffer) (with-current-buffer log-buffer udev-this-chain)) (defun udev-this-step (log-buffer) "Return current function to call from LOG-BUFFER." (let ((this-chain (udev-chain log-buffer))) (caar this-chain))) (defun udev-goto-next-step (log-buffer) "Set next function as current in LOG-BUFFER." (let* ((this-chain (udev-chain log-buffer)) (this-step (car this-chain))) (setcar this-chain (cdr this-step)))) (defun udev-num-steps (log-buffer) "Return number of steps." (length (nth 2 (udev-chain log-buffer)))) (defun udev-step-num (log-buffer) "Return current step number." (let ((this-chain (udev-chain log-buffer))) (when this-chain (1+ (- (udev-num-steps log-buffer) (length (car this-chain))))))) (defun udev-finish-function (log-buffer) "Return setup function to be called when finished." (nth 3 (udev-chain log-buffer))) (defvar udev-control-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map button-buffer-map) map)) (define-derived-mode udev-control-mode nil "Udev-Src" "Mode for udev control buffer." (setq show-trailing-whitespace nil) (setq buffer-read-only t) (nxhtml-menu-mode 1)) ;;; Calling steps ;;;###autoload (defun udev-call-first-step (log-buffer steps header finish-fun) "Set up and call first step. Set up buffer LOG-BUFFER to be used for log messages and controling of the execution of the functions in list STEPS which are executed one after another. Write HEADER at the end of LOG-BUFFER. Call first step. If FINISH-FUN non-nil it should be a function. This is called after last step with LOG-BUFFER as parameter." ;;(dolist (step steps) (unless (functionp step) (error "Not a known function: %s" step))) (switch-to-buffer log-buffer) (udev-control-mode) (setq udev-is-log-buffer t) (let ((this-chain (cons nil (cons log-buffer (cons (copy-tree steps) (cons finish-fun nil)))))) (setcar this-chain (caddr this-chain)) (setq udev-this-chain this-chain)) (assert (eq (car steps) (udev-this-step log-buffer)) t) (assert (eq finish-fun (udev-finish-function log-buffer)) t) (widen) (goto-char (point-max)) (let ((inhibit-read-only t)) (unless (= (point) (point-min)) (insert "\n\n")) (insert header)) (udev-call-this-step log-buffer nil) (current-buffer)) (defvar udev-step-keymap (let ((map (make-sparse-keymap))) (define-key map [(control ?c) ?r] 'udev-rerun-this-step) (define-key map [(control ?c) ?c] 'udev-continue-from-this-step) (define-key map [(control ?c) ?s] 'udev-goto-this-step-source) map)) (defun udev-step-at-point () (get-text-property (point) 'udev-step)) (defun udev-rerun-this-step () "Rerun this step." (interactive) (let ((this-step (udev-step-at-point))) (udev-call-this-step (current-buffer) this-step))) (defun udev-continue-from-this-step () "Continue from this step." (interactive) (let ((this-step (udev-step-at-point))) (goto-char (point-max)) (let ((inhibit-read-only t)) (insert (format "\n\nContinuing from %s..." this-step))) (udev-call-this-step (current-buffer) this-step))) (defun udev-goto-this-step-source () "Find source function for this step." (interactive) (let ((this-step (udev-step-at-point))) (find-function-other-window this-step))) (defun udev-call-this-step (log-buffer this-step) "Call the current function in LOG-BUFFER. If this function returns a buffer and the buffer has a process then change the process sentinel to `udev-compilation-sentinel'. Otherwise continue to call the next function. Also put a log message in in LOG-BUFFER with a link to the buffer returned above if any." (setq this-step (or this-step (udev-this-step log-buffer))) (with-current-buffer log-buffer (setq udev-last-error nil) (widen) (goto-char (point-max)) (let* ((inhibit-read-only t) here buf proc) (if (not this-step) (let ((finish-fun (udev-finish-function log-buffer))) (insert (propertize "\nFinished\n" 'face 'compilation-info)) (when finish-fun (funcall finish-fun log-buffer))) (insert (format "\nStep %s(%s): " (udev-step-num log-buffer) (udev-num-steps log-buffer))) (setq here (point)) (insert (pp-to-string this-step)) (setq buf (funcall this-step log-buffer)) (when (bufferp buf) (make-text-button here (point) 'udev-step this-step 'keymap udev-step-keymap 'buffer buf 'help-echo "Push RET to see log buffer, for other actions" 'action (lambda (btn) (display-buffer (button-get btn 'buffer)))) (setq proc (get-buffer-process buf))) ;; Setup for next step (if (and proc (not udev-last-error)) (progn (with-current-buffer buf ;; Make a copy here for the sentinel function. (setq udev-log-buffer log-buffer) (setq udev-orig-sentinel (process-sentinel proc)) (set-process-sentinel proc 'udev-compilation-sentinel))) ;;(message "proc is nil") (if udev-last-error (insert " " (propertize udev-last-error 'face 'compilation-error)) (udev-call-next-step log-buffer 0 nil))))))) (defun udev-call-next-step (log-buffer prev-exit-status exit-status-buffer) "Go to next step in LOG-BUFFER and call `udev-call-this-step'. However if PREV-EXIT-STATUS \(which is the exit status from the previous step) is not 0 and there is in EXIT-STATUS-BUFFER no `udev-continue-on-error-function' then stop and insert an error message in LOG-BUFFER." (with-current-buffer log-buffer (let ((inhibit-read-only t)) (widen) (goto-char (point-max)) (insert " ") (if (or (= 0 prev-exit-status) (with-current-buffer exit-status-buffer (when udev-continue-on-error-function (funcall udev-continue-on-error-function exit-status-buffer)))) (progn (insert (if (= 0 prev-exit-status) (propertize "Ok" 'face 'compilation-info) (propertize "Warning, check next step" 'face 'compilation-warning))) (udev-goto-next-step log-buffer) (udev-call-this-step log-buffer nil)) (insert (propertize "Error" 'face 'compilation-error)))))) ;;; Sentinel (defvar udev-orig-sentinel nil "Old sentinel function remembered by `udev-call-this-step'.") (make-variable-buffer-local 'udev-orig-sentinel) (defun udev-compilation-sentinel (proc msg) "Sentinel to use for processes started by `udev-call-this-step'. Check for error messages and call next step. PROC and MSG have the same meaning as for `compilation-sentinel'." ;;(message "udev-compilation-sentinel proc=%s msg=%s" proc msg) (let ((buf (process-buffer proc)) (exit-status (process-exit-status proc))) (with-current-buffer buf (when udev-orig-sentinel (funcall udev-orig-sentinel proc msg)) (when (and (eq 'exit (process-status proc)) (= 0 exit-status)) ;; Check for errors (let ((here (point)) (err-point 1) (has-error nil)) (widen) (goto-char (point-min)) (setq has-error (catch 'found-error (while err-point (setq err-point (next-single-property-change err-point 'face)) (when err-point (let ((face (get-text-property err-point 'face))) (when (or (and (listp face) (memq 'compilation-error face)) (eq 'compilation-error face)) (throw 'found-error t))))))) (when has-error (setq exit-status 1) (goto-char (point-max)) (let ((inhibit-read-only t)) (insert (propertize "There were errors" 'font-lock-face 'compilation-error))) (udev-set-compilation-end-message buf 'exit (cons "has errors" 1))) (goto-char here) )) (unless (member proc compilation-in-progress) (udev-call-next-step udev-log-buffer exit-status (current-buffer)))))) (defun udev-set-compilation-end-message (buffer process-status status) "Change the message shown after compilation. This is similar to `compilation-end-message' and BUFFER, PROCESS-STATUS and STATUS have the same meaning as there." (with-current-buffer buffer (setq mode-line-process (let ((out-string (format ":%s [%s]" process-status (cdr status))) (msg (format "%s %s" mode-name (replace-regexp-in-string "\n?$" "" (car status))))) (message "%s" msg) (propertize out-string 'help-echo msg 'face (if (> (cdr status) 0) 'compilation-error 'compilation-info)))))) (defvar udev-continue-on-error-function nil "One-time helper to resolve exit status error problem. This can be used for example after calling `cvs diff' which returns error exit status if there is a difference - even though there does not have to be an error.") (make-variable-buffer-local 'udev-continue-on-error-function) ;;; Convenience functions (defun udev-buffer-name (fmt log-buffer mode) "Return a name for compilation buffer. Use format string FMT and buffer LOG-BUFFER, but ignoring MODE." (format fmt (when (buffer-live-p log-buffer) (udev-this-step log-buffer)))) (defvar udev-this-dir (let ((this-file (or load-file-name (buffer-file-name)))) (file-name-directory this-file))) (defun udev-batch-compile (emacs-args defdir name-function) "Compile elisp code in an inferior Emacs. Start Emacs with emacs -Q -batch EMACS-ARGS in the default directory DEFDIR. Set the buffer name for the inferior process with NAME-FUNCTION by giving this to `compilation-start'." (let ((default-directory (file-name-as-directory defdir)) (this-emacs (ourcomments-find-emacs))) (compilation-start (concat this-emacs " -Q -batch " emacs-args) 'compilation-mode name-function))) ;;; Convenience functions for CVS (defun udev-fetch-cvs-diff (defdir name-function) "Fetch cvs diff in directory DEFDIR. Put the diff in file 'your-patches.diff' in DEFDIR. Give inferior buffer name with NAME-FUNCTION." (let ((default-directory (file-name-as-directory defdir))) (with-current-buffer (compilation-start (concat "cvs diff -b -u > " (shell-quote-argument "your-patches.diff")) 'compilation-mode name-function) (setq udev-continue-on-error-function 'udev-cvs-diff-continue) (current-buffer)))) (defun udev-cvs-diff-continue (cvs-diff-buffer) "Return non-nil if it is ok to continue. Check the output from the `cvs diff' command in buffer CVS-DIFF-BUFFER. The cvs command exits with a failure status if there is a difference, which means that it is hard to know whether there was an error or just a difference. This function tries to find out." (with-current-buffer cvs-diff-buffer (let ((here (point)) (ret t)) (goto-char (point-min)) (when (search-forward "cvs [diff aborted]" nil t) (setq ret nil)) (goto-char (point-min)) (when (search-forward "merge conflict" nil t) (setq ret t)) ;; From cvs co command: ;; rcsmerge: warning: conflicts during merge (goto-char (point-min)) (when (search-forward "conflicts during merge" nil t) (setq ret t)) ;; cvs checkout: conflicts found in emacs/lisp/startup.el (goto-char (point-min)) (when (search-forward "conflicts found in" nil t) (setq ret t)) (goto-char here) ret))) (defun udev-check-cvs-diff (diff-file log-buffer) "Check cvs diff output in file DIFF-FILE for merge conflicts. Return buffer containing DIFF-FILE." (let ((buf (find-buffer-visiting diff-file))) ;; Kill buffer to avoid question about revert. (when buf (kill-buffer buf)) (setq buf (find-file-noselect diff-file)) (with-current-buffer buf (widen) (let ((here (point))) (goto-char (point-min)) ;; Fix-me: Better pattern: (if (search-forward "<<<<<<<" nil t) ;; Merge conflict (with-current-buffer log-buffer (let ((inhibit-read-only t)) (setq udev-last-error "Error: merge conflict"))) (goto-char here)))) buf)) ;;(setq compilation-scroll-output t) ;;(add-to-list 'compilation-error-regexp-alist 'cvs) ;;(setq compilation-error-regexp-alist (delq 'cvs compilation-error-regexp-alist)) ;;; Misc (defun udev-send-buffer-process (str) (interactive "sString to send to process: ") (let* ((procs (process-list)) (proc (catch 'found (dolist (p procs) (when (eq (process-buffer p) (current-buffer)) (throw 'found p)))))) (unless proc (error "Can't find process in buffer")) ;;(message "str=%s" str) ;;(message "proc=%s" proc) (process-send-string proc (concat str "\n")) )) (provide 'udev) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; udev.el ends here