[comp.emacs] Gnews news

weemba@garnet.berkeley.edu (Matthew P Wiener) (04/01/88)

I'll be gone for about 2 months this trip east; and then I should be
here all summer long.
----------------------------------------------------------------------
I'd appreciate getting e-mail about experiences with Gnews: everything
from why you find it the greatest thing since sliced bread to how it's
been completely incomprehensible and a waste of your time; comments on
the documentation, installation, ELisp code, etc are appreciated also.
----------------------------------------------------------------------
Hal Peterson of CRI has gotten the gnews-spool code to work under Gnews
1.3!  I enclose the contents of their site's gnews-init.el file; by
this I mean that this assumes (autoload 'gnews "gnews-init" nil t) is
what users put in their .emacs (along with any load-path fixing that
needs to be done).  Two of the changes, in article-get-slow and arti-
cle-get-msg-id, should be installed by everyone, although this is minor.
----------------------------------------------------------------------
On the down side of the above, I must retract my claim earlier that FSF
is adopting Gnews as soon as the spool code is working; I misunderstood
e-mail that only said that working spool code was a necessary prerequi-
site.  It's not clear that FSF will adopt Gnews; I plan to keep support-
ing Gnews as long as I can.
----------------------------------------------------------------------
One strange bug, which no one off of garnet has reported to me, is that
Gnews sometimes returns to the *gnews*nntp*index* buffer on exit.  I do
not understand what causes this, except that the gnews-buffer-return
variable gets mis-set on Gnews start up.  I replaced the setq of g-b-r
to (buffer-name) with a setq to (buffer-name (car (buffer-list))), and
the problem went away.  I suspect that I've run into an obscure Emacs
bug; this behavior is incomprehensible.
----------------------------------------------------------------------
A bug was noticed by Hal Peterson that effects people who had an options
line in their .newsrc.  The corrected gnews-hook-write is below.
----------------------------------------------------------------------
When I return, finishing the infotex manual and rewriting innards from
the GC-minimization angle are the next goals.
----------------------------------------------------------------------
ucbvax!garnet!weemba	Matthew P Wiener/Brahms Gang/Berkeley CA 94720
----------------------------------------------------------------------
Here's the spool code that worked with 1.3:

;; local (Cray) initialization for running gnews on the local machine.

(load "gnews" nil t)
(load-library "gnewspool")

(setq gnews-start-hook 'gnews:start:hook)

(defun gnews:start:hook ()
  (fset 'nntp-start 'gnews-spool-start)
  (fset 'nntp-exec 'gnews-spool-exec)
  (fset 'article-get 'article-get-slow)
  (fset 'nntp-index-start '(lambda () t))
  (fset 'nntp-run-p '(lambda () t))
  (setq nntp-index-fast nil) )

;; Some fixes from weemba.  30 Mar 88.
(defun article-get-msg-id (msg-id)
  "Display article with Message-ID MSG-ID.  The enclosing angle brackets
are optional."
  (interactive "smessage-id: ")
  (if (string= (substring msg-id 0 1) "<") nil
    (setq msg-id (concat "<" msg-id)))
  (if (string= (substring msg-id -1) ">") nil
    (setq msg-id (concat msg-id ">")))
  (if (nntp-exec t t "article" msg-id)
      (let (lines)
	(if (< 0 article-current)
	    (setq article-message-id msg-id
		  article-trace article-current
		  article-current 0))		; NNTP can't return #/gp
	(set-buffer nntp-buffer)
	(article-header-clean t)
	(setq lines (article-effective-init-display))
	(if lines (forward-line lines))
	(setq article-grab-point (if lines (point)))
	(if (cdr article-field-list)
	    (article-display-init t)
	  (message "Message-ID %s: no such article" msg-id (ding))))
    (message "Message-ID %s: no such article" msg-id (ding)))
  (gnews-flush))

(defun article-get-slow (number &optional hook interact)
  "Display article NUMBER of the current newsgroup.\n
In ELisp code, optional argument HOOK is a list of per-hooks to apply, and
non-nil INTERACT means to pretend this function was called interactively.\n
\(This is the original version of article-get.  The group-pattern-* functions
fail mysteriously on the new version.  I don't understand why.\)"
  (interactive "narticle #: ")
  (if (< article-final number)
      (group-last)
    (if (nntp-exec t t "article" number)
	(let ((b (current-buffer))
	      lines)
	  (set-buffer nntp-buffer)
	  (if (or (interactive-p) interact)
	      (article-current-set number))
	  (article-header-clean t)
	  (setq lines (article-effective-init-display))
	  (let ((article-current number)
		(gnews-hook-continue t))
	    (while (and hook gnews-hook-continue)
	      (if (gnews-hook-do (car hook) t)
		  (progn
		    (gnews-hook-junk-message number hook)
		    (set-buffer b) ; I have to doooo this?
		    (throw 'article-nil t)))	; article KILLed; try again
	      (setq hook (cdr hook))))
	  (if lines (forward-line lines))
	  (setq article-grab-point (if lines (point)))
	  (article-display-init t)
	  (if (setq article-junkable (article-done)) (article-quit)))
      (throw 'article-nil t)))		; article not found--give it up
  (gnews-flush))

;; My fixes to gnewspool.el.  hrp, 31 March 1988.
(defun gnews-spool-regexp (msg-id)
  "Return the regexp that matches MSG-ID in the history file, and also
brackets off the newsgroup and article number as match #1 and match #2."
  (concat "^" (regexp-quote msg-id)  	  ; Message-ID
	  "[ \t]../../..[ \t]..:..[ \t]"  ; date/time.  If this doesn't work,
					  ; add a + to the [ \t] expressions(?)
	  "\\([^/]*\\)/\\([0-9]*\\) "))	  ; group/###

(defun gnews-spool-start ()
  "Initialize gnews-spool buffers."
  (setq gnews-spool-active (find-file-noselect gnews-spool-active-file)
	gnews-spool-history (find-file-noselect gnews-spool-history-file))
  (gnews-spool-info (if n-reply-allowed "200 " "201 ") news-path))

(defun gnews-spool-exec (clear finish comm &rest args)
  "NNTP commands interpreted directly off a news spool."
  (interactive (list (not current-prefix-arg)
		     (read-from-minibuffer "NNTP command: ")
		     nil))
  (if (interactive-p)
      (setq args (progn
		   (string-match "\\<[^ ]*\\>" comm)
		   (if (/= (length comm) (match-end 0))
		       (list (substring comm (1+ (match-end 0))))))
	    comm (substring comm (match-beginning 0) (match-end 0)))
    (if (stringp clear) (error "Uh, you forgot the clear flag, eh?")))
  (if clear (nntp-clear nntp-buffer))
  (let* ((b (current-buffer))
	 (a1 (car args))
	 (a2 (cadr args))
	 (art (or a1 article-current)))
    (prog2
	(set-buffer nntp-buffer)
	(cond ((string= comm "group")
	       (gnews-spool-exec-group a1))
	      ((string= comm "article")
	       (gnews-spool-exec-art art 'art))
	      ((string= comm "head")
	       (gnews-spool-exec-art art 'head))
	      ((string= comm "body")
	       (gnews-spool-exec-art art 'body)
	       (goto-char (point-min))
	       (insert nntp-info "\n"))
	      ((string= comm "stat")
	       (gnews-spool-exec-art art 'stat))
	      ((string= comm "next")
	       (gnews-spool-exec-motion t))
	      ((string= comm "last")
	       (gnews-spool-exec-motion nil))
	      ((string= comm "list")
	       (gnews-spool-exec-list))
	      ((string= comm "newgroups")
	       (gnews-spool-exec-newgroups a1 a2))
	      ((string= comm "help")
	       (gnews-spool-exec-help))
	      ((string= comm "quit")
	       (gnews-spool-exec-quit)))
	(setq nntp-eot 'spool)
	(set-buffer b))))

(defun gnews-spool-exec-group (gp)
  "Fake an NNTP group command."
  (let ((dir (gnews-spool-dir gp)) c f l)
    (if (and (file-readable-p dir)
	     (car (file-attributes dir)))
	(gnews-string-as-buffer "" nil
	  (call-process "ls" nil t nil dir)
	  (goto-char 1)
	  (insert "(setq gnews-spool-group-list (gnews-spool-preen '(")
	  (goto-char (point-max))
	  (insert ")))")
	  (eval-current-buffer)
	  (if (null gnews-spool-group-list)
	      (gnews-spool-info "211 0 0 0" gp)	; nothing there
	    (sort gnews-spool-group-list '<)
	    (setq gnews-spool-group-tsil (reverse gnews-spool-group-list))
	    (setq c (length gnews-spool-group-list))
	    (setq f (car gnews-spool-group-list))
	    (setq l (car gnews-spool-group-tsil))
	    (gnews-spool-info "211" c f l gp)
	    t))
      (gnews-spool-info "411 Invalid group name.")
      nil)))

(defun gnews-spool-preen (grouplist)
  "Remove all subgroups from GROUPLIST, a list of articles in a group."
  (let ((preened-list nil))
    (while (not (null grouplist))
      (if (integerp (car grouplist))
	  (setq preened-list (append preened-list
				     (list (car grouplist)))))
      (setq grouplist (cdr grouplist)))
    preened-list))

(defun gnews-spool-exec-art (art-id part)
  "Fake an NNTP article/head/body/stat command."
  (let (file msg-id (art-no (if (integerp art-id)
				(int-to-string art-id)
			      art-id)))
    (if (and (cond ((string-match "^[0-9]+$" art-no)
		    (setq file (gnews-spool-art group-current art-no))
		    (if (let ((attributes (file-attributes file)))
			  (and attributes
			       (> (nth 7 attributes) 0)))
			(if (memq part '(body stat))
			    ;; Set the Message-ID by hand
			    (setq msg-id (gnews-string-as-buffer "" nil
					   (call-process "sed" file t t
							 "/^Message-ID:/q")
					   (forward-line -1)
					   (forward-char 12)
					   (buffer-substring
					    (point) (gnews-eol))))
			  t)))
		   ((string-match "^<.*>$" art-no)
		    (setq msg-id art-no art-no "0")
		    (set-buffer gnews-spool-history)
		    (setq file (if (re-search-forward
				     (gnews-spool-regexp msg-id) nil t)
				   (gnews-spool-art
				     (buffer-substring
				       (match-beginning 1) (match-end 1))
				     (buffer-substring
				       (match-beginning 2) (match-end 2)))
				 "/meese/sucks/raw/eggs/film/at/11"))
		    (set-buffer nntp-buffer)))
	     (file-readable-p file))
	(progn
	  (cond ((eq part 'art)
		 (insert-file file))
		((eq part 'head)
		 (call-process "sed" file t t "/^$/q")
		 (goto-char (point-max))
		 (delete-char -1))
		((eq part 'body)
		 (call-process "sed" file t t "1,/^$/d")))
	  (gnews-spool-info (cond ((eq part 'art) "220")
				  ((eq part 'head) "221")
				  ((eq part 'body) "222")
				  ((eq part 'stat) "223"))
			    art-no
			    msg-id
			    "Article retrieved;"
			    (cond ((eq part 'art) "head and body follow.")
				  ((eq part 'head) "head follows.")
				  ((eq part 'stat) "request text separately.")
				  ((eq part 'body) "body follows.")))
	  t)
      (gnews-spool-info "423 Invalid article number:" art)
      nil)))

(defun gnews-spool-exec-newgroups (ymd hms &optional gmt)
  "Fake an NNTP newgroups command.  So what if we're off half a day?"
  ;; fake it.  There is no groupdates file.
  t)

;; fixes to gnewsutils.el.  31 Mar 88, hrp.
(defun gnews-hook-write (file)
  "Write out gnews-hook-list and gnews-abbrev to FILE."
  (setq gnews-hook-dangle)
  (gnews-string-as-buffer "" nil
    (insert "(setq\t\t\t\t; -*- Emacs-Lisp -*-\n gnews-hook-list\n '(nil")
    (mapcar '(lambda (g)
	       (let ((gh (cdr (assoc g gnews-hook-list))))
		 (if (car gh)
		     (progn
		       (insert "\n   (" (prin1-to-string g))
		       (mapcar '(lambda (x)
				  (insert "\n    " (prin1-to-string x)))
			       gh)
		       (insert "\n    )")))))
	    (mapcar 'car (cdr gnews-hook-list)))
    (insert "\n   ))\n\f\n(setq\n gnews-abbrev\n '(")
    (mapcar '(lambda (x) (insert "\n   " (prin1-to-string x))) gnews-abbrev)
    (insert "\n   ))\n")
    (if (boundp 'gnews-rn-options)
	(insert "\f\n(setq gnews-rn-options \"" gnews-rn-options "\")\n"))
    (write-region 1 (point-max) file nil 0)))

(defun gnews-load (file &optional mok nom nos)
  "Safely load FILE, with optional arguments as in load, which see.
On error, abort NNTP and throw the user into FILE.  [[On quit, abort
NNTP and get out of Gnews.]]"
  (condition-case q
      (load file mok nom nos)
    (error (if nntp (delete-process nntp))
	   (bury-buffer news-buffer)
	   (find-file file)
	   (signal (car q) (cdr q)))
;;; (quit (nntp-exec t t "quit")		; doesn't work?
;;;	  (bury-buffer news-buffer))		; so why not?
    ))