summaryrefslogtreecommitdiffstats
path: root/emacs.d/nxhtml/util/udev.el
blob: ee9d86a4e43d2ae34e8363ff63bfeba8fbeab2d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
;;; 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, <APPS> 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