1 ;;; patcher-mail.el --- Mail management
3 ;; Copyright (C) 2008, 2009, 2010, 2011 Didier Verna
4 ;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007 Didier Verna
6 ;; Author: Didier Verna <didier@xemacs.org>
7 ;; Maintainer: Didier Verna <didier@xemacs.org>
8 ;; Created: Mon Feb 15 15:26:26 2010
9 ;; Last Revision: Thu Dec 8 08:53:32 2011
13 ;; This file is part of Patcher.
15 ;; Patcher is free software; you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License version 3,
17 ;; as published by the Free Software Foundation.
19 ;; Patcher is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with this program; if not, write to the Free Software
26 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31 ;; Contents management by FCM version 0.1.
38 (eval-when-compile (require 'patcher-cutil))
39 (require 'patcher-util)
40 (require 'patcher-project)
41 (require 'patcher-instance)
42 (require 'patcher-source)
43 (require 'patcher-change-log)
44 (require 'patcher-diff)
45 (require 'patcher-cmtcmd)
46 (require 'patcher-logmsg)
49 ;; Require 'sendmail for getting `mail-header-separator'.
50 ;; #### Now that a fake mail sending function exists, sendmail shouldn't be
51 ;; #### systematically required like this. However, since most users will
52 ;; #### really want do send real messages, it probably doesn't hurt to keep
58 ;; ==========================================================================
60 ;; ==========================================================================
62 (make-variable-buffer-local
63 (defvar patcher-change-logs-marker nil
64 ;; Marker indicating the beginning of the ChangeLog entries, when they are
65 ;; separated from the patch.
68 (make-variable-buffer-local
69 (defvar patcher-diff-marker nil
70 ;; Marker indicating the beginning of the diff.
73 (defun patcher-goto-subject ()
74 ;; Move point to the beginning of the Subject: header's contents.
75 (goto-char (point-min))
76 (re-search-forward "^Subject: "))
78 (defun patcher-goto-signature ()
79 ;; Move point to the beginning of the mail signature (actually, in front of
80 ;; the signature separator), if any. Otherwise, move point to the end of
81 ;; the message. Return that position.
82 (goto-char (point-min))
83 (if (re-search-forward
84 (cond ((eq major-mode 'mail-mode)
85 ;; this is hard-wired in sendmail.el
87 ((eq major-mode 'message-mode)
88 (declare-boundp message-signature-separator))
92 Your mailing method is not fully supported by Patcher.
93 This is not critical though: Patcher may not find the message signature
96 Please report to <didier@xemacs.org>."
98 ;; Use the standard one by default.
101 (goto-char (match-beginning 0))
102 ;; else: no signature
103 (goto-char (point-max))))
108 ;; ==========================================================================
110 ;; ==========================================================================
112 (globally-declare-boundp 'unused)
114 (defun* patcher-diff-project
116 &aux (buffer (patcher-project-process-buffer project))
117 (updating (patcher-project-option project :change-logs-updating))
118 (appearance (patcher-project-option project
119 :change-logs-appearance)))
120 ;; (Re)generate PROJECT's diff and insert items in the current mail buffer.
121 ;; Depending on PROJECT settings (ChangeLogs updating mode and appearance),
122 ;; insertion of ChangeLog entries or even the diff itself might be delayed.
123 (patcher-condition-case condition
124 ;; Note that in case of a diff regeneration, PROJECT may contain old
125 ;; source or ChangeLog references, and also maybe retained ChangeLog
126 ;; skeletons. We need to deal with that by doing some additional cleanup
127 ;; (like forgetting about obsolete source or ChangeLog files) or some
128 ;; additional work (like generating newly required skeletons).
129 (multiple-value-bind (sources change-logs)
130 (patcher-diff-specification project)
131 ;; First, figure out what's new and what's obsolete. When we end up
132 ;; with obsolete source or ChangeLog files, it's like they never
133 ;; existed, so there's no point in honoring the :kill-*-after-sending
135 (multiple-value-bind (unused obsolete-sources new-sources)
136 (patcher-list= (patcher-project-sources project) sources
138 (patcher-unlink-sources project
139 :sources obsolete-sources
141 (patcher-link-sources project new-sources))
142 (multiple-value-bind (unused obsolete-change-logs new-change-logs)
143 (patcher-list= (patcher-project-change-logs project) change-logs
145 ;; Having obsolete ChangeLogs that still contain skeletons is very
146 ;; likely to be an error.
147 (when (eq updating 'automatic)
148 (let (generated-change-log-buffers)
149 (dolist (obsolete-change-log obsolete-change-logs)
150 (multiple-value-bind (obsolete-change-log-buffer)
151 (patcher-change-log-buffer project obsolete-change-log)
152 (when (and obsolete-change-log-buffer
153 (patcher-change-log-extent project
154 obsolete-change-log-buffer))
155 (patcher-endpush obsolete-change-log-buffer
156 generated-change-log-buffers))))
157 (when generated-change-log-buffers
158 (case (patcher-with-message
160 WARNING: some ChangeLog skeletons remain in obsolete ChangeLog files (not
161 involved with the project anymore). The relevant ChangeLog files are the
164 By answering the question below, you have the possibility to keep those
165 skeletons (n), remove them all (y) or choose interactively (i).
167 Note that if you keep some of them, you may run into trouble later..."
168 (patcher-buffers-string
169 generated-change-log-buffers))
171 "Remove obsolete ChangeLog skeletons? " "yni"))
172 (?y (patcher-ungenerate-change-logs project
173 generated-change-log-buffers))
174 (?i (patcher-ungenerate-change-logs project
175 generated-change-log-buffers
177 :prompt "Remove this obsolete skeleton? "))))))
178 (patcher-unlink-change-logs project
179 :change-logs obsolete-change-logs
181 (patcher-link-change-logs project new-change-logs)
182 ;; Now, insert whatever possible in the mail buffer, perform some
183 ;; sanity checks based on what we know and generate ChangeLog
184 ;; skeletons if needed.
186 ;; No ChangeLog. The diff we already have is good enough so we can
187 ;; insert it right now.
189 (patcher-insert-diff project patcher-diff-marker)
191 To commit your changes, type \\[patcher-mail-commit]."))
192 ;; Automatic mode. ChangeLogs insertion, if required, is postponed
193 ;; but we might still be able to insert the diff right now.
195 ;; New ChangeLogs must be clean. Otherwise the project is out of
196 ;; date. Note that except for new ChangeLogs, the current diff
197 ;; might still contain ChangeLog entries: obsolete ones that were
198 ;; generated and not removed, and correct ones that were not
200 (patcher-detect-spurious-change-logs project new-change-logs)
201 ;; When ChangeLogs are inserted in a separate place, we can
202 ;; already insert the source diff.
203 (when (member appearance '(verbatim pack nil))
204 (patcher-insert-diff project patcher-diff-marker))
205 ;; Finally, we need to generate the missing skeletons. Since
206 ;; patch-to-change-log doesn't know if some of them have been
207 ;; generated already, I need to remove from the process buffer
208 ;; all source diffs that would contribute to an already generated
209 ;; skeleton. I definitely need to write my own version of
210 ;; patch-to-change-log.
211 ;; #### WARNING: it seems unsafe to delete extents from within
212 ;; mapcar-extents (I got cases where the mapping were interrupted
213 ;; before all extents were processed).
214 (dolist (extent (patcher-source-extents buffer))
215 (multiple-value-bind (change-log)
216 (patcher-change-log-buffer project
217 (patcher-locate-change-log project
218 (extent-property extent 'patcher-source)))
219 (when (and change-log
220 (patcher-change-log-extent project change-log))
221 (patcher-delete-extent-and-region extent))))
222 (patcher-generate-change-logs project)
223 (patcher-message "Please annotate the ChangeLog skeletons%s."
227 , and type \\[patcher-mail-insert-change-logs] to insert them")
230 , and type \\[patcher-mail-insert-change-logs] to diff them")
232 , and type \\[patcher-mail-insert-change-logs] to create the whole diff")
234 (if (patcher-project-option project
236 " before sending your message"
238 , and type \\[patcher-mail-commit] to commit your project."))
240 (patcher-error 'invalid-project-option
241 :change-logs-appearance
243 ;; Manual mode. ChangeLogs are supposed to be written already so
244 ;; insertion can always be done right now.
246 ;; Without a specification, the diff is global so this is an
247 ;; opportunity to check for ChangeLogs consistency.
248 (unless (patcher-project-specification project)
249 (patcher-detect-inconsistent-change-logs project))
252 (patcher-insert-diff project patcher-diff-marker)
253 ;; #### NOTE: when ChangeLog entries are part of the diff, we
254 ;; could try to convert the diff to a verbatim version instead
255 ;; of calling `patcher-insert-change-log-contents'.
256 (patcher-insert-change-log-contents
257 project patcher-change-logs-marker))
259 (patcher-detect-ephemeral-change-logs project)
260 (let ((command (patcher-project-option project
261 :change-logs-diff-command)))
263 ;; The diff command is different. We need to rediff
264 ;; the ChangeLogs in all situations.
265 (patcher-insert-diff project patcher-diff-marker)
266 (patcher-diff-change-logs project)
267 (patcher-insert-diff project
268 patcher-change-logs-marker :change-logs))
270 ;; We use the same diff command:
271 (cond ((patcher-project-specification project)
272 ;; Some ChangeLogs may not be there. We must
274 (patcher-insert-diff project
276 (patcher-diff-change-logs project)
277 (patcher-insert-diff project
278 patcher-change-logs-marker :change-logs))
280 ;; All ChangeLogs appear in the diff.
281 (patcher-insert-diff project
283 (patcher-insert-diff project
284 patcher-change-logs-marker
287 (patcher-detect-ephemeral-change-logs project)
288 (cond ((patcher-project-specification project)
289 ;; Some ChangeLogs not be there. We must rediff the
291 (patcher-diff-all project)
292 (patcher-insert-diff project patcher-diff-marker
295 ;; All ChangeLogs appear in the diff.
296 (when (patcher-project-option project
297 :change-logs-diff-command)
298 (patcher-convert-change-logs-diff project))
299 (patcher-insert-diff project patcher-diff-marker
302 (patcher-insert-diff project patcher-diff-marker))
304 (patcher-error 'invalid-project-option
305 :change-logs-appearance appearance)))
307 To commit your changes, type \\[patcher-mail-commit]."))
309 (patcher-error 'invalid-project-option
310 :change-logs-updating updating)))))
312 (display-buffer (patcher-project-process-buffer patcher-project) t)
315 Error during diff. Type \\[patcher-mail-diff] to try again."))
316 (change-logs-consistency
317 (patcher-display-error-message
318 (concat (patcher-inconsistent-change-logs-description
322 \nPlease fix the problem and type \\[patcher-mail-diff] to try again.")))
324 (patcher-display-error-message
325 (concat (patcher-inconsistent-sources-description
329 \nPlease fix the problem and type \\[patcher-mail-diff] to try again.")))))
333 ;; ===========================================================================
334 ;; Patcher mail minor mode
335 ;; ===========================================================================
337 (defun patcher-mail-change-subject ()
338 "Read a new subject for the current project.
340 The new subject is propagated to all relevant buffers."
342 (patcher-change-subject patcher-project))
344 (defun patcher-mail-diff (&optional arg)
345 "Regenerate the diff in the current Patcher mail buffer.
347 When called interactively, use a prefix to override the diff command
348 used for this project.
350 Note that this is *not* the way to specify files affected by this patch.
351 For that, either define a permanent subproject (see `patcher-subprojects')
352 or call `patcher-mail' with a prefix argument."
354 (patcher-detect-undiffable-project patcher-project)
355 (when (or (not (patcher-project-sources patcher-project))
356 (y-or-n-p "Really regenerate the diff ? "))
358 (setf (patcher-project-diff-command patcher-project)
359 (read-shell-command "Diff command: "
360 (patcher-project-diff-command
362 (let ((change-log-buffers
363 (patcher-generated-change-logs patcher-project)))
364 (when change-log-buffers
365 (case (patcher-with-message
367 Some ChangeLog skeletons for this patch have already been generated%s.
368 The relevant ChangeLog files are the following: %s.
370 Before regenerating the diff, please answer the question below to keep the
371 current skeletons (n), regenerate all of them (y) or choose interactively (i).
373 Beware that if you regenerate the skeletons, you will loose what you have
374 possibly already filled in."
375 (if (patcher-extent 'change-logs)
376 "\nand inserted into the current mail buffer"
378 (patcher-buffers-string change-log-buffers))
379 (patcher-read-char "Regenerate ChangeLog skeletons? " "yni"))
380 (?y (patcher-ungenerate-change-logs patcher-project
382 (?i (patcher-ungenerate-change-logs patcher-project
385 :prompt "Regenerate this skeleton? ")))))
386 ;; #### NOTE: it is too complicated to decide whether or not to keep
387 ;; inserted ChangeLog entries in the mail buffer here. For instance, the
388 ;; user might have decided to keep the skeletons, but further modify them
389 ;; after the diff. So let's just remove them.
390 (patcher-delete-extent-and-region (patcher-extent 'change-logs))
391 (patcher-delete-extent-and-region (patcher-extent 'diff))
392 (patcher-diff-project patcher-project)))
394 (defun patcher-mail-insert-change-logs (&optional arg)
395 "(Re)Insert ChangeLog entries in the current Patcher mail buffer.
397 When called interactively, use a prefix argument to temporarily override
398 the ChangeLogs appearance."
400 (let ((updating (patcher-project-option patcher-project
401 :change-logs-updating)))
402 (patcher-condition-case condition
405 (patcher-error "This project does not use ChangeLogs"))
407 (when (or (and (not (patcher-extent 'change-logs))
408 (not (patcher-change-logs)))
410 ChangeLog entries already inserted. Replace? "))
411 (let ((appearance (patcher-project-option patcher-project
412 :change-logs-appearance)))
413 (when (or (not appearance) arg)
415 (let ((table '(("verbatim" . verbatim)
419 (patcher-with-message (format "\
420 %sWhich type of appearance would you like to use?"
424 This project is set to not include ChangeLogs in mail buffers.\n"))
427 Select a ChangeLog appearance (verbatim by default): "
428 table nil t nil nil "verbatim")
430 (patcher-delete-extent-and-region (patcher-extent 'change-logs))
431 (patcher-within-extent (unused 'diff)
432 (dolist (extent (patcher-change-log-extents))
433 (patcher-delete-extent-and-region extent)))
437 (patcher-insert-change-log-contents
438 patcher-project patcher-change-logs-marker))
440 (patcher-detect-ephemeral-change-logs patcher-project)
441 (patcher-detect-undiffable-project patcher-project)
442 (patcher-diff-change-logs patcher-project)
443 (patcher-insert-diff patcher-project
444 patcher-change-logs-marker :change-logs))
446 (patcher-detect-ephemeral-change-logs patcher-project)
447 (patcher-detect-undiffable-project patcher-project)
448 (patcher-delete-extent-and-region (patcher-extent 'diff))
449 (patcher-diff-all patcher-project)
450 (patcher-insert-diff patcher-project patcher-diff-marker
453 (patcher-error 'invalid-project-option
454 :change-logs-appearance appearance))))))
456 (patcher-error 'invalid-project-option
457 :change-logs-updating updating)))
459 (patcher-display-error-message "\
460 This project has already been committed, so it is impossible to show the
461 ChangeLog entries as a diff because the diff would be empty."))
463 (display-buffer (patcher-project-process-buffer patcher-project) t)
466 Error during diff. Type \\[patcher-mail-insert-change-logs] to try again."))
467 (change-logs-consistency
468 (patcher-display-error-message
470 (patcher-inconsistent-change-logs-description (nth 1 condition)
473 \nPlease fix the problem and type \\[patcher-mail-insert-change-logs] to try again.")))
475 (patcher-display-error-message
477 (patcher-inconsistent-sources-description (nth 1 condition)
480 \nPlease fix the problem and type \\[patcher-mail-insert-change-logs] to try again."))))))
482 (defun patcher-mail-first-change-log ()
483 "Switch to first ChangeLog buffer."
485 (patcher-switch-to-first-change-log patcher-project))
487 (defun patcher-mail-last-change-log ()
488 "Switch to the last ChangeLog buffer."
490 (patcher-switch-to-last-change-log patcher-project))
492 (defun patcher-mail-commit (&optional arg)
493 "Prepare to, and possibly commit a change to a project's repository.
494 The change is the one that is announced in the mail buffer.
496 When called interactively, use a prefix (ARG) to override the commit
497 command to use. Note that this is not meant to modify the source and
498 ChangeLog files affected by the commit: they are computed automatically."
500 (patcher-detect-committed-project patcher-project)
502 (setf (patcher-project-commit-command patcher-project)
503 (read-shell-command "Commit command: "
504 (patcher-project-commit-command
506 (setf (patcher-project-window-configuration patcher-project)
507 (current-window-configuration))
508 (let ((buffer (patcher-project-logmsg-buffer patcher-project)))
510 (erase-buffer buffer)
512 (setf (patcher-project-logmsg-buffer patcher-project)
514 (format "*%s Patcher Project Log Message*"
515 (patcher-project-name patcher-project)))))
516 ;; Do it first! It kills local variables.
517 (with-current-buffer buffer
518 (patcher-logmsg-mode))
519 (patcher-setup-auxiliary-buffer patcher-project buffer)))
520 (if (not (patcher-project-option patcher-project :edit-log-message))
521 (with-current-buffer (patcher-project-logmsg-buffer patcher-project)
522 (patcher-logmsg-init)
523 (patcher-condition-case nil
524 (patcher-logmsg-commit)
526 (display-buffer (patcher-project-process-buffer patcher-project) t)
527 (with-current-buffer (patcher-project-mail-buffer patcher-project)
530 Error during commit. Type \\[patcher-mail-commit] to try again.")))))
531 (pop-to-buffer (patcher-project-logmsg-buffer patcher-project))
532 (patcher-logmsg-init)))
534 (defun patcher-mail-kill ()
535 "Kill the project related to the current mail buffer."
537 (patcher-kill-project patcher-project))
539 (defcustom patcher-mail-minor-mode-string " Patch"
540 "*Patcher mail minor mode modeline string."
544 (defcustom patcher-mail-minor-mode-hook nil
545 "*Hooks to run after setting up Patcher mail minor mode."
549 (defvar patcher-mail-minor-mode-map
550 (let ((map (make-sparse-keymap 'patcher-mail-minor-mode-map)))
551 (define-key map [(control c) (control p) S] 'patcher-mail-change-subject)
552 (define-key map [(control c) (control p) d] 'patcher-mail-diff)
553 (define-key map [(control c) (control p) l]
554 'patcher-mail-insert-change-logs)
555 (define-key map [(control c) (control p) n] 'patcher-mail-first-change-log)
556 (define-key map [(control c) (control p) p] 'patcher-mail-last-change-log)
557 (define-key map [(control c) (control p) c] 'patcher-mail-commit)
558 (define-key map [(control c) (control p) k] 'patcher-mail-kill)
559 (define-key map [(control c) (control p) v] 'patcher-version)
561 ;; Patcher minor mode keymap.
564 (make-variable-buffer-local
565 (defvar patcher-mail-minor-mode nil))
567 (defun patcher-insert-patcher-header ()
568 ;; Insert a Patcher version header in the message.
570 (goto-char (point-min))
571 (unless (re-search-forward "^X-Generated-By: Patcher " nil t)
572 ;; This search can fail in case of fake mail method.
573 (when (re-search-forward
574 (concat "^" (regexp-quote mail-header-separator)) nil t)
575 (goto-char (point-at-bol))
576 (insert "X-Generated-By: " (patcher-version) "\n")))))
578 (defun patcher-mail-minor-mode (&optional arg)
579 "Toggles Patcher mail minor mode.
581 Used for mails prepared with `patcher-mail'. You're not supposed to use
582 this, unless you know what you're doing.
584 The Patcher Mail minor mode provides the following commands:
585 \\{patcher-mail-minor-mode-map}"
587 (let ((was-off (not patcher-mail-minor-mode)))
588 (setq patcher-mail-minor-mode
591 (> (prefix-numeric-value arg) 0)))
592 (when (and patcher-mail-minor-mode was-off)
593 (patcher-insert-patcher-header)
594 (run-hooks 'patcher-mail-minor-mode-hook))))
596 (add-minor-mode 'patcher-mail-minor-mode
597 patcher-mail-minor-mode-string
598 patcher-mail-minor-mode-map)
603 ;; ===========================================================================
604 ;; Mail preparation routines
605 ;; ===========================================================================
607 (defgroup patcher-mail nil
608 "Patcher settings for mail buffers."
611 (defun patcher-before-send ()
612 ;; Function hooked in the different mailing methods to perform some
613 ;; checkings prior to sending the message.
615 ;; #### NOTE: it is currently impossible (and probably not worth it) to
616 ;; #### offer an automatic ChangeLog insertion or commit operation at that
617 ;; #### point: we're already in an interactive call (the message sending
618 ;; #### pocess) and a complex trickery would be necessary in case of
619 ;; #### operation failure. So it's simpler to just abort the sending, let
620 ;; #### the user manually fix things, and re-send the message.
623 (or (patcher-extent 'diff)
624 (patcher-error "There's no diff in this message !"))
625 ;; Check for ChangeLogs:
626 (let ((check-insertion (patcher-project-option patcher-project
627 :check-change-logs-insertion)))
628 (when (and check-insertion
629 (patcher-project-option patcher-project :change-logs-appearance)
630 (not (patcher-change-logs))
631 (or (eq check-insertion t)
632 ;; all other values are considered to be like 'ask
634 You did not insert the ChangeLog entries. Proceed with sending anyway ? "))))
636 Sending aborted. Please insert the ChangeLogs first.")))
637 ;; Check commit operation:
638 (let ((check-commit (patcher-project-option patcher-project :check-commit)))
639 (when (and check-commit
640 (patcher-project-option patcher-project :commit-privilege)
641 (not (patcher-project-committed-p patcher-project))
642 (or (eq check-commit t)
643 ;; all other values are considered to be like 'ask
645 You did not commit your changes. Proceed with sending anyway ? "))))
647 Sending aborted. Please commit your changes first."))))
649 (defun patcher-after-send (&optional unused)
650 ;; Function hooked in the different mailing methods to clean up the place
651 ;; when a Patcher mail is sent.
652 (patcher-delete-project patcher-project))
654 (defun patcher-install-send-hooks ()
655 ;; Install before- and after-send hooks into the MUA.
656 (cond ((eq major-mode 'mail-mode)
657 (add-local-hook 'mail-send-hook 'patcher-before-send)
658 (push '(patcher-after-send) mail-send-actions))
659 ((eq major-mode 'message-mode)
660 (add-local-hook 'message-send-hook 'patcher-before-send)
661 ;; `message-exit-actions' is probably more appropriate than
662 ;; `message-send-actions' to perform the cleanup.
663 (with-boundp 'message-exit-actions
664 (push '(patcher-after-send) message-exit-actions)))
668 This mailing method is not fully supported by Patcher.
669 This is not critical though: Patcher won't be able to perform checks or
670 cleanups during mail sending.
672 Please report to <didier@xemacs.org>."
676 ;; Patcher FakeMail mode ====================================================
678 (defun patcher-fakemail-send ()
679 "Pretend to send a fake Patcher mail.
681 Only perform the usual cleanup after real Patcher mails are sent."
683 (patcher-before-send)
685 (kill-buffer (current-buffer)))
687 (defvar patcher-fakemail-mode-map
688 (let ((map (make-sparse-keymap 'patcher-fakemail-mode-map)))
689 (define-key map [(control c) (control c)] 'patcher-fakemail-send)
692 (defun patcher-fakemail-mode ()
693 "Sets up Patcher-FakeMail major mode.
695 Used for editing a fake Patcher mail.
697 The following command are available in a Fake Mail buffer:
698 \\{patcher-fakemail-mode-map}"
700 (kill-all-local-variables)
701 (setq major-mode 'patcher-fakemail)
702 (setq mode-name "Patcher-FakeMail")
703 (use-local-map patcher-fakemail-mode-map)
704 (run-hooks 'patcher-logmsg-mode-hook))
707 ;; Interface to the different mailing methods ================================
709 (put 'patcher-with-mail-parameters 'lisp-indent-function 1)
710 (defmacro* patcher-with-mail-parameters (project &body body)
711 ;; Wrap BODY in a let construct possibly defining user-full-name and
712 ;; user-mail-address by Patcher options.
713 ;; Return the value of BODY execution.
714 ;; #### NOTE: why is it called like this ? Because I'm sure one day or
715 ;; #### another, some sucker will ask for more parameters, like the mail
716 ;; #### signature for instance ;-)
717 `(let ((user-full-name (or (patcher-project-option ,project :user-name)
719 (user-mail-address (or (patcher-project-option ,project :user-mail)
724 (defun patcher-mail-compose-mail (project subject)
725 "Prepare a patch-related mail with the `compose-mail' function.
727 This function uses the `:to-address' project option to determine the email
728 address for sending the message. Otherwise, the address is prompted for.
730 See also the `mail-user-agent' variable."
731 (patcher-with-mail-parameters project
732 (compose-mail (or (patcher-project-option project :to-address)
733 (read-string "To address: "))
735 (patcher-install-send-hooks))
738 (defun patcher-mail-sendmail (project subject)
739 "Prepare a patch-related mail with the `mail' function.
740 This method requires the `sendmail' library.
742 This function uses the `:to-address' project option to determine the email
743 address for sending the message. Otherwise, the address is prompted for."
745 (patcher-with-mail-parameters project
746 (mail nil (or (patcher-project-option project :to-address)
747 (read-string "To address: "))
749 (patcher-install-send-hooks))
751 (defun patcher-mail-message (project subject)
752 "Prepare a patch-related mail with the `message-mail' function.
753 This method requires the `message' library.
755 This function uses the `:to-address' project option to determine the email
756 address for sending the message. Otherwise, the address is prompted for."
758 (patcher-with-mail-parameters project
759 (message-mail (or (patcher-project-option project :to-address)
760 (read-string "To address: "))
762 (patcher-install-send-hooks))
764 (defcustom patcher-mail-run-gnus 'prompt
765 "*Whether Patcher should run Gnus.
767 The 'gnus mailing method of Patcher needs a running Gnus session.
768 If Gnus is not running at the time it is needed, Patcher can start
769 it (or not) depending on this variable:
770 - if nil, Patcher will abort execution,
771 - it 'prompt (the default), Patcher will ask you what to do,
772 - if t Patcher will unconditionally start Gnus.
774 See also the function `patcher-mail-gnus'."
776 :type '(radio (const :tag "never" nil)
777 (const :tag "ask user" prompt)
778 (const :tag "as needed" t)))
780 (defcustom patcher-mail-run-gnus-other-frame t
781 "*Whether Patcher should start Gnus in a new frame.
783 This is used in case Patcher has to start Gnus by itself \(see the
784 variable `patcher-mail-run-gnus'). Possible values are:
785 - nil: start Gnus in the current frame,
786 - t: start Gnus in a new frame,
787 - 'follow: start Gnus in a new frame, and also use this frame to prepare
788 the new Patcher message."
790 :type '(radio (const :tag "Use current frame" nil)
791 (const :tag "Create new frame" t)
792 (const :tag "Create new frame, and use it for patcher"
795 (defun patcher-mail-run-gnus ()
796 ;; Start a gnus session.
799 (cond ((eq patcher-mail-run-gnus-other-frame t)
800 (save-selected-frame (gnus-other-frame)))
801 ((eq patcher-mail-run-gnus-other-frame 'follow)
803 ((not patcher-mail-run-gnus-other-frame)
807 Invalid value for `patcher-mail-run-gnus-other-frame': "
808 patcher-mail-run-gnus-other-frame)))))
810 (globally-declare-boundp 'gnus-article-copy)
812 (defun patcher-mail-gnus (project subject)
813 "Prepare a patch-related mail with the `gnus-post-news' function.
814 Don't worry, this function can also send mails ;-). This method
815 requires that you have Gnus *running* in your XEmacs session \(see
816 the variable `patcher-mail-run-gnus').
818 This function uses the `:gnus-group' project option to determine the Gnus
819 group to use \(as if you had typed `C-u a' on that group in the Group
820 buffer). Otherwise, the group is prompted for."
822 (with-fboundp '(gnus-alive-p gnus-post-news message-goto-body)
823 (unless (gnus-alive-p)
824 (cond ((not patcher-mail-run-gnus)
826 "The 'gnus mailing method requires a running Gnus session"))
827 ((eq patcher-mail-run-gnus t)
828 (patcher-mail-run-gnus))
829 ((eq patcher-mail-run-gnus 'prompt)
830 (if (y-or-n-p "Gnus is not currently running. Start it ? ")
831 (patcher-mail-run-gnus)
833 "The 'gnus mailing method requires a running Gnus session")))
835 (patcher-error "Invalid value for `patcher-mail-run-gnus': "
836 patcher-mail-run-gnus))))
837 (let ((gnus-newsgroup-name (or (patcher-project-option project
839 (read-string "Gnus group name: ")))
841 (patcher-with-mail-parameters project
842 (gnus-post-news 'post gnus-newsgroup-name)))
843 (patcher-goto-subject)
846 (patcher-install-send-hooks)))
848 (defun* patcher-mail-fake
849 (project subject &aux (buffer (generate-new-buffer "*Patcher Fake Mail*")))
850 "Prepare a patch-related fake mail.
851 Use this function if you want to do all that Patcher can do, apart from
852 sending a real mail. This function generates a fake mail buffer which acts
853 as a standard Patcher mail buffer, apart from the fact that when you type
854 \\<patcher-fakemail-mode-map>\\[patcher-fakemail-send] in it, it doesn't
855 really send a mail, but just clean things up."
856 (switch-to-buffer buffer)
857 (insert "Subject: " subject "\n")
858 (patcher-fakemail-mode))
860 (defun patcher-mail-setup (project)
861 ;; Setup patcher-mail-minor-mode and initialize Patcher local variables in
862 ;; mails (both generated or adapted).
863 (setf (patcher-project-mail-buffer project) (current-buffer))
864 (patcher-mail-minor-mode t)
865 (cd (patcher-project-command-directory project))
866 (setq patcher-project project))
869 ;; Mail generation entry point ==============================================
872 (defun* patcher-mail (project)
873 "Prepare a mail about a patch to apply on PROJECT.
875 When called interactively, prompt for a project name
876 \(see the variables `patcher-projects' and `patcher-subprojects') and
877 a subject for the mail.
879 With a additional prefix argument, create a temporary subproject by
880 prompting for an optional subdirectory and specific files as well. Files,
881 directories and even wildcards are acceptable in your specification.
883 With a prefix of 1, offer to relocate the project to another directory.
884 With a prefix of -1, do the same, but also create a temporary subproject of
885 the relocated project."
886 (interactive (patcher-project-interactive current-prefix-arg))
887 (funcall (intern (concat "patcher-mail-"
889 (patcher-project-option project :mail-method t))))
891 (patcher-prefixed-subject project))
892 (patcher-mail-setup project)
893 (let ((mail-prologue (patcher-project-option project :mail-prologue)))
894 (unless (zerop (length mail-prologue))
895 (insert "\n" mail-prologue)))
898 (when (patcher-project-option project :change-logs-updating)
900 (patcher-project-option project :change-logs-appearance)))
901 (when (and appearance (not (eq appearance 'patch)))
902 (setq patcher-change-logs-marker (point-marker))
904 (setq patcher-diff-marker (point-marker))
905 (patcher-diff-project project)))
908 ;; Mail adaptation entry point ==============================================
911 (defun patcher-mail-adapt (project)
912 "Same as `patcher-mail', but for existing mails."
913 (interactive (patcher-project-interactive current-prefix-arg))
914 (patcher-goto-subject)
918 (while (progn (forward-line 1)
919 (looking-at "[ \t]")))
922 (skip-chars-backward " \t")
923 (setq old-subject (buffer-substring beg (point)))
924 (delete-region beg end)
925 (while (string-match "\n[\t ]+" old-subject)
926 (setq old-subject (replace-match " " t t old-subject)))
927 (insert (patcher-prefixed-subject project old-subject)))
928 (patcher-install-send-hooks)
929 (patcher-mail-setup project)
930 ;; #### NOTE: currently, I have simply discarded the mail-prologue
931 ;; #### insertion for adapted mails. This is because mail adaptation is
932 ;; #### mostly for replies in which you probably don't want the standard
933 ;; #### prologue. However, this could be turned into a standard option.
934 ;; (let ((mail-prologue (patcher-project-option project :mail-prologue)))
935 ;; (unless (zerop (length mail-prologue))
936 ;; (insert "\n" mail-prologue)))
937 (patcher-goto-signature)
938 (when (patcher-project-option project :change-logs-updating)
940 (patcher-project-option project :change-logs-appearance)))
941 (when (and appearance (not (eq appearance 'patch)))
942 (setq patcher-change-logs-marker (point-marker))
944 (setq patcher-diff-marker (point-marker))
945 (patcher-diff-project project))
950 ;; ==========================================================================
952 ;; ==========================================================================
954 ;; Patcher Gnus Summary minor mode ==========================================
956 (defun patcher-gnus-summary-followup (&optional arg)
957 "Prepare a Patcher followup from the Gnus Summary buffer.
958 See `patcher-mail' for more information."
960 (declare-fboundp (gnus-summary-followup nil))
961 (call-interactively 'patcher-mail-adapt))
963 (defun patcher-gnus-summary-followup-with-original (&optional arg)
964 "Prepare a Patcher followup from the Gnus Summary buffer.
965 The original message is yanked.
966 See `patcher-mail' for more information."
968 (declare-fboundp (gnus-summary-followup-with-original nil))
969 (call-interactively 'patcher-mail-adapt))
971 (defun patcher-gnus-summary-reply (&optional arg)
972 "Prepare a Patcher reply from the Gnus Summary buffer.
973 See `patcher-mail' for more information."
975 ;; #### NOTE: it is strange that this function's first argument is not
976 ;; #### mandatory, as in the 3 other ones.
977 (declare-fboundp (gnus-summary-reply))
978 (call-interactively 'patcher-mail-adapt))
980 (defun patcher-gnus-summary-reply-with-original (&optional arg)
981 "Prepare a Patcher reply from the Gnus Summary buffer.
982 The original message is yanked.
983 See `patcher-mail' for more information."
985 (declare-fboundp (gnus-summary-reply-with-original nil))
986 (call-interactively 'patcher-mail-adapt))
988 (defcustom patcher-gnus-summary-minor-mode-string " Patch"
989 "*Patcher Gnus Summary minor mode modeline string."
993 (defcustom patcher-gnus-summary-minor-mode-hook nil
994 "*Hooks to run after setting up Patcher Gnus Summary minor mode."
998 (defvar patcher-gnus-summary-minor-mode-map
999 (let ((map (make-sparse-keymap 'patcher-gnus-summary-minor-mode-map)))
1000 (define-key map [(control c) (control p) f]
1001 'patcher-gnus-summary-followup)
1002 (define-key map [(control c) (control p) F]
1003 'patcher-gnus-summary-followup-with-original)
1004 (define-key map [(control c) (control p) r]
1005 'patcher-gnus-summary-reply)
1006 (define-key map [(control c) (control p) R]
1007 'patcher-gnus-summary-reply-with-original)
1009 ;; Patcher Gnus Summary minor mode keymap.
1012 (make-variable-buffer-local
1013 (defvar patcher-gnus-summary-minor-mode nil))
1015 (defun patcher-gnus-summary-minor-mode (&optional arg)
1016 "Toggles Patcher Gnus Summary minor mode.
1018 Used for Patcher messages composed as Gnus replies and followups.
1019 You're not supposed to use this, unless you know what you're doing.
1021 The Patcher Gnus Summary minor mode provides the following commands:
1022 \\{patcher-gnus-summary-minor-mode-map}"
1024 (let ((was-off (not patcher-gnus-summary-minor-mode)))
1025 (setq patcher-gnus-summary-minor-mode
1028 (> (prefix-numeric-value arg) 0)))
1029 (when (and patcher-gnus-summary-minor-mode was-off)
1030 (run-hooks 'patcher-gnus-summary-minor-mode-hook))))
1033 'patcher-gnus-summary-minor-mode
1034 patcher-gnus-summary-minor-mode-string
1035 patcher-gnus-summary-minor-mode-map)
1038 ;; Patcher Gnus Article minor mode ==========================================
1040 (defcustom patcher-gnus-article-minor-mode-string " Patch"
1041 "*Patcher Gnus Article minor mode modeline string."
1045 (defcustom patcher-gnus-article-minor-mode-hook nil
1046 "*Hooks to run after setting up Patcher Gnus Article minor mode."
1050 (defvar patcher-gnus-article-minor-mode-map
1051 (let ((map (make-sparse-keymap 'patcher-gnus-article-minor-mode-map)))
1052 (define-key map [(control c) (control p) f]
1053 'patcher-gnus-summary-followup)
1054 (define-key map [(control c) (control p) F]
1055 'patcher-gnus-summary-followup-with-original)
1056 (define-key map [(control c) (control p) r]
1057 'patcher-gnus-summary-reply)
1058 (define-key map [(control c) (control p) R]
1059 'patcher-gnus-summary-reply-with-original)
1061 ;; Patcher Gnus Article minor mode keymap.
1064 (make-variable-buffer-local
1065 (defvar patcher-gnus-article-minor-mode nil))
1067 (defun patcher-gnus-article-minor-mode (&optional arg)
1068 "Toggles Patcher Gnus Article minor mode.
1070 Used for Patcher messages composed as Gnus replies and followups.
1071 You're not supposed to use this, unless you know what you're doing.
1073 The Patcher Gnus Article minor mode provides the following commands:
1074 \\{patcher-gnus-article-minor-mode-map}"
1076 (let ((was-off (not patcher-gnus-article-minor-mode)))
1077 (setq patcher-gnus-article-minor-mode
1080 (> (prefix-numeric-value arg) 0)))
1081 (when (and patcher-gnus-article-minor-mode was-off)
1082 (run-hooks 'patcher-gnus-article-minor-mode-hook))))
1085 'patcher-gnus-article-minor-mode
1086 patcher-gnus-article-minor-mode-string
1087 patcher-gnus-article-minor-mode-map)
1090 ;; Insinuation ==============================================================
1093 (defun patcher-insinuate-gnus ()
1094 "Hook Patcher functionality into Gnus.
1096 This function should be called from your gnusrc file."
1097 (add-hook 'gnus-summary-mode-hook
1098 (lambda () (patcher-gnus-summary-minor-mode t)))
1099 (add-hook 'gnus-article-mode-hook
1100 (lambda () (patcher-gnus-article-minor-mode t))))
1103 (provide 'patcher-mail)
1105 ;;; patcher-mail.el ends here