Initial revision
authorChris Hanson <org/chris-hanson/cph>
Sun, 21 Apr 1991 01:49:14 +0000 (01:49 +0000)
committerChris Hanson <org/chris-hanson/cph>
Sun, 21 Apr 1991 01:49:14 +0000 (01:49 +0000)
v7/src/edwin/sendmail.scm [new file with mode: 0644]

diff --git a/v7/src/edwin/sendmail.scm b/v7/src/edwin/sendmail.scm
new file mode 100644 (file)
index 0000000..4e7921b
--- /dev/null
@@ -0,0 +1,518 @@
+;;; -*-Scheme-*-
+;;;
+;;;    $Header: /Users/cph/tmp/foo/mit-scheme/mit-scheme/v7/src/edwin/sendmail.scm,v 1.1 1991/04/21 01:49:14 cph Exp $
+;;;
+;;;    Copyright (c) 1991 Massachusetts Institute of Technology
+;;;
+;;;    This material was developed by the Scheme project at the
+;;;    Massachusetts Institute of Technology, Department of
+;;;    Electrical Engineering and Computer Science.  Permission to
+;;;    copy this software, to redistribute it, and to use it for any
+;;;    purpose is granted, subject to the following restrictions and
+;;;    understandings.
+;;;
+;;;    1. Any copy made of this software must include this copyright
+;;;    notice in full.
+;;;
+;;;    2. Users of this software agree to make their best efforts (a)
+;;;    to return to the MIT Scheme project any improvements or
+;;;    extensions that they make, so that these may be included in
+;;;    future releases; and (b) to inform MIT of noteworthy uses of
+;;;    this software.
+;;;
+;;;    3. All materials developed as a consequence of the use of this
+;;;    software shall duly acknowledge such use, in accordance with
+;;;    the usual standards of acknowledging credit in academic
+;;;    research.
+;;;
+;;;    4. MIT has made no warrantee or representation that the
+;;;    operation of this software will be error-free, and MIT is
+;;;    under no obligation to provide any services, by way of
+;;;    maintenance, update, or otherwise.
+;;;
+;;;    5. In conjunction with products arising from the use of this
+;;;    material, there shall be no use of the name of the
+;;;    Massachusetts Institute of Technology nor of any adaptation
+;;;    thereof in any advertising, promotional, or sales literature
+;;;    without prior written consent from MIT in each case.
+;;;
+;;; NOTE: Parts of this program (Edwin) were created by translation
+;;; from corresponding parts of GNU Emacs.  Users should be aware that
+;;; the GNU GENERAL PUBLIC LICENSE may apply to these parts.  A copy
+;;; of that license should have been included along with this file.
+;;;
+
+;;;; Mail Sending
+
+(declare (usual-integrations))
+\f
+(define-variable mail-reply-buffer
+  ""
+  false
+  (lambda (object) (or (false? object) (buffer? object))))
+
+(define-variable mail-default-reply-to
+  "Address to insert as default Reply-to field of outgoing messages."
+  false
+  string-or-false?)
+
+(define-variable mail-self-blind
+  "True means insert BCC to self in messages to be sent.
+This is done when the message is initialized,
+so you can remove or alter the BCC field to override the default."
+  false
+  boolean?)
+
+(define-variable mail-archive-file-name
+  "Name of file to write all outgoing messages in, or false for none."
+  false
+  string-or-false?)
+
+(define-variable mail-header-separator
+  "Line used to separate headers from text in messages being composed."
+  "--text follows this line--"
+  string?)
+
+(define-variable mail-interactive
+  "True means when sending a message wait for and display errors.
+False means let mailer mail back a message to report errors."
+  false
+  boolean?)
+
+(define-variable sendmail-program
+  "Filename of sendmail program."
+  "/usr/lib/sendmail"
+  string?)
+
+(define-variable mail-yank-ignored-headers
+  "Delete these headers from old message when it's inserted in a reply."
+  "^via:\\|^mail-from:\\|^origin:\\|^status:\\|^remailed\\|^received:\\|^[a-z-]*message-id:\\|^summary-line:\\|^to:\\|^cc:\\|^subject:\\|^in-reply-to:\\|^return-path:")
+
+(define-variable send-mail-procedure
+  "Procedure to call to send the current buffer as mail.
+The headers are delimited by a string found in mail-header-separator."
+  (lambda () (sendmail-send-it)))
+
+(define-variable mail-setup-hook
+  "An event distributor invoked immediately after a mail buffer initialized."
+  (make-event-distributor))
+\f
+(define-command mail
+  "Edit a message to be sent.  Argument means resume editing (don't erase).
+Returns with message buffer selected.
+While editing message, type C-c C-c to send the message and exit.
+
+Separate names of recipients with commas.
+
+Various special commands starting with C-c are available in sendmail mode
+to move to message header fields.
+
+If mail-self-blind is non-false, a BCC to yourself is inserted
+when the message is initialized.
+
+If mail-default-reply-to is non-false, it should be an address (a string);
+a Reply-to: field with that address is inserted.
+
+If mail-archive-file-name is non-false, an FCC field with that file name
+is inserted."
+  "P"
+  (lambda (no-erase?)
+    (make-mail-buffer no-erase? select-buffer false false false false false)))
+
+(define-command mail-other-window
+  "Like `mail' command, but display mail buffer in another window."
+  "P"
+  (lambda (no-erase?)
+    (make-mail-buffer no-erase? select-buffer-other-window
+                     false false false false false)))
+
+(define (make-mail-buffer no-erase? select-buffer
+                         to subject in-reply-to cc reply-buffer)
+  (let ((buffer (find-or-create-buffer "*mail*")))
+    (select-buffer buffer)
+    (if (and (not no-erase?)
+            (or (not (buffer-modified? buffer))
+                (prompt-for-confirmation?
+                 "Unsent message being composed; erase it")))
+       (begin
+         (set-buffer-default-directory! buffer (->pathname "~/"))
+         (setup-buffer-auto-save! buffer)
+         (region-delete! (buffer-unclipped-region buffer))
+         (mail-setup buffer to subject in-reply-to cc reply-buffer)))))
+\f
+(define (mail-setup buffer to subject in-reply-to cc reply-buffer)
+  (guarantee-mail-aliases)
+  (set-buffer-major-mode! buffer (ref-mode-object mail))
+  (local-set-variable! mail-reply-buffer reply-buffer)
+  (let ((point (mark-left-inserting-copy (buffer-start buffer)))
+       (fill
+        (lambda (start end)
+          (fill-region (make-region start end)
+                       "\t"
+                       (ref-variable fill-column)))))
+    (insert-string "To: " point)
+    (if to
+       (begin
+         (insert-string to point)
+         (fill (buffer-start buffer) point)))
+    (insert-newline point)
+    (if cc
+       (let ((start (mark-right-inserting point)))
+         (insert-string "CC: " point)
+         (insert-string cc point)
+         (fill start point)
+         (insert-newline point)))
+    (if in-reply-to
+       (begin
+         (insert-string "In-reply-to: " point)
+         (insert-string in-reply-to point)
+         (insert-newline point)))
+    (insert-string "Subject: " point)
+    (if subject
+       (insert-string subject point))
+    (insert-newline point)
+    (let ((mail-default-reply-to (ref-variable mail-default-reply-to)))
+      (if mail-default-reply-to
+         (begin
+           (insert-string "Reply-to: " point)
+           (insert-string mail-default-reply-to point)
+           (insert-newline point))))
+    (if (ref-variable mail-self-blind)
+       (begin
+         (insert-string "BCC: " point)
+         (insert-string (unix/current-user-name) point)
+         (insert-newline point)))
+    (let ((mail-archive-file-name (ref-variable mail-archive-file-name)))
+      (if mail-archive-file-name
+         (begin
+           (insert-string "FCC: " point)
+           (insert-string mail-archive-file-name point)
+           (insert-newline point))))
+    (insert-string (ref-variable mail-header-separator) point)
+    (insert-newline point)
+    (mark-temporary! point))
+  (set-buffer-point! buffer
+                    (if to
+                        (buffer-end buffer)
+                        (mail-position-on-field buffer "To")))
+  (if (not (or to subject in-reply-to))
+      (buffer-not-modified! buffer))
+  (event-distributor/invoke! (ref-variable mail-setup-hook)))
+\f
+(define-major-mode mail text "Mail"
+  "Major mode for editing mail to be sent.
+Separate names of recipients (in To: and CC: fields) with commas.
+Like Text Mode but with these additional commands:
+C-c C-s  mail-send (send the message)    C-c C-c  mail-send-and-exit
+C-c C-f  move to a header field (and create it if there isn't):
+        C-c C-f C-t  move to To:       C-c C-f C-s  move to Subj:
+        C-c C-f C-b  move to BCC:      C-c C-f C-c  move to CC:
+C-c C-w  mail-signature (insert ~/.signature at end).
+C-c C-y  mail-yank-original (insert current message, in Rmail).
+C-c C-q  mail-fill-yanked-message (fill what was yanked)."
+  (local-set-variable!
+   paragraph-start
+   (string-append "^"
+                 (re-quote-string (ref-variable mail-header-separator))
+                 "$\\|^[ \t]*[-_][-_][-_]+$\\|"
+                 (ref-variable paragraph-start)))
+  (local-set-variable!
+   paragraph-separate
+   (string-append "^"
+                 (re-quote-string (ref-variable mail-header-separator))
+                 "$\\|^[ \t]*[-_][-_][-_]+$\\|"
+                 (ref-variable paragraph-separate)))
+  (event-distributor/invoke! (ref-variable mail-mode-hook)))
+
+(define-variable mail-mode-hook
+  "An event distributor that is invoked when entering Mail mode."
+  (make-event-distributor))
+
+(define-prefix-key 'mail #\C-c 'prefix-char)
+(define-prefix-key 'mail '(#\C-c #\C-f) 'prefix-char)
+
+(define-key 'mail '(#\C-c #\?) 'describe-mode)
+(define-key 'mail '(#\C-c #\C-f #\C-t) 'mail-to)
+(define-key 'mail '(#\C-c #\C-f #\C-b) 'mail-bcc)
+(define-key 'mail '(#\C-c #\C-f #\C-c) 'mail-cc)
+(define-key 'mail '(#\C-c #\C-f #\C-s) 'mail-subject)
+(define-key 'mail '(#\C-c #\C-w) 'mail-signature)
+(define-key 'mail '(#\C-c #\C-y) 'mail-yank-original)
+(define-key 'mail '(#\C-c #\C-q) 'mail-fill-yanked-message)
+(define-key 'mail '(#\C-c #\C-c) 'mail-send-and-exit)
+(define-key 'mail '(#\C-c #\C-s) 'mail-send)
+
+(define-command mail-send-and-exit
+  "Send message like mail-send, then, if no errors, exit from mail buffer.
+Prefix arg means don't delete this window."
+  "P"
+  (lambda (argument)
+    ((ref-command mail-send))
+    (bury-buffer (current-buffer))
+    (if (and (not argument)
+            (not (window-has-no-neighbors? (current-window)))
+            (eq? (ref-mode-object rmail)
+                 (buffer-major-mode (window-buffer (other-window)))))
+       (window-delete! (current-window))
+       (select-buffer (previous-buffer)))))
+
+(define-command mail-send
+  "Send the message in the current buffer.
+If  mail-interactive  is non-false, wait for success indication
+or error messages, and inform user.
+Otherwise any failure is reported in a message back to
+the user from the mailer."
+  ()
+  (lambda ()
+    (message "Sending...")
+    ((ref-variable send-mail-procedure))
+    (buffer-not-modified! (current-buffer))
+    (delete-auto-save-file! (current-buffer))
+    (message "Sending...done")))
+\f
+(define-command mail-to
+  "Move point to end of To field."
+  ()
+  (lambda ()
+    (set-current-point! (mail-position-on-field (current-buffer) "To"))))
+
+(define-command mail-subject
+  "Move point to end of Subject field."
+  ()
+  (lambda ()
+    (set-current-point! (mail-position-on-field (current-buffer) "Subject"))))
+
+(define-command mail-cc
+  "Move point to end of CC field."
+  ()
+  (lambda ()
+    (set-current-point! (mail-position-on-cc-field (current-buffer) "CC"))))
+
+(define-command mail-bcc
+  "Move point to end of BCC field."
+  ()
+  (lambda ()
+    (set-current-point! (mail-position-on-cc-field (current-buffer) "BCC"))))
+
+(define (mail-position-on-field buffer field)
+  (let ((start (buffer-start buffer)))
+    (mail-field-end! start (mail-header-end start (buffer-end buffer)) field)))
+
+(define (mail-position-on-cc-field buffer field)
+  (let ((start (buffer-start buffer)))
+    (let ((end (mail-header-end start (buffer-end buffer))))
+      (or (mail-field-end start end field)
+         (mail-insert-field (mail-field-end! start end "To") field)))))
+
+(define (mail-header-end start end)
+  (mail-match-header-separator start end)
+  (skip-chars-backward "\n" (re-match-start 0) start))
+
+(define (mail-match-header-separator start end)
+  (if (not (re-search (string-append
+                      "^"
+                      (re-quote-string (ref-variable mail-header-separator))
+                      "$")
+                     false start end))
+      (editor-error "Can't find mail-header-separator")))
+
+(define (mail-field-end! start end field)
+  (or (mail-field-end start end field)
+      (mail-insert-field end field)))
+
+(define (mail-field-end start end field)
+  (and (re-search (string-append "^" field ":[ \t]*") true start end)
+       (let ((field-start (re-match-end 0)))
+        (if (re-search "^[^ \t]" false field-start end)
+            (skip-chars-backward "\n" (re-match-start 0) field-start)
+            end))))
+
+(define (mail-insert-field end field)
+  (let ((end (mark-left-inserting-copy end)))
+    (guarantee-newline end)
+    (insert-string field end)
+    (insert-string ": " end)
+    (if (line-end? end)
+       (begin
+         (mark-temporary! end)
+         end)
+       (begin
+         (insert-newline end)
+         (mark-temporary! end)
+         (mark-1+ end)))))
+\f
+(define-command mail-signature
+  "Sign letter with contents of ~/.signature file."
+  ()
+  (lambda ()
+    (insert-file (buffer-end (current-buffer)) "~/.signature")))
+
+(define-command mail-yank-original
+  "Insert the message being replied to, if any (in rmail).
+Puts point after the text and mark before.
+Indents each nonblank line ARG spaces (default 3).
+Just \\[universal-argument] as argument means don't indent
+and don't delete any header fields."
+  "P"
+  (lambda (argument)
+    (let ((mail-reply-buffer (ref-variable mail-reply-buffer)))
+      (if mail-reply-buffer
+         (begin
+           (for-each (lambda (window)
+                       (if (not (window-has-no-neighbors? window))
+                           (window-delete! window)))
+                     (buffer-windows mail-reply-buffer))
+           (let ((end (mark-left-inserting-copy (current-point))))
+             (let ((start (mark-right-inserting-copy end)))
+               (insert-region (buffer-start mail-reply-buffer)
+                              (buffer-end mail-reply-buffer)
+                              start)
+               (if (not (line-end? end))
+                   (insert-newline end))
+               (if (not (command-argument-multiplier-only?))
+                   (begin
+                     (mail-yank-clear-headers start end)
+                     (indent-rigidly start end (or argument 3))))
+               (mark-temporary! start)
+               (mark-temporary! end)
+               (push-current-mark! start)
+               (set-current-point! end))))))))
+
+(define (mail-yank-clear-headers start end)
+  (let ((start (mark-left-inserting-copy start))
+       (end
+        (mark-left-inserting-copy
+         (if (re-search "\n\n" false start end)
+             (mark1+ (re-match-start 0))
+             end)))
+       (mail-yank-ignored-headers (ref-variable mail-yank-ignored-headers)))
+    (do ()
+       ((not (re-search mail-yank-ignored-headers true start end)))
+      (move-mark-to! start (re-match-start 0))
+      (delete-string start
+                    (if (re-search "^[^ \t]" false (line-end start 0) end)
+                        (re-match-start 0)
+                        end)))
+    (mark-temporary! start)
+    (mark-temporary! end)))
+\f
+(define (sendmail-send-it)
+  (let ((error-buffer
+        (and (ref-variable mail-interactive)
+             (temporary-buffer " sendmail errors")))
+       (temp-buffer (temporary-buffer " sendmail temp"))
+       (mail-buffer (current-buffer))
+       (user-name (unix/current-user-name)))
+    (with-selected-buffer temp-buffer
+      (lambda ()
+       (let ((start (buffer-start temp-buffer))
+             (end (buffer-end temp-buffer)))
+         (insert-region (buffer-start mail-buffer)
+                        (buffer-end mail-buffer)
+                        start)
+         (if (not (line-start? end))
+             (insert-char #\newline end))
+         (mail-match-header-separator start end)
+         (let ((header-end (mark-left-inserting-copy (delete-match))))
+           ;; Delete any blank lines in the header.
+           (do ((start start (replace-match "\n")))
+               ((not (re-search "\n\n+" false start header-end))))
+           (expand-mail-aliases start header-end)
+           (if (re-search "^FCC:" true start header-end)
+               (mail-do-fcc temp-buffer header-end))
+           ;; If there is a From and no Sender, put in a Sender.
+           (if (and (re-search "^From:" true start header-end)
+                    (not (re-search "^Sender:" true start header-end)))
+               (begin
+                 (insert-string "\nSender: " header-end)
+                 (insert-string user-name header-end)))
+           ;; Don't send out a blank subject line.
+           (if (re-search "^Subject:[ \t]*\n" true start header-end)
+               (delete-match)))
+         (apply run-synchronous-process
+                (make-region start end)
+                (and error-buffer (buffer-end error-buffer))
+                (ref-variable sendmail-program)
+                "-oi" "-t"
+                ;; Always specify who from, since some systems have
+                ;; broken sendmails.
+                "-f" user-name
+                (if error-buffer
+                    '()
+                    ;; These mean "report errors by mail"
+                    ;; and "deliver in background".
+                    '("-oem" "-odb")))
+         (if error-buffer
+             (let ((end (buffer-end error-buffer)))
+               (do ((start (buffer-start error-buffer) (replace-match "; ")))
+                   ((not (re-search "\n+ *" false start end)))))))))
+    (kill-buffer temp-buffer)
+    (if error-buffer
+       (let ((errors
+              (extract-string (buffer-start error-buffer)
+                              (buffer-end error-buffer))))
+         (kill-buffer error-buffer)
+         (if (not (string-null? errors))
+             (editor-error "Sending...failed to " errors))))))
+\f
+(define (mail-do-fcc mail-buffer header-end)
+  (let ((pathnames (digest-fcc-headers (buffer-start mail-buffer) header-end))
+       (temp-buffer (temporary-buffer " rmail output")))
+    (let ((start (buffer-start temp-buffer))
+         (end (buffer-end temp-buffer)))
+      (insert-newline end)
+      (insert-string "From " end)
+      (insert-string (unix/current-user-name) end)
+      (insert-string " " end)
+      (insert-string (unix/file-time->string (unix/current-file-time)) end)
+      (insert-newline end)
+      (insert-region (buffer-start mail-buffer)
+                    (buffer-end mail-buffer)
+                    end)
+      (insert-newline end)
+      ;; ``Quote'' "^From " as ">From "
+      ;;  (note that this isn't really quoting, as there is no requirement
+      ;;   that "^[>]+From " be quoted in the same transparent way.)
+      (let ((m (mark-right-inserting-copy (mark+ start 2))))
+       (do ()
+           ((not (re-search "^From " false m end)))
+         (move-mark-to! m (re-match-end 0))
+         (insert-string ">" (re-match-start 0)))
+       (mark-temporary! m))
+      (for-each (lambda (pathname)
+                 (let ((buffer (pathname->buffer pathname)))
+                   (if buffer
+                       (insert-region start end (buffer-end buffer))
+                       (append-to-file (make-region start end)
+                                       pathname
+                                       true))))
+               pathnames)
+      (kill-buffer temp-buffer))))
+
+(define (digest-fcc-headers start header-end)
+  (let ((m (mark-right-inserting-copy start)))
+    (let loop ((pathnames '()))
+      (if (re-search "^FCC:[ \t]*\\([^ \t\n]+\\)" true m header-end)
+         (let ((filename
+                (extract-string (re-match-start 1) (re-match-end 1))))
+           (move-mark-to! m (line-start (re-match-start 0) 0))
+           (delete-string m (line-start m 1))
+           (loop (cons (string->pathname filename) pathnames)))
+         (begin
+           (mark-temporary! m)
+           pathnames)))))
+
+(define-integrable (re-search regexp case-fold-search start end)
+  (re-search-buffer-forward (re-compile-pattern regexp case-fold-search)
+                           case-fold-search
+                           false
+                           (mark-group start)
+                           (mark-index start)
+                           (mark-index end)))
+
+(define (guarantee-mail-aliases)
+  unspecific)
+
+(define (expand-mail-aliases start end)
+  start end
+  unspecific)
\ No newline at end of file