[net.sources] Readnews for Gosling Emacs

jim (02/14/83)

This is an mlisp package, rnews.ml, for reading the news in Gosling
Emacs.  It is similar to rmail.ml, and in fact depends on some of the
functions in rmail.ml.  Our rmail.ml is modified, but I think this will
also work with the unmodified rmail.ml.  If it doesn't I'm sure I'll
hear about it (no flames about distributing untested software, please.
You get what you pay for.).

Rnews is undocumented except for the first few lines of comments in the
source.  It has bugs, particularly in error recovery.  If you make
improvements to it, please send them back to me.

Rnews needs a modified readnews in order to work.  This is because
there is no way to ask readnews for a list of articles to be read
without also getting the articles themselves (readnews -p).  Readnews -l
doesn't work because it gives article IDs but not the name of the spool
file containing the article.

You need to modify three of the source files of readnews:  rparams.h,
process.c, and readr.c.  With these mods, 'readnews -e' (for emacs)
will produce a list of unread articles with their spool file names.
This list becomes the rnews directory.  Rnews visits the spool files
directly.  If your news system is properly installed, you won't be able
to change the spool file (although you can edit the buffer, which can
be convenient).

To install rnews, make the changes to readnews, then install rnews.ml
in your emacs/maclib directory.  In your profile, put an autoload for
rnews.ml:

	(autoload "rnews" "rnews.ml")

Then enter emacs and do ESC-^X-rnews.

The rnews directory is kept in your ~/Messages directory, so if you
don't use rmail you might have to make this directory before you can
use rnews.

Here are the mods to readnews, followed by rnews.ml:

% diff -c /usr/src/cmd/news/src/rparams.h rparams.h
*** /usr/src/cmd/news/src/rparams.h	Tue Jun  1 08:25:33 1982
--- rparams.h	Tue Dec 21 12:26:06 1982
***************
*** 19,24
  #define hflag	options[9].flag
  #define Mflag	options[10].flag
  #define fflag	options[11].flag
  
  #define	NEXT	0
  #define SPEC	1

--- 19,25 -----
  #define hflag	options[9].flag
  #define Mflag	options[10].flag
  #define fflag	options[11].flag
+ #define eflag	options[12].flag
  
  #define	NEXT	0
  #define SPEC	1
% diff -c /usr/src/cmd/news/src/process.c process.c
*** /usr/src/cmd/news/src/process.c	Mon Jun  7 19:14:10 1982
--- process.c	Tue Dec 21 12:25:43 1982
***************
*** 23,28
  'h',	'\0',	FALSE,	OPTION,	ANY,		UNKNOWN,(char *)NULL,
  'M',	'\0',	FALSE,	OPTION,	UNKNOWN,	MAIL,	(char *)NULL,
  'f',	'\0',	FALSE,	OPTION,	ANY,		UNKNOWN,(char *)NULL,
  '\0',	'\0',	0,	0,	0,		0,	(char *)NULL
  };
  

--- 23,29 -----
  'h',	'\0',	FALSE,	OPTION,	ANY,		UNKNOWN,(char *)NULL,
  'M',	'\0',	FALSE,	OPTION,	UNKNOWN,	MAIL,	(char *)NULL,
  'f',	'\0',	FALSE,	OPTION,	ANY,		UNKNOWN,(char *)NULL,
+ 'e',	'\0',	FALSE,	OPTION,	ANY,		UNKNOWN,(char *)NULL,
  '\0',	'\0',	0,	0,	0,		0,	(char *)NULL
  };
  
% diff -c /usr/src/cmd/news/src/readr.c readr.c
*** /usr/src/cmd/news/src/readr.c	Wed Nov 17 09:35:50 1982
--- readr.c	Wed Dec 22 11:50:14 1982
***************
*** 67,73
  	for (;;) {
  		if (bit != obit) {
  			if (fp != NULL) {
! 				if (!lflag && !pflag) {
  #ifdef PAGE
  					/*
  					 * This code filters the tail of long

--- 67,73 -----
  	for (;;) {
  		if (bit != obit) {
  			if (fp != NULL) {
! 				if (!lflag && !pflag && !eflag) {
  #ifdef PAGE
  					/*
  					 * This code filters the tail of long
***************
*** 164,169
  						ngrp--;
  					}
  					fprintf(ofp, "Subject: %s\n", h.title);
  				} else {
  					if (!pflag && !lflag) {
  						if (ngrp) {

--- 164,173 -----
  						ngrp--;
  					}
  					fprintf(ofp, "Subject: %s\n", h.title);
+ 				} else if (eflag) {
+ 					sprintf(bfr, "%.14s/%d", groupdir, bit);
+ 					fprintf(ofp, "%-19.19s %-10.10s %.45s\n",
+ 					    bfr, h.subdate, h.title);
  				} else {
  					if (!pflag && !lflag) {
  						if (ngrp) {
***************
*** 188,194
  		}
  same:
  		strcpy(goodone, filename);
! 		if (pflag || lflag) {
  			if (sigtrap) {
  				qfflush(ofp);
  				fprintf(ofp, "\n");

--- 192,198 -----
  		}
  same:
  		strcpy(goodone, filename);
! 		if (pflag || lflag || eflag) {
  			if (sigtrap) {
  				qfflush(ofp);
  				fprintf(ofp, "\n");

------------------------------ Rnews.ml----------------------------------
(if (! (is-bound rmail-default-log))
    (load "rmail.ml"))
(message "Loading the news system, please wait...")
(sit-for 0)

; Unix Emacs readnews facility.

; "rnews" is used for reading news.  Executing it places your news
; directory into a window and enters a special command interpretation loop.
; The commands that it understands are:
;  p	move to the previous message.
;  n	move to the next message.
;  f	move forward in the current message.
;  b	move backward in the current message.
;  d	delete the current message.
;  u	undelete the last deleted message.
;  r	reply to the current message.
;  m	enter smail, to send mail.
;  a	append the current message to a file.
;  F	Post a followup to the current message.
;  P	Post a message.
;  q	quit out of RMail, appending all undeleted messages to mbox.

; "smail" is used for sending mail.  It places you in a buffer for
; constructing the message and locally defines a few commands:
;  ^X^S	send the mail -- if all went well the window will disappear,
;	otherwise a message indicating which addresses failed will appear
;	at the bottom of the acreen.  Unfortunatly, the way the mailers on
;	Unix work, the message will have been sent to those addresses which
;	succeded and not to the others, so you have to delete some
;	addresses and fix up the others before you resend the message.
;  ^Xt	positions you in the To: field of the message.
;  ^Xc	positions you in the Cc: field of the message, creating it if it
;	doesn't already exist.
; 		The abbrev facility is used for mail address expansion,
; 		the file /usr/local/lib/emacs/RMailAbbrevs should contain
; 		abbrev definitions to expand login names to their
;		proper mail address.  This gets used at CMU since we have
;		7 VAXen, 4 10's and countless 11's;  remembering where a
;		person usually logs in is nearly impossible.
;  ^Xs	positions you in the Subject: field of the message.
;  ^Xa	positions you to the end of the body of the message, ready to
; 	append more text.

(defun
    (rnews nbx			; The top level mail reader
	(setq nbx (concat (getenv "HOME") "/Messages/Newsbox"))
	(message "Please wait while I read your news file...")
	(sit-for 0)
	(save-window-excursion
	    (pop-to-buffer "rnews-directory")
	    (setq mode-line-format
		(concat "     News from message file "
		    (substr nbx 1 -1)
		    "      %M   %[%p%]"))
	    (setq needs-checkpointing 0)
	    (setq mode-string "RNews")
	    (erase-buffer)
	    (set-mark)
	    (if (= (nargs) 0)
		(filter-region (concat "readnews -e >> " nbx)))
	    (read-file nbx)
	    (end-of-file)
	    (setq case-fold-search 0)
	    (if (error-occured (re-search-reverse "^[>N ]"))
		(beginning-of-file)
		(next-line)
	    )
	    (error-occured
		(re-replace-string "^" "N "))
	    (setq case-fold-search 1)
	    (rnews-position)
	    (rnews-mark)
	    (sit-for 0)
	    (message "Type ^C to exit rnews; ? for help")
	    (recursive-edit)
	    (pop-to-buffer "rnews-directory")
	    (rnews-erase-messages)
	    (if buffer-is-modified (write-current-file))
	)
	(novalue)
    )
)

(defun
    (rnews-position
	(beginning-of-line)
	(if (! (looking-at "^>"))
	    (progn
		(beginning-of-file)
		(error-occured (re-search-forward "^>"))
		(beginning-of-line)
	    )
	)
    )
)

(defun
    (rnews-pickup rnews-file
	(beginning-of-line)
	(save-excursion
	    (provide-prefix-argument 2 (forward-character))
	    (set-mark)
	    (search-forward " ")
	    (backward-character)
	    (setq rnews-file (region-to-string))
	    (pop-to-buffer "current-message")
	    (setq needs-checkpointing 0)
	    (if (error-occured
		    (read-file (concat "/usr/spool/news/" rnews-file)))
		(progn
		    (erase-buffer)
		    (message (concat rnews-file " has expired"))
		)
	    )
	    (beginning-of-file)
	    (set-rmail-mode-line-format)
	    (setq case-fold-search 1)
	    (set-mark)
	)
    )
)

(defun
    (rnews-erase-messages
	(save-excursion
	    (pop-to-buffer "rnews-directory")
	    (beginning-of-file)
	    (error-occured
		(while 1
		    (re-search-forward "^.D")
		    (beginning-of-line)
		    (set-mark)
		    (end-of-line)
		    (forward-character)
		    (erase-region)
		)
	    )
	)
    )
)

(defun
    (rnews-com
	(argc)
	(rnews)
	(exit-emacs)
    )
)

(defun
    (rnews-next-message
	(rnews-position)
	(delete-next-character)
	(insert-character ' ')
	(beginning-of-line)
	(next-line)
	(if (eobp) (progn (previous-line)
			  (message "You're at the last message already")))
	(delete-next-character)
	(insert-character '>')
	(rnews-pickup)
    )
)

(defun
    (rnews-previous-message
	(rnews-position)
	(delete-next-character)
	(insert-character ' ')
	(previous-line)
	(beginning-of-line)
	(delete-next-character)
	(insert-character '>')
	(rnews-pickup)
    )
)

(defun
    (rnews-delete-message
	(rnews-position)
	(forward-character)
	(delete-next-character)
	(insert-character 'D')
	(beginning-of-line)
    )
)

(defun
    (rnews-undelete-message
	(rnews-position)
	(forward-character)
	(delete-next-character)
	(insert-character ' ')
	(beginning-of-line)
    )
)

(autoload "&info" "info.ml")

(defun
    (rnews-help
	(&info "emacs" "rnews")))

(defun
    (rnews-reply subject dest excess refs
	(setq subject "")
	(setq dest "")
	(setq excess "")
	(save-window-excursion
	    (pop-to-buffer "current-message")
	    (setq case-fold-search 1)
	    (beginning-of-file)
	    (search-forward "\n\n")
	    (set-mark)
	    (beginning-of-file)
	    (narrow-region)
	    (error-occured
		(re-search-forward "^Title:[ \t]*\\(.*\\)")
		(region-around-match 1)
		(setq subject (region-to-string))
		(if (!= (substr subject 1 3) "Re:")
		    (setq subject (concat "Re: " subject))
		)
	    )
	    (beginning-of-file)
	    (error-occured
		(if (error-occured (re-search-forward
				       "^reply-to:[ \t]*\\(.*\\)"))
		    (if (error-occured (re-search-forward
					   "^from:[ \t]*[^ \t!]*!\\(.*\\)"))
			(re-search-forward "^from[ \t]*\\(.[^ \t]*\\)")
		    )
		)
		(region-around-match 1)
		(setq dest (region-to-string))
	    )
	    (beginning-of-file)
	    (error-occured edest
		(save-excursion 
		    (temp-use-buffer "Scratch Stuff")
		    (setq needs-checkpointing 0)
		    (erase-buffer)
		    (insert-string dest)
		    (set-mark)
		    (beginning-of-file)
		    (if (! (error-occured
			(re-search-forward " (\\(.*\\))")))
			(progn
			    (region-around-match 1)
			    (setq dest (region-to-string))
			    (beginning-of-file)
			    (insert-string (concat dest "  <"))
			    (re-replace-string "  *(.*" ">")
			    (end-of-line)
			    (set-mark)
			    (beginning-of-line)
			    (setq dest (region-to-string))
			)
		    )
		    (error-occured 
			(re-replace-string
			    "  *at  *[^,\n]*\\| *@ *[^,\n]*\\| *([^)\n]*)\\| *<[^>\n]*>"
			    ""))
		    (error-occured
			(re-replace-string ".*!" ""))
		    (setq edest (region-to-string))
		)
		(if (error-occured
			(re-search-forward "^Posted:[ \t]*"))
		    (re-search-forward "^Received:[ \t]*.[^ \t]*[ \t]*"))
		(set-mark)
		(end-of-line)
		(setq excess (concat
				 "In-Reply-To: "
				 edest "'s message of "
				 (region-to-string)
				 "\n"))
		(beginning-of-file)
		(error-occured
		    (re-search-forward "^Article-I.D.:[ \t]*\\(.*\\)")
		    (region-around-match 1)
		    (setq refs (concat "References: " (region-to-string)))
		)
	    )
	    (widen-region)
	    (pop-to-buffer "send-mail")
	    (setq needs-checkpointing 0)
	    (setq case-fold-search 1)
	    (erase-buffer)
	    (insert-string subject)
	    (newline)
	    (insert-string dest)
	    (newline)
	    (insert-string excess)
	    (insert-string refs)
	    (newline)
	    (do-mail-setup)
	)
	(rnews-position)
	(if (looking-at "^>")
	    (progn
		(forward-character)
		(delete-next-character)
		(insert-character 'A')
		(beginning-of-line)))
    )
)

(defun
    (rnews-followup newsgroups title refs
	(setq newsgroups "")
	(setq title "")
	(setq refs "")
	(save-window-excursion
	    (pop-to-buffer "current-message")
	    (beginning-of-file)
	    (search-forward "\n\n")
	    (set-mark)
	    (beginning-of-file)
	    (narrow-region)
	    (error-occured
		(re-search-forward "^Newsgroups:[ \t]*\\(.*\\)")
		(region-around-match 1)
		(setq newsgroups (region-to-string))
	    )
	    (save-excursion
		(temp-use-buffer "Scratch Stuff")
		(setq needs-checkpointing 0)
		(erase-buffer)
		(insert-string newsgroups)
		(beginning-of-file)
		(error-occured
		    (replace-string "general" "followup"))
		(set-mark)
		(end-of-file)
		(setq newsgroups (region-to-string))
	    )
	    (beginning-of-file)
	    (error-occured
		(re-search-forward "^Title:[ \t]*\\(.*\\)")
		(region-around-match 1)
		(setq title (region-to-string))
		(if (!= (substr title 1 3) "Re:")
		    (setq title (concat "Re: " title))
		)
	    )
	    (beginning-of-file)
	    (error-occured
		(re-search-forward "^Article-I.D.:[ \t]*\\(.*\\)")
		(region-around-match 1)
		(setq refs (region-to-string))
	    )
	    (beginning-of-file)
	    (widen-region)
	    (pop-to-buffer "send-mail")
	    (setq needs-checkpointing 0)
	    (setq case-fold-search 1)
	    (erase-buffer)
	    (insert-string (concat "Newsgroups: " newsgroups))
	    (newline)
	    (insert-string (concat "Title: " title))
	    (newline)
	    (insert-string (concat "References: " refs))
	    (newline)
	    (newline)
	    (rnews-do-post)
	)
	(rnews-position)
	(if (looking-at "^>")
	    (progn
		(forward-character)
		(delete-next-character)
		(insert-character 'F')
		(beginning-of-line)))
    )

    (rnews-post
	(save-window-excursion
	    (pop-to-buffer "send-mail")
	    (setq needs-checkpointing 0)
	    (setq case-fold-search 1)
	    (erase-buffer)
	    (insert-string "Newsgroups: \nTitle: \n\n")
	    (beginning-of-file)
	    (end-of-line)
	    (rnews-do-post)
	)
    )

    (rnews-do-post rnews-do-send
	(setq rnews-do-send 1)
	(setq right-margin 72)
	(local-bind-to-key "exit-emacs" "\^X\^S")
	(local-bind-to-key "exit-emacs" "\^X\^F")
	(local-bind-to-key "rnews-abort-send" "\^X\^A")
	(local-bind-to-key "justify-paragraph" "\ej")
	(recursive-edit)
	(if (= rnews-do-send 1)
	    (rnews-call-inews))
    )

    (rnews-call-inews newsgroups title refs
	(save-excursion
	    (setq newsgroups "")
	    (setq title "")
	    (setq refs "")
	    (beginning-of-file)
	    (search-forward "\n\n")
	    (set-mark)
	    (beginning-of-file)
	    (narrow-region)
	    (error-occured
		(re-search-forward "^Newsgroups:[ \t]*\\(.*\\)")
		(region-around-match 1)
		(setq newsgroups (region-to-string))
	    )
	    (if (= (length newsgroups) 0)
		(setq newsgroups (get-tty-string "Newsgroups [general]: ")))
	    (if (= (length newsgroups) 0)
		(setq newsgroups "general"))
	    (beginning-of-file)
	    (error-occured
		(re-search-forward "^Title:[ \t]*\\(.*\\)")
		(region-around-match 1)
		(setq title (region-to-string))
	    )
	    (if (= (length title) 0)
		(setq title (get-tty-string "Title: ")))
	    (beginning-of-file)
	    (error-occured
		(re-search-forward "^References:[ \t]*\\(.*\\)")
		(region-around-match 1)
		(setq refs (concat " -F " (region-to-string)))
	    )
	    (widen-region)
	    (beginning-of-file)
	    (search-forward "\n\n")
	    (set-mark)
	    (end-of-file)
	    (copy-region-to-buffer "Delivery-errors")
	    )
	(message "Sending...")
	(sit-for 0)
	(save-window-excursion
	    (switch-to-buffer "Delivery-errors")
	    (beginning-of-file)
	    (set-mark)
	    (end-of-file)
	    (filter-region
		(concat "inews" refs " -n " newsgroups " -t '" title "'"))
	    (beginning-of-file)
	    (set-mark)
	    (error-occured (re-replace-string "\n\n* *" "; "))
	    (end-of-line)
	    (message (region-to-string))
	)
    )

    (rnews-abort-send
	(if (!= "y" (substr (get-tty-string
				"Do you really want to abort the message? ")
			    1 1))
	    (error-message "Turkey!"))
	(setq rnews-do-send 0)
	(exit-emacs)
    )
)

(defun
    (rnews-unmark
	(error-occured
	    (rnews-position)
	    (delete-next-character)
	    (insert-character ' ')
	    (beginning-of-line)))
    
    (rnews-mark
	(if (error-occured
		(beginning-of-line)
		(if (eobp)
		    (re-search-reverse "^.")
		    (progn
			(re-search-forward "^.")
			(beginning-of-line)))
		(delete-next-character)
		(insert-character '>')
		(rnews-position)
		(rnews-pickup)
	    )
	    (message "No messages"))
    )
)

(defun
    (rnews-first-message
	(rnews-unmark)
	(beginning-of-file)
	(rnews-mark)
    )
)

(defun
    (rnews-last-message
	(rnews-unmark)
	(end-of-file)
	(rnews-mark)
    )
)

(defun
    (rnews-skip n
	(setq n (get-tty-string "Skip messages: "))
	(rnews-unmark)
	(provide-prefix-argument n (next-line))
	(rnews-mark)
    )
)

(defun
    (rnews-this-message save-dot
	(setq save-dot (dot))
	(rnews-unmark)
	(goto-character save-dot)
	(rnews-mark)
    )
)

(save-excursion i
    (temp-use-buffer "rnews-directory")
    (setq i ' ')
    (while (< i 128)
	(local-bind-to-key "illegal-operation" i)
	(setq i (+ i 1)))
    (local-bind-to-key "rmail-next-page" 'f')
    (local-bind-to-key "rmail-previous-page" 'b')
    (local-bind-to-key "rmail-previous-page" '^H')
    (local-bind-to-key "rnews-next-message" 'n')
    (local-bind-to-key "rnews-previous-message" 'p')
    (local-bind-to-key "rnews-delete-message" 'd')
    (local-bind-to-key "rnews-undelete-message" 'u')
    (local-bind-to-key "rnews-help" '?')
    (local-bind-to-key "exit-emacs" 'q')
    (local-bind-to-key "rnews-reply" 'r')
    (local-bind-to-key "rnews-followup" 'F')
    (local-bind-to-key "rnews-post" 'P')
    (local-bind-to-key "smail" 'm')
    (local-bind-to-key "rmail-goto-message" 'g')
    (local-bind-to-key "rnews-first-message" '<')
    (local-bind-to-key "rnews-last-message" '>')
    (local-bind-to-key "rnews-skip" 's')
    (local-bind-to-key "rmail-append" 'a')
    (local-bind-to-key "rmail-shell" '!')
    (local-bind-to-key "execute-extended-command" ':')
    (local-bind-to-key "rnews-this-message" ' ')
    (local-bind-to-key "rnews-this-message" '\r')
)