*** empty log message ***
authorLars Magne Ingebrigtsen <larsi@gnus.org>
Tue, 4 Mar 1997 04:24:41 +0000 (04:24 +0000)
committerLars Magne Ingebrigtsen <larsi@gnus.org>
Tue, 4 Mar 1997 04:24:41 +0000 (04:24 +0000)
lisp/auc-menu.el [new file with mode: 0644]
lisp/gnus-soup.el [new file with mode: 0644]
lisp/nnsoup.el [new file with mode: 0644]
texi/ChangeLog [new file with mode: 0644]

diff --git a/lisp/auc-menu.el b/lisp/auc-menu.el
new file mode 100644 (file)
index 0000000..dba462c
--- /dev/null
@@ -0,0 +1,313 @@
+;;; auc-menu.el - Easy menu support for GNU Emacs 19 and XEmacs.
+;; 
+;; $Id: auc-menu.el,v 5.7 1994/11/28 01:41:22 amanda Exp $
+;;
+;; LCD Archive Entry:
+;; auc-menu|Per Abrahamsen|abraham@iesd.auc.dk|
+;; Easy menu support for GNU Emacs 19 and XEmacs|
+;; $Date: 1994/11/28 01:41:22 $|$Revision: 5.7 $|~/misc/auc-menu.el.gz|
+
+;; Copyright (C) 1992, 1993, 1994 Free Software Foundation, Inc.
+;; Copyright (C) 1994 Per Abrahamsen <abraham@iesd.auc.dk>
+;;
+;; 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; if not, write to the Free Software
+;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;; Commentary:
+;;
+;; Easymenu allows you to define menus for both Emacs 19 and XEmacs.
+;; The advantages of using easymenu are:
+;;
+;; - Easier to use than either the Emacs 19 and XEmacs menu syntax.
+;;
+;; - Common interface for Emacs 18, Emacs 19, and XEmacs.  
+;;   (The code does nothing when run under Emacs 18).
+;;
+;; The public functions are:
+;; 
+;; - Function: easy-menu-define SYMBOL MAPS DOC MENU
+;;     SYMBOL is both the name of the variable that holds the menu and
+;;            the name of a function that will present a the menu.
+;;     MAPS is a list of keymaps where the menu should appear in the menubar.
+;;     DOC is the documentation string for the variable.
+;;     MENU is an XEmacs style menu description.  
+;;
+;;     See the documentation for easy-menu-define for details.
+;;
+;; - Function: easy-menu-change PATH NAME ITEMS
+;;     Change an existing menu.
+;;     The menu must already exist an be visible on the menu bar.
+;;     PATH is a list of strings used for locating the menu on the menu bar. 
+;;     NAME is the name of the menu.  
+;;     ITEMS is a list of menu items, as defined in `easy-menu-define'.
+;;
+;; - Function: easy-menu-add MENU [ MAP ]
+;;     Add MENU to the current menubar in MAP.
+;;
+;; - Function: easy-menu-remove MENU
+;;     Remove MENU from the current menubar.
+;;
+;; GNU Emacs 19 never uses `easy-menu-add' or `easy-menu-remove',
+;; menus automatically appear and disappear when the keymaps
+;; specified by the MAPS argument to `easy-menu-define' are
+;; activated.
+;;
+;; XEmacs will bind the map to button3 in each MAPS, but you must
+;; explicitly call `easy-menu-add' and `easy-menu-remove' to add and
+;; remove menus from the menu bar.
+
+;; auc-menu.el define the easymenu API included in Emacs 19.29 and
+;; later.  In fact, the Emacs 19 specific code should be identical.
+
+;;; Code:
+
+;;;###autoload
+(defmacro easy-menu-define (symbol maps doc menu)
+  "Define a menu bar submenu in maps MAPS, according to MENU.
+The arguments SYMBOL and DOC are ignored; they are present for
+compatibility only.  SYMBOL is not evaluated.  In other Emacs versions
+these arguments may be used as a variable to hold the menu data, and a
+doc string for that variable.
+
+The first element of MENU must be a string.  It is the menu bar item name.
+The rest of the elements are menu items.
+
+A menu item is usually a vector of three elements:  [NAME CALLBACK ENABLE]
+
+NAME is a string--the menu item name.
+
+CALLBACK is a command to run when the item is chosen,
+or a list to evaluate when the item is chosen.
+
+ENABLE is an expression; the item is enabled for selection
+whenever this expression's value is non-nil.
+
+Alternatively, a menu item may have the form: 
+
+   [ NAME CALLBACK [ KEYWORD ARG ] ... ]
+
+Where KEYWORD is one of the symbol defined below.
+
+   :keys KEYS
+
+KEYS is a string; a complex keyboard equivalent to this menu item.
+
+   :active ENABLE
+
+ENABLE is an expression; the item is enabled for selection
+whenever this expression's value is non-nil.
+
+   :suffix NAME
+
+NAME is a string; the name of an argument to CALLBACK.
+
+   :style STYLE
+   
+STYLE is a symbol describing the type of menu item.  The following are
+defined:  
+
+toggle: A checkbox.  
+        Currently just prepend the name with the string \"Toggle \".
+radio: A radio button. 
+nil: An ordinary menu item.
+
+   :selected SELECTED
+
+SELECTED is an expression; the checkbox or radio button is selected
+whenever this expression's value is non-nil.
+Currently just disable radio buttons, no effect on checkboxes.
+
+A menu item can be a string.  Then that string appears in the menu as
+unselectable text.  A string consisting solely of hyphens is displayed
+as a solid horizontal line.
+
+A menu item can be a list.  It is treated as a submenu.
+The first element should be the submenu name.  That's used as the
+menu item in the top-level menu.  The cdr of the submenu list
+is a list of menu items, as above."
+  (` (progn
+       (defvar (, symbol) nil (, doc))
+       (easy-menu-do-define (quote (, symbol)) (, maps) (, doc) (, menu)))))
+
+(cond 
+
+;;; Emacs 18
+
+((< (string-to-int emacs-version) 19)
+
+(defun easy-menu-do-define (symbol maps doc menu)
+  (fset symbol (symbol-function 'ignore)))
+
+(defun easy-menu-remove (menu))
+
+(defun easy-menu-add (menu &optional map))
+
+(defun easy-menu-change (path name items))
+
+)                                      ;Emacs 18
+
+;;; XEmacs
+
+((string-match "XEmacs\\|Lucid" emacs-version)
+
+(defun easy-menu-do-define (symbol maps doc menu)
+  (set symbol menu)
+  (fset symbol (list 'lambda '(e)
+                    doc
+                    '(interactive "@e")
+                    '(run-hooks 'activate-menubar-hook)
+                    '(setq zmacs-region-stays 't)
+                    (list 'popup-menu symbol)))
+  (mapcar (function (lambda (map) (define-key map 'button3 symbol)))
+         (if (keymapp maps) (list maps) maps)))
+
+(fset 'easy-menu-change (symbol-function 'add-menu))
+
+(defun easy-menu-add (menu &optional map)
+  "Add MENU to the current menu bar."
+  (cond ((null current-menubar)
+        ;; Don't add it to a non-existing menubar.
+        nil)
+       ((assoc (car menu) current-menubar)
+        ;; Already present.
+        nil)
+       ((equal current-menubar '(nil))
+        ;; Set at left if only contains right marker.
+        (set-buffer-menubar (list menu nil)))
+       (t
+        ;; Add at right.
+        (set-buffer-menubar (copy-sequence current-menubar))
+        (add-menu nil (car menu) (cdr menu)))))
+
+(defun easy-menu-remove (menu)
+  "Remove MENU from the current menu bar."
+  (and current-menubar
+       (assoc (car menu) current-menubar)
+       (delete-menu-item (list (car menu)))))
+
+)                                      ;XEmacs
+
+;;; GNU Emacs 19
+
+(t
+
+(defun easy-menu-do-define (symbol maps doc menu)
+  ;; We can't do anything that might differ between Emacs dialects in
+  ;; `easy-menu-define' in order to make byte compiled files
+  ;; compatible.  Therefore everything interesting is done in this
+  ;; function. 
+  (set symbol (easy-menu-create-keymaps (car menu) (cdr menu)))
+  (fset symbol (` (lambda (event) (, doc) (interactive "@e")
+                   (easy-popup-menu event (, symbol)))))
+  (mapcar (function (lambda (map) 
+           (define-key map (vector 'menu-bar (intern (car menu)))
+             (cons (car menu) (symbol-value symbol)))))
+         (if (keymapp maps) (list maps) maps)))
+
+(defvar easy-menu-item-count 0)
+
+;; Return a menu keymap corresponding to a XEmacs style menu list
+;; MENU-ITEMS, and with name MENU-NAME.
+(defun easy-menu-create-keymaps (menu-name menu-items)
+  (let ((menu (make-sparse-keymap menu-name)))
+    ;; Process items in reverse order,
+    ;; since the define-key loop reverses them again.
+    (setq menu-items (reverse menu-items))
+    (while menu-items
+      (let* ((item (car menu-items))
+            (callback (if (vectorp item) (aref item 1)))
+            command enabler name)
+       (cond ((stringp item)
+              (setq command nil)
+              (setq name (if (string-match "^-+$" item) "" item)))
+             ((consp item)
+              (setq command (easy-menu-create-keymaps (car item) (cdr item)))
+              (setq name (car item)))
+             ((vectorp item)
+              (setq command (make-symbol (format "menu-function-%d"
+                                                 easy-menu-item-count)))
+              (setq easy-menu-item-count (1+ easy-menu-item-count))
+              (setq name (aref item 0))
+              (let ((keyword (aref item 2)))
+                (if (and (symbolp keyword)
+                         (= ?: (aref (symbol-name keyword) 0)))
+                    (let ((count 2)
+                          style selected active keys
+                          arg)
+                      (while (> (length item) count)
+                        (setq keyword (aref item count))
+                        (setq arg (aref item (1+ count)))
+                        (setq count (+ 2 count))
+                        (cond ((eq keyword ':keys)
+                               (setq keys arg))
+                              ((eq keyword ':active)
+                               (setq active arg))
+                              ((eq keyword ':suffix)
+                               (setq name (concat name " " arg)))
+                              ((eq keyword ':style)
+                               (setq style arg))
+                              ((eq keyword ':selected)
+                               (setq selected arg))))
+                      (if keys
+                          (setq name (concat name "  (" keys ")")))
+                      (if (eq style 'toggle)
+                          ;; Simulate checkboxes.
+                          (setq name (concat "Toggle " name)))
+                      (if active 
+                          (put command 'menu-enable active)
+                        (and (eq style 'radio)
+                             selected
+                             ;; Simulate radio buttons with menu-enable.
+                             (put command 'menu-enable
+                                  (list 'not selected)))))))          
+              (if (keymapp callback)
+                  (setq name (concat name " ...")))
+              (if (symbolp callback)
+                  (fset command callback)
+                (fset command (list 'lambda () '(interactive) callback)))))
+       (if (null command)
+           ;; Handle inactive strings specially--allow any number
+           ;; of identical ones.
+           (setcdr menu (cons (list nil name) (cdr menu)))
+         (if name 
+             (define-key menu (vector (intern name)) (cons name command)))))
+      (setq menu-items (cdr menu-items)))
+    menu))
+
+(defun easy-menu-change (path name items)
+  "Change menu found at PATH as item NAME to contain ITEMS.
+PATH is a list of strings for locating the menu containing NAME in the
+menu bar.  ITEMS is a list of menu items, as in `easy-menu-define'.
+These items entirely replace the previous items in that map.
+
+Call this from `activate-menubar-hook' to implement dynamic menus."
+  (let ((map (key-binding (apply 'vector
+                                'menu-bar
+                                (mapcar 'intern (append path (list name)))))))
+    (if (keymapp map)
+       (setcdr map (cdr (easy-menu-create-keymaps name items)))
+      (error "Malformed menu in `easy-menu-change'"))))
+
+(defun easy-menu-remove (menu))
+
+(defun easy-menu-add (menu &optional map))
+
+)                                      ;GNU Emacs 19
+
+)                                      ;cond
+
+(provide 'easymenu)
+(provide 'auc-menu)
+
+;;; auc-menu.el ends here
diff --git a/lisp/gnus-soup.el b/lisp/gnus-soup.el
new file mode 100644 (file)
index 0000000..be85946
--- /dev/null
@@ -0,0 +1,549 @@
+;;; gnus-soup.el --- SOUP packet writing support for Gnus
+;; Copyright (C) 1995 Free Software Foundation, Inc.
+
+;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
+;;     Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus-msg)
+(require 'gnus)
+
+;;; User Variables:
+
+(defvar gnus-soup-directory "~/SoupBrew/"
+  "*Directory containing an unpacked SOUP packet.")
+
+(defvar gnus-soup-replies-directory (concat gnus-soup-directory "SoupReplies/")
+  "*Directory where Gnus will do processing of replies.")
+
+(defvar gnus-soup-prefix-file "gnus-prefix"
+  "*Name of the file where Gnus stores the last used prefix.")
+
+(defvar gnus-soup-packer "tar cf - %s | gzip > $HOME/Soupout%d.tgz"
+  "Format string command for packing a SOUP packet.
+The SOUP files will be inserted where the %s is in the string.
+This string MUST contain both %s and %d. The file number will be
+inserted where %d appears.")
+
+(defvar gnus-soup-unpacker "gunzip -c %s | tar xvf -"
+  "*Format string command for unpacking a SOUP packet.
+The SOUP packet file name will be inserted at the %s.")
+
+(defvar gnus-soup-packet-directory "~/"
+  "*Where gnus-soup will look for REPLIES packets.")
+
+(defvar gnus-soup-packet-regexp "Soupin"
+  "*Regular expression matching SOUP REPLIES packets in `gnus-soup-packet-directory'.")
+
+;;; Internal Variables:
+
+(defvar gnus-soup-encoding-type ?n
+  "*Soup encoding type.
+`n' is news format, `m' is Unix mbox format, and `M' is MMDF mailbox
+format.")
+
+(defvar gnus-soup-index-type ?c
+  "*Soup index type.
+`n' means no index file and `c' means standard Cnews overview
+format.") 
+
+(defvar gnus-soup-group-type ?u
+  "*Soup message area type.
+`u' is unknown, `m' is private mail, and `n' is news.
+Gnus will determine by itself what type to use in what group, so
+setting this variable won't do much.")
+
+(defvar gnus-soup-areas nil)
+(defvar gnus-soup-last-prefix nil)
+(defvar gnus-soup-prev-prefix nil)
+(defvar gnus-soup-buffers nil)
+
+;;; Access macros:
+
+(defmacro gnus-soup-area-prefix (area)
+  (` (aref (, area) 0)))
+(defmacro gnus-soup-area-name (area)
+  (` (aref (, area) 1)))
+(defmacro gnus-soup-area-encoding (area)
+  (` (aref (, area) 2)))
+(defmacro gnus-soup-area-description (area)
+  (` (aref (, area) 3)))
+(defmacro gnus-soup-area-number (area)
+  (` (aref (, area) 4)))
+(defmacro gnus-soup-area-set-number (area value)
+  (` (aset (, area) 4 (, value))))
+
+(defmacro gnus-soup-encoding-format (encoding)
+  (` (aref (, encoding) 0)))
+(defmacro gnus-soup-encoding-index (encoding)
+  (` (aref (, encoding) 1)))
+(defmacro gnus-soup-encoding-kind (encoding)
+  (` (aref (, encoding) 2)))
+
+(defmacro gnus-soup-reply-prefix (reply)
+  (` (aref (, reply) 0)))
+(defmacro gnus-soup-reply-kind (reply)
+  (` (aref (, reply) 1)))
+(defmacro gnus-soup-reply-encoding (reply)
+  (` (aref (, reply) 2)))
+
+;;; Commands:
+
+(defun gnus-soup-send-replies ()
+  "Unpack and send all replies in the reply packet."
+  (interactive)
+  (let ((packets (directory-files
+                 gnus-soup-packet-directory t gnus-soup-packet-regexp)))
+    (while packets
+      (and (gnus-soup-send-packet (car packets))
+          (delete-file (car packets)))
+      (setq packets (cdr packets)))))
+
+(defun gnus-soup-add-article (n)
+  "Add the current article to SOUP packet.
+If N is a positive number, add the N next articles.
+If N is a negative number, add the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+move those articles instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let* ((articles (gnus-summary-work-articles n))
+        (tmp-buf (get-buffer-create "*soup work*"))
+        (area (gnus-soup-area gnus-newsgroup-name))
+        (prefix (gnus-soup-area-prefix area))
+        headers)
+    (buffer-disable-undo tmp-buf)
+    (save-excursion
+      (while articles
+       ;; Find the header of the article.
+       (set-buffer gnus-summary-buffer)
+       (setq headers (gnus-summary-article-header (car articles)))
+       ;; Put the article in a buffer.
+       (set-buffer tmp-buf)
+       (gnus-request-article-this-buffer 
+        (car articles) gnus-newsgroup-name)
+       (gnus-soup-store gnus-soup-directory prefix headers
+                        gnus-soup-encoding-type 
+                        gnus-soup-index-type)
+       (gnus-soup-area-set-number area
+        (1+ (or (gnus-soup-area-number area) 0)))
+       ;; Mark article as read. 
+       (set-buffer gnus-summary-buffer)
+       (gnus-summary-remove-process-mark (car articles))
+       (gnus-summary-mark-as-read (car articles) "F")
+       (setq articles (cdr articles)))
+      (kill-buffer tmp-buf))
+    (gnus-soup-save-areas)))
+
+(defun gnus-soup-pack-packet ()
+  "Make a SOUP packet from the SOUP areas."
+  (interactive)
+  (gnus-soup-read-areas)
+  (gnus-soup-pack gnus-soup-directory gnus-soup-packer))
+
+(defun gnus-group-brew-soup (n)
+  "Make a soup packet from the current group.
+Uses the process/prefix convention."
+  (interactive "P")
+  (let ((groups (gnus-group-process-prefix n)))
+    (while groups
+      (gnus-group-remove-mark (car groups))
+      (gnus-soup-group-brew (car groups))
+      (setq groups (cdr groups)))
+    (gnus-soup-save-areas)))
+
+(defun gnus-brew-soup (&optional level)
+  "Go through all groups on LEVEL or less and make a soup packet."
+  (interactive "P")
+  (let ((level (or level gnus-level-subscribed))
+       (newsrc (cdr gnus-newsrc-alist)))
+    (while newsrc
+      (and (<= (nth 1 (car newsrc)) level)
+          (gnus-soup-group-brew (car (car newsrc))))
+      (setq newsrc (cdr newsrc)))
+    (gnus-soup-save-areas)))
+
+;;;###autoload
+(defun gnus-batch-brew-soup ()
+  "Brew a SOUP packet from groups mention on the command line.
+Will use the remaining command line arguments as regular expressions
+for matching on group names.
+
+For instance, if you want to brew on all the nnml groups, as well as
+groups with \"emacs\" in the name, you could say something like:
+
+$ emacs -batch -f gnus-batch-brew-soup ^nnml \".*emacs.*\""
+  (interactive)
+  )
+  
+;;; Internal Functions:
+
+;; Store the current buffer. 
+(defun gnus-soup-store (directory prefix headers format index)
+  ;; Create the directory, if needed. 
+  (or (file-directory-p directory)
+      (gnus-make-directory directory))
+  (let* ((msg-buf (find-file-noselect
+                  (concat directory prefix ".MSG")))
+        (idx-buf (if (= index ?n)
+                     nil
+                   (find-file-noselect
+                    (concat directory prefix ".IDX"))))
+        (article-buf (current-buffer))
+        from head-line beg type)
+    (setq gnus-soup-buffers (cons msg-buf gnus-soup-buffers))
+    (buffer-disable-undo msg-buf)
+    (and idx-buf 
+        (progn
+          (setq gnus-soup-buffers (cons idx-buf gnus-soup-buffers))
+          (buffer-disable-undo idx-buf)))
+    (save-excursion
+      ;; Make sure the last char in the buffer is a newline.
+      (goto-char (point-max))
+      (or (= (current-column) 0)
+         (insert "\n"))
+      ;; Find the "from".
+      (goto-char (point-min))
+      (setq from
+           (mail-strip-quoted-names
+            (or (mail-fetch-field "from")
+                (mail-fetch-field "really-from")
+                (mail-fetch-field "sender"))))
+      (goto-char (point-min))
+      ;; Depending on what encoding is supposed to be used, we make
+      ;; a soup header. 
+      (setq head-line
+           (cond 
+            ((= gnus-soup-encoding-type ?n)
+             (format "#! rnews %d\n" (buffer-size)))
+            ((= gnus-soup-encoding-type ?m)
+             (while (search-forward "\nFrom " nil t)
+               (replace-match "\n>From " t t))
+             (concat "From " (or from "unknown")
+                     " " (current-time-string) "\n"))
+            ((= gnus-soup-encoding-type ?M)
+             "\^a\^a\^a\^a\n")
+            (t (error "Unsupported type: %c" gnus-soup-encoding-type))))
+      ;; Insert the soup header and the article in the MSG buf.
+      (set-buffer msg-buf)
+      (goto-char (point-max))
+      (insert head-line)
+      (setq beg (point))
+      (insert-buffer-substring article-buf)
+      ;; Insert the index in the IDX buf.
+      (cond ((= index ?c)
+            (set-buffer idx-buf)
+            (gnus-soup-insert-idx beg headers))
+           ((/= index ?n)
+            (error "Unknown index type: %c" type))))))
+
+(defun gnus-soup-group-brew (group)
+  (let ((gnus-expert-user t)
+       (gnus-large-newsgroup nil))
+    (and (gnus-summary-read-group group)
+        (let ((gnus-newsgroup-processable 
+               (gnus-sorted-complement 
+                gnus-newsgroup-unreads
+                (append gnus-newsgroup-dormant gnus-newsgroup-marked))))
+          (gnus-soup-add-article nil)))
+    (gnus-summary-exit)))
+
+(defun gnus-soup-insert-idx (offset header)
+  ;; [number subject from date id references chars lines xref]
+  (goto-char (point-max))
+  (insert
+   (format "%d\t%s\t%s\t%s\t%s\t%s\t%d\t%s\t%s\t\n"
+          offset
+          (or (mail-header-subject header) "(none)")
+          (or (mail-header-from header) "(nobody)")
+          (or (mail-header-date header) "")
+          (or (mail-header-id header)
+              (concat "soup-dummy-id-" 
+                      (mapconcat 
+                       (lambda (time) (int-to-string time))
+                       (current-time) "-")))
+          (or (mail-header-references header) "")
+          (or (mail-header-chars header) 0) 
+          (or (mail-header-lines header) "0") 
+          (or (mail-header-xref header) ""))))
+
+(defun gnus-soup-save-areas ()
+  (gnus-soup-write-areas)
+  (save-excursion
+    (let (buf)
+      (while gnus-soup-buffers
+       (setq buf (car gnus-soup-buffers)
+             gnus-soup-buffers (cdr gnus-soup-buffers))
+       (if (not (buffer-name buf))
+           ()
+         (set-buffer buf)
+         (and (buffer-modified-p) (save-buffer))
+         (kill-buffer (current-buffer)))))
+    (let ((prefix gnus-soup-last-prefix))
+      (while prefix
+       (gnus-set-work-buffer)
+       (insert (format "(setq gnus-soup-prev-prefix %d)\n" 
+                       (cdr (car prefix))))
+       (write-region (point-min) (point-max)
+                     (concat (car (car prefix)) 
+                             gnus-soup-prefix-file) 
+                     nil 'nomesg)
+       (setq prefix (cdr prefix))))))
+
+(defun gnus-soup-pack (dir packer)
+  (let* ((files (mapconcat 'identity
+                          '("AREAS" "*.MSG" "*.IDX" "INFO"
+                            "LIST" "REPLIES" "COMMANDS" "ERRORS")
+                          " "))
+        (packer (if (< (string-match "%s" packer)
+                       (string-match "%d" packer))
+                    (format packer files
+                            (string-to-int (gnus-soup-unique-prefix dir)))
+                  (format packer 
+                          (string-to-int (gnus-soup-unique-prefix dir))
+                          files)))
+        (dir (expand-file-name dir)))
+    (setq gnus-soup-areas nil)
+    (message "Packing %s..." packer)
+    (if (zerop (call-process "sh" nil nil nil "-c" 
+                            (concat "cd " dir " ; " packer)))
+       (progn
+         (call-process "sh" nil nil nil "-c" 
+                       (concat "cd " dir " ; rm " files))
+         (message "Packing...done" packer))
+      (error "Couldn't pack packet."))))
+
+(defun gnus-soup-parse-areas (file)
+  "Parse soup area file FILE.
+The result is a of vectors, each containing one entry from the AREA file.
+The vector contain five strings, 
+  [prefix name encoding description number]
+though the two last may be nil if they are missing."
+  (let (areas)
+    (save-excursion
+      (set-buffer (find-file-noselect file 'force))
+      (buffer-disable-undo (current-buffer))
+      (goto-char (point-min))
+      (while (not (eobp))
+       (setq areas
+             (cons (vector (gnus-soup-field) 
+                           (gnus-soup-field)
+                           (gnus-soup-field)
+                           (and (eq (preceding-char) ?\t)
+                                (gnus-soup-field))
+                           (and (eq (preceding-char) ?\t)
+                                (string-to-int (gnus-soup-field))))
+                   areas))
+       (if (eq (preceding-char) ?\t)
+           (beginning-of-line 2))))
+    areas))
+
+(defun gnus-soup-parse-replies (file)
+  "Parse soup REPLIES file FILE.
+The result is a of vectors, each containing one entry from the REPLIES
+file. The vector contain three strings, [prefix name encoding]."
+  (let (replies)
+    (save-excursion
+      (set-buffer (find-file-noselect file))
+      (buffer-disable-undo (current-buffer))
+      (goto-char (point-min))
+      (while (not (eobp))
+       (setq replies
+             (cons (vector (gnus-soup-field) (gnus-soup-field)
+                           (gnus-soup-field))
+                   replies))
+       (if (eq (preceding-char) ?\t)
+           (beginning-of-line 2))))
+    replies))
+
+(defun gnus-soup-field ()
+  (prog1
+      (buffer-substring (point) (progn (skip-chars-forward "^\t\n") (point)))
+    (forward-char 1)))
+
+(defun gnus-soup-read-areas ()
+  (or gnus-soup-areas
+      (setq gnus-soup-areas
+           (gnus-soup-parse-areas (concat gnus-soup-directory "AREAS")))))
+
+(defun gnus-soup-write-areas ()
+  "Write all areas to disk."
+  (interactive)
+  (if (not gnus-soup-areas)
+      ()
+    (save-excursion
+      (set-buffer (find-file-noselect
+                  (concat gnus-soup-directory "AREAS")))
+      (erase-buffer)
+      (let ((areas gnus-soup-areas)
+           area)
+       (while areas
+         (setq area (car areas)
+               areas (cdr areas))
+         (insert (format "%s\t%s\t%s%s\n"
+                         (gnus-soup-area-prefix area)
+                         (gnus-soup-area-name area) 
+                         (gnus-soup-area-encoding area)
+                         (if (or (gnus-soup-area-description area) 
+                                 (gnus-soup-area-number area))
+                             (concat "\t" (or (gnus-soup-area-description
+                                               area)
+                                              "")
+                                     (if (gnus-soup-area-number area)
+                                         (concat "\t" 
+                                                 (int-to-string
+                                                  (gnus-soup-area-number 
+                                                   area)))
+                                       "")) "")))))
+      (write-region (point-min) (point-max)
+                   (concat gnus-soup-directory "AREAS"))
+      (set-buffer-modified-p nil)
+      (kill-buffer (current-buffer)))))
+
+(defun gnus-soup-write-replies (dir areas)
+  (save-excursion
+    (set-buffer (find-file-noselect (concat dir "REPLIES")))
+    (erase-buffer)
+    (let (area)
+      (while areas
+       (setq area (car areas)
+             areas (cdr areas))
+       (insert (format "%s\t%s\t%s\n"
+                       (gnus-soup-reply-prefix area)
+                       (gnus-soup-reply-kind area) 
+                       (gnus-soup-reply-encoding area)))))
+    (write-region (point-min) (point-max) (concat dir "REPLIES"))
+    (set-buffer-modified-p nil)
+    (kill-buffer (current-buffer))))
+
+(defun gnus-soup-area (group)
+  (gnus-soup-read-areas)
+  (let ((areas gnus-soup-areas)
+       (real-group (gnus-group-real-name group))
+       area result)
+    (while areas
+      (setq area (car areas)
+           areas (cdr areas))
+      (if (equal (gnus-soup-area-name area) real-group)
+         (setq result area)))
+    (or result
+       (setq result
+             (vector (gnus-soup-unique-prefix)
+                     real-group 
+                     (format "%c%c%c"
+                             gnus-soup-encoding-type
+                             gnus-soup-index-type
+                             (if (gnus-member-of-valid 'mail group) ?m ?n))
+                     nil nil)
+             gnus-soup-areas (cons result gnus-soup-areas)))
+    result))
+
+(defun gnus-soup-unique-prefix (&optional dir)
+  (let* ((dir (file-name-as-directory (or dir gnus-soup-directory)))
+        (entry (assoc dir gnus-soup-last-prefix))
+        gnus-soup-prev-prefix)
+    (if entry
+       ()
+      (and (file-exists-p (concat dir gnus-soup-prefix-file))
+          (condition-case nil
+              (load-file (concat dir gnus-soup-prefix-file))
+            (error nil)))
+      (setq gnus-soup-last-prefix 
+           (cons (setq entry (cons dir (or gnus-soup-prev-prefix 0)))
+                 gnus-soup-last-prefix)))
+    (setcdr entry (1+ (cdr entry)))
+    (int-to-string (cdr entry))))
+
+(defun gnus-soup-unpack-packet (dir unpacker packet)
+  (gnus-make-directory dir)
+  (message "Unpacking: %s" (format unpacker packet))
+  (call-process
+   "sh" nil nil nil "-c"
+   (format "cd %s ; %s" (expand-file-name dir) (format unpacker packet)))
+  (message "Unpacking...done"))
+
+(defun gnus-soup-send-packet (packet)
+  (gnus-soup-unpack-packet 
+   gnus-soup-replies-directory gnus-soup-unpacker packet)
+  (let ((replies (gnus-soup-parse-replies 
+                 (concat gnus-soup-replies-directory "REPLIES"))))
+    (save-excursion
+      (while replies
+       (let* ((msg-file (concat gnus-soup-replies-directory
+                                (gnus-soup-reply-prefix (car replies))
+                                ".MSG"))
+              (msg-buf (and (file-exists-p msg-file)
+                            (find-file-noselect msg-file)))
+              (tmp-buf (get-buffer-create " *soup send*"))
+              beg end)
+         (cond 
+          ((/= (gnus-soup-encoding-format 
+                (gnus-soup-reply-encoding (car replies))) ?n)
+           (error "Unsupported encoding"))
+          ((null msg-buf)
+           t)
+          (t
+           (buffer-disable-undo msg-buf)
+           (buffer-disable-undo tmp-buf)
+           (set-buffer msg-buf)
+           (goto-char (point-min))
+           (while (not (eobp))
+             (or (looking-at "#! *rnews +\\([0-9]+\\)")
+                 (error "Bad header."))
+             (forward-line 1)
+             (setq beg (point)
+                   end (+ (point) (string-to-int 
+                                   (buffer-substring 
+                                    (match-beginning 1) (match-end 1)))))
+             (switch-to-buffer tmp-buf)
+             (erase-buffer)
+             (insert-buffer-substring msg-buf beg end)
+             (goto-char (point-min))
+             (search-forward "\n\n")
+             (forward-char -1)
+             (insert mail-header-separator)
+             (cond 
+              ((string= (gnus-soup-reply-kind (car replies)) "news")
+               (message "Sending news message to %s..."
+                        (mail-fetch-field "newsgroups"))
+               (sit-for 1)
+               (gnus-inews-article))
+              ((string= (gnus-soup-reply-kind (car replies)) "mail")
+               (message "Sending mail to %s..."
+                        (mail-fetch-field "to"))
+               (sit-for 1)
+               (gnus-mail-send-and-exit))
+              (t
+               (error "Unknown reply kind")))
+             (set-buffer msg-buf)
+             (goto-char end))
+           (delete-file (buffer-file-name))
+           (kill-buffer msg-buf)
+           (kill-buffer tmp-buf)
+           (message "Sent packet"))))
+       (setq replies (cdr replies)))
+      t)))
+                  
+(provide 'gnus-soup)
+
+;;; gnus-soup.el ends here
diff --git a/lisp/nnsoup.el b/lisp/nnsoup.el
new file mode 100644 (file)
index 0000000..d4ea1d0
--- /dev/null
@@ -0,0 +1,564 @@
+;;; nnsoup.el --- SOUP access for Gnus
+;; Copyright (C) 1995 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;;     Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'nnmail)
+(require 'gnus-soup)
+(require 'gnus-msg)
+
+(defvar nnsoup-directory "~/SOUP/"
+  "*SOUP packet directory directory.")
+
+(defvar nnsoup-replies-directory (concat nnsoup-directory "replies/")
+  "*Directory where outgoing packets will be composed.")
+
+(defvar nnsoup-replies-format-type ?n
+  "*Format of the replies packages.")
+
+(defvar nnsoup-replies-index-type ?n
+  "*Index type of the replies packages.")
+
+(defvar nnsoup-active-file (concat nnsoup-directory "active")
+  "Active file.")
+
+(defvar nnsoup-packer "tar cf - %s | gzip > $HOME/Soupin%d.tgz"
+  "Format string command for packing a SOUP packet.
+The SOUP files will be inserted where the %s is in the string.
+This string MUST contain both %s and %d. The file number will be
+inserted where %d appears.")
+
+(defvar nnsoup-unpacker "gunzip -c %s | tar xvf -"
+  "*Format string command for unpacking a SOUP packet.
+The SOUP packet file name will be inserted at the %s.")
+
+(defvar nnsoup-packet-directory "~/"
+  "*Where nnsoup will look for incoming packets.")
+
+(defvar nnsoup-packet-regexp "Soupout"
+  "*Regular expression matching SOUP packets in `nnsoup-packet-directory'.")
+
+\f
+
+(defconst nnsoup-version "nnsoup 0.0"
+  "nnsoup version.")
+
+(defvar nnsoup-status-string "")
+(defvar nnsoup-group-alist nil)
+(defvar nnsoup-replies-list nil)
+(defvar nnsoup-buffers nil)
+(defvar nnsoup-current-group nil)
+
+\f
+
+;; Server variables.
+
+(defvar nnsoup-current-server nil)
+(defvar nnsoup-server-alist nil)
+(defvar nnsoup-server-variables 
+  (list 
+   (list 'nnsoup-directory nnsoup-directory)
+   (list 'nnsoup-active-file nnsoup-active-file)
+   '(nnsoup-status-string "")
+   '(nnsoup-group-alist nil)))
+
+\f
+
+;;; Interface functions.
+
+(defun nnsoup-retrieve-headers (sequence &optional group server fetch-old)
+  (nnsoup-possibly-change-group group)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (let ((areas (cdr (assoc nnsoup-current-group nnsoup-group-alist)))
+         (articles sequence)
+         (use-nov t)
+         useful-areas this-area-seq)
+      (if (stringp (car sequence))
+         'headers
+       ;; We go through all the areas and find which files the
+       ;; articles in SEQUENCE come from.
+       (while (and areas sequence)
+         ;; Peel off areas that are below sequence.
+         (while (and areas (< (cdr (car (car areas))) (car sequence)))
+           (setq areas (cdr areas)))
+         (if (not areas)
+             ()
+           ;; This is a useful area.
+           (setq useful-areas (cons (car areas) useful-areas)
+                 this-area-seq nil)
+           ;; We take note whether this MSG has a corresponding IDX
+           ;; for later use.
+           (if (or (= (gnus-soup-encoding-index 
+                       (gnus-soup-area-encoding (nth 1 (car areas)))) ?n)
+                   (not (file-exists-p
+                         (nnsoup-file
+                          (gnus-soup-area-prefix (nth 1 (car areas)))))))
+               (setq use-nov nil))
+           ;; We assing the portion of `sequence' that is relevant to
+           ;; this MSG packet to this packet.
+           (while (and sequence (<= (car sequence) (cdr (car (car areas)))))
+             (setq this-area-seq (cons (car sequence) this-area-seq)
+                   sequence (cdr sequence)))
+           (setcar useful-areas (cons (nreverse this-area-seq)
+                                      (car useful-areas)))))
+
+       ;; We now have a list of article numbers and corresponding
+       ;; areas. 
+       (setq useful-areas (nreverse useful-areas))
+
+       ;; Two different approaches depending on whether all the MSG
+       ;; files have corresponding IDX files.  If they all do, we
+       ;; simply return the relevant IDX files and let Gnus sort out
+       ;; what lines are relevant.  If some of the IDX files are
+       ;; missing, we must return HEADs for all the articles.
+       (if use-nov
+           (while useful-areas
+             (goto-char (point-max))
+             (let ((b (point))
+                   (number (car (nth 1 (car useful-areas)))))
+               (insert-buffer-substring
+                (nnsoup-index-buffer
+                 (gnus-soup-area-prefix
+                  (nth 2 (car useful-areas)))))
+               (goto-char b)
+               ;; We have to remove the index number entires and
+               ;; insert article numbers instead.
+               (while (looking-at "[0-9]+")
+                 (replace-match (int-to-string number) t t)
+                 (setq number (1+ number))
+                 (forward-line 1)))
+             (setq useful-areas (cdr useful-areas)))
+         ;; We insert HEADs.
+         (while useful-areas
+           (setq articles (car (car useful-areas))
+                 useful-areas (cdr useful-areas))
+           (while articles
+             (goto-char (point-max))
+             (insert (format "221 %d Article retrieved.\n" (car articles)))
+             (insert-buffer-substring
+              (nnsoup-narrow-to-article 
+               (car articles) (cdr (car useful-areas)) 'head))
+             (goto-char (point-max))
+             (insert ".\n")
+             (setq articles (cdr articles))))
+
+         ;; Fold continuation lines.
+         (goto-char (point-min))
+         (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
+           (replace-match " " t t)))
+       (if use-nov 'nov 'headers)))))
+
+(defun nnsoup-open-server (server &optional defs)
+  (nnsoup-set-variables)
+  (nnheader-init-server-buffer)
+  (if (equal server nnsoup-current-server)
+      t
+    (if nnsoup-current-server
+       (setq nnsoup-server-alist 
+             (cons (list nnsoup-current-server
+                         (nnheader-save-variables nnsoup-server-variables))
+                   nnsoup-server-alist)))
+    (let ((state (assoc server nnsoup-server-alist)))
+      (if state 
+         (progn
+           (nnheader-restore-variables (nth 1 state))
+           (setq nnsoup-server-alist (delq state nnsoup-server-alist)))
+       (nnheader-set-init-variables nnsoup-server-variables defs)))
+    (setq nnsoup-current-server server))
+  (nnsoup-read-active-file))
+
+(defun nnsoup-request-close ()
+  (nnsoup-write-active-file)
+  (nnsoup-write-replies)
+  (while nnsoup-buffers
+    (and (car nnsoup-buffers)
+        (buffer-name (car nnsoup-buffers))
+        (kill-buffer (car nnsoup-buffers)))
+    (setq nnsoup-buffers (cdr nnsoup-buffers)))
+  (setq nnsoup-group-alist nil
+       nnsoup-current-group nil
+       nnsoup-current-server nil
+       nnsoup-server-alist nil
+       nnsoup-replies-list nil)
+  t)
+
+(defun nnsoup-close-server (&optional server)
+  t)
+
+(defun nnsoup-server-opened (&optional server)
+  (and (equal server nnsoup-current-server)
+       nntp-server-buffer
+       (buffer-name nntp-server-buffer)))
+
+(defun nnsoup-status-message (&optional server)
+  nnsoup-status-string)
+
+(defun nnsoup-request-article (id &optional newsgroup server buffer)
+  (nnsoup-possibly-change-group newsgroup)
+  (let ((buffer (or buffer nntp-server-buffer)))
+    (save-excursion
+      (set-buffer buffer)
+      (erase-buffer)
+      (if (stringp id)
+         ()
+       (insert-buffer-substring
+        (nnsoup-narrow-to-article id))
+       t))))
+
+(defun nnsoup-request-group (group &optional server dont-check)
+  (nnsoup-possibly-change-group group)
+  (if dont-check 
+      ()
+    (let ((area (cdr (assoc group nnsoup-group-alist)))
+         min max)
+      (save-excursion
+       (set-buffer nntp-server-buffer)
+       (erase-buffer)
+       (setq min (car (car (car area))))
+       (while (cdr area)
+         (setq area (cdr area)))
+       (setq max (cdr (car (car area))))
+       (insert (format "211 %d %d %d %s\n" 
+                       (max (1+ (- max min)) 0) min max group)))))
+  t)
+
+(defun nnsoup-close-group (group &optional server)
+  t)
+
+(defun nnsoup-request-list (&optional server)
+  (or nnsoup-group-alist (nnsoup-read-areas))
+  (nnsoup-unpack-packets)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (let ((alist nnsoup-group-alist)
+         min)
+      (while alist
+       (setq min (car (car (nth 1 (car alist)))))
+       (insert (format "%s %d %d y\n" (car (car alist))
+                       (let ((areas (car alist)))
+                         (while (cdr areas)
+                           (setq areas (cdr areas)))
+                         (cdr (car (car areas)))) min))
+       (setq alist (cdr alist)))
+      t)))
+
+(defun nnsoup-request-newgroups (date &optional server)
+  (nnsoup-request-list))
+
+(defun nnsoup-request-list-newsgroups (&optional server)
+  nil)
+
+(defun nnsoup-request-post (&optional server)
+  (nnsoup-store-reply "news")
+  t)
+
+(defun nnsoup-request-mail ()
+  (nnsoup-store-reply "mail")
+  t)
+
+(defun nnsoup-request-post-buffer (post group &rest args)
+  (nnsoup-possibly-change-group group)
+  (apply
+   ;; Find out whether the source for this group is a mail or a news
+   ;; group and call the right function for getting a buffer.
+   (let ((enc (nth 1 (car (cdr (assoc nnsoup-current-group
+                                     nnsoup-group-alist))))))
+     (if (and enc
+             (= (gnus-soup-encoding-kind (gnus-soup-area-encoding enc)) ?m))
+        'nnmail-request-post-buffer 
+       'nntp-request-post-buffer))
+   post group args))
+
+\f
+;;; Internal functions
+
+(defun nnsoup-possibly-change-group (group &optional force)
+  (if group
+      (setq nnsoup-current-group group)
+    t))
+
+(defun nnsoup-read-active-file ()
+  (if (file-exists-p nnsoup-active-file)
+      (condition-case ()
+         (load nnsoup-active-file)
+       (error nil))))
+
+(defun nnsoup-write-active-file ()
+  (save-excursion
+    (set-buffer (get-buffer-create " *nnsoup work*"))
+    (buffer-disable-undo (current-buffer))
+    (erase-buffer)
+    (insert (format "(setq nnsoup-group-alist '%S)\n" nnsoup-group-alist))
+    (write-region (point-min) (point-max) nnsoup-active-file
+                 nil 'silent)
+    (kill-buffer (current-buffer))))
+
+(defun nnsoup-read-areas ()
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (let ((areas (gnus-soup-parse-areas (concat nnsoup-directory "AREAS")))
+         entry number area lnum)
+      ;; Go through all areas in the new AREAS file.
+      (while areas
+       (setq area (car areas)
+             areas (cdr areas))
+       ;; Find the number of new articles in this area.
+       (setq number (nnsoup-number-of-articles area))
+       (if (not (setq entry (assoc (gnus-soup-area-name area)
+                                   nnsoup-group-alist)))
+           ;; If this is a new area (group), we just add this info to
+           ;; the group alist. 
+           (setq nnsoup-group-alist
+                 (cons (list (gnus-soup-area-name area)
+                             (list (cons 1 number) area))
+                       nnsoup-group-alist))
+         ;; There are already articles in this group, so we add this
+         ;; info to the end of the entry.
+         (let ((e (cdr entry)))
+           (while (cdr e)
+             (setq e (cdr e)))
+           (setcdr e (list (list (cons (setq lnum (1+ (cdr (car (car e)))))
+                                       (+ lnum number)) 
+                                 area)))))))
+    (nnsoup-write-active-file)))
+
+(defun nnsoup-number-of-articles (area)
+  (save-excursion
+    (cond 
+     ;; If the number is in the area info, we just return it.
+     ((gnus-soup-area-number area)
+      (gnus-soup-area-number area))
+     ;; If there is an index file, we just count the lines.
+     ((/= (gnus-soup-encoding-index (gnus-soup-area-encoding area)) ?n)
+      (set-buffer (nnsoup-index-buffer (gnus-soup-area-prefix area)))
+      (count-lines (point-min) (point-max)))
+     ;; We do it the hard way - re-searching through the message
+     ;; buffer. 
+     (t
+      (set-buffer (nnsoup-message-buffer (gnus-soup-area-prefix area)))
+      (goto-char (point-min))
+      (let ((regexp (nnsoup-header (gnus-soup-encoding-format 
+                                   (gnus-soup-area-encoding area))))
+           (num 0))
+       (while (re-search-forward regexp nil t)
+         (setq num (1+ num)))
+       num)))))
+
+(defun nnsoup-index-buffer (prefix &optional message)
+  (let* ((file (concat prefix (if message ".MSG" ".IDX")))
+        (buffer-name (concat " *nnsoup " file "*")))
+    (or (get-buffer buffer-name)       ; File aready loaded.
+       (save-excursion                 ; Load the file.
+         (set-buffer (get-buffer-create buffer-name))
+         (setq nnsoup-buffers (cons (current-buffer) nnsoup-buffers))
+         (insert-file-contents (concat nnsoup-directory file))
+         (current-buffer)))))
+
+(defun nnsoup-file (prefix &optional message)
+  (concat nnsoup-directory prefix (if message ".MSG" ".IDX")))
+
+(defun nnsoup-message-buffer (prefix)
+  (nnsoup-index-buffer prefix 'msg))
+
+(defun nnsoup-unpack-packets ()
+  (let ((packets (directory-files
+                 nnsoup-packet-directory t nnsoup-packet-regexp))
+       msg)
+    (while packets
+      (message (setq msg (format "nnsoup: unpacking %s..." (car packets))))
+      (gnus-soup-unpack-packet nnsoup-directory nnsoup-unpacker (car packets))
+      (delete-file (car packets))
+      (nnsoup-read-areas)
+      (message "%sdone" msg)
+      (setq packets (cdr packets)))))
+
+(defun nnsoup-narrow-to-article (article &optional area head)
+  (let* ((area (or area (nnsoup-article-to-area article nnsoup-current-group)))
+        (prefix (gnus-soup-area-prefix (nth 1 area)))
+        beg end msg-buf)
+    (setq msg-buf (nnsoup-index-buffer prefix 'msg))
+    (save-excursion
+      (cond
+       ;; We use the index file to find out where the article begins and ends. 
+       ((and (= (gnus-soup-encoding-index 
+                (gnus-soup-area-encoding (nth 1 area)))
+               ?c)
+            (file-exists-p (nnsoup-file prefix)))
+       (set-buffer (nnsoup-index-buffer prefix))
+       (widen)
+       (goto-char (point-min))
+       (forward-line (- article (car (car area))))
+       (setq beg (read (current-buffer)))
+       (forward-line 1)
+       (if (looking-at "[0-9]+")
+           (progn
+             (setq end (read (current-buffer)))
+             (set-buffer msg-buf)
+             (widen)
+             (let ((format (gnus-soup-encoding-format
+                            (gnus-soup-area-encoding (nth 1 area)))))
+               (goto-char end)
+               (if (or (= format ?n) (= format ?m))
+                   (setq end (progn (forward-line -1) (point))))))
+         (set-buffer msg-buf))
+       (widen)
+       (narrow-to-region beg (or end (point-max))))
+       (t
+       (set-buffer msg-buf)
+       (widen)
+       (goto-char (point-min))
+       (let ((header (nnsoup-header 
+                      (gnus-soup-encoding-format 
+                       (gnus-soup-area-encoding (nth 1 area))))))
+         (re-search-forward header nil t (- article (car (car area))))
+         (narrow-to-region
+          (match-beginning 0)
+          (if (re-search-forward header nil t)
+              (match-beginning 0)
+            (point-max))))))
+      (goto-char (point-min))
+      (if (not head)
+         ()
+       (narrow-to-region
+        (point-min)
+        (if (search-forward "\n\n" nil t)
+            (1- (point))
+          (point-max))))
+      msg-buf)))
+
+(defun nnsoup-header (format)
+  (cond 
+   ((= format ?n)
+    "^#! *rnews +[0-9]+ *$")
+   ((= format ?m)
+    (concat "^" rmail-unix-mail-delimiter))
+   ((= format ?M)
+    "^\^A\^A\^A\^A\n")
+   (t
+    (error "Unknown format: %c" format))))
+
+(defun nnsoup-pack-replies ()
+  "Make an outbound package of SOUP replies."
+  (interactive)
+  (nnsoup-write-active-file)
+  (nnsoup-write-replies)
+  (gnus-soup-pack nnsoup-replies-directory nnsoup-packer))
+
+(defun nnsoup-write-replies ()
+  (gnus-soup-write-replies nnsoup-replies-directory nnsoup-replies-list))
+
+(defun nnsoup-article-to-area (article group)
+  (let ((areas (cdr (assoc group nnsoup-group-alist))))
+    (while (and areas (< (cdr (car (car areas))) article))
+      (setq areas (cdr areas)))
+    (and areas (car areas))))
+
+(defun nnsoup-set-variables ()
+  (setq gnus-inews-article-function 'nnsoup-request-post)
+  (setq gnus-mail-send-method 'nnsoup-request-mail)
+  (setq send-mail-function 'nnsoup-request-mail))
+
+(defun nnsoup-store-reply (kind)
+  ;; Mostly stolen from `sendmail.el'.
+  (let ((tembuf (generate-new-buffer " sendmail temp"))
+       (case-fold-search nil)
+       (mailbuf (current-buffer))
+       delimline)
+    (save-excursion
+      (set-buffer tembuf)
+      (erase-buffer)
+      (insert-buffer-substring mailbuf)
+      (goto-char (point-max))
+      ;; require one newline at the end.
+      (or (= (preceding-char) ?\n)
+         (insert ?\n))
+      ;; Change header-delimiter to be what sendmail expects.
+      (goto-char (point-min))
+      (re-search-forward
+       (concat "^" (regexp-quote mail-header-separator) "\n"))
+      (replace-match "\n")
+      (backward-char 1)
+      (setq delimline (point-marker))
+      (if mail-aliases (expand-mail-aliases (point-min) delimline))
+      (goto-char (point-min))
+      ;; ignore any blank lines in the header
+      (while (and (re-search-forward "\n\n\n*" delimline t)
+                 (< (point) delimline))
+       (replace-match "\n"))
+      (let ((case-fold-search t))
+       (goto-char (point-min))
+       ;; Find and handle any FCC fields.
+       (goto-char (point-min))
+       (if (re-search-forward "^FCC:" delimline t)
+           (mail-do-fcc delimline))
+       (goto-char (point-min))
+       ;; "S:" is an abbreviation for "Subject:".
+       (goto-char (point-min))
+       (if (re-search-forward "^S:" delimline t)
+           (replace-match "Subject:"))
+       ;; Don't send out a blank subject line
+       (goto-char (point-min))
+       (if (re-search-forward "^Subject:[ \t]*\n" delimline t)
+           (replace-match ""))
+       ;; Insert an extra newline if we need it to work around
+       ;; Sun's bug that swallows newlines.
+       (goto-char (1+ delimline))
+       (if (eval mail-mailer-swallows-blank-line)
+           (newline)))
+      (gnus-soup-store 
+       nnsoup-replies-directory 
+       (nnsoup-kind-to-prefix kind) nil nnsoup-replies-format-type
+       nnsoup-replies-index-type)
+      (kill-buffer tembuf))))
+
+(defun nnsoup-kind-to-prefix (kind)
+  (or nnsoup-replies-list
+      (setq nnsoup-replies-list
+           (gnus-soup-parse-replies 
+            (concat nnsoup-replies-directory "REPLIES"))))
+  (let ((replies nnsoup-replies-list))
+    (while (and replies 
+               (not (string= kind (gnus-soup-reply-kind (car replies)))))
+      (setq replies (cdr replies)))
+    (if replies
+       (gnus-soup-reply-prefix (car replies))
+      (setq nnsoup-replies-list
+           (cons (vector (gnus-soup-unique-prefix nnsoup-replies-directory)
+                         kind 
+                         (format "%c%c%c"
+                                 nnsoup-replies-format-type
+                                 nnsoup-replies-index-type
+                                 (if (string= kind "news")
+                                     ?n ?m)))
+                 nnsoup-replies-list))
+      (gnus-soup-reply-prefix (car nnsoup-replies-list)))))
+       
+(provide 'nnsoup)
+
+;;; nnsoup.el ends here
diff --git a/texi/ChangeLog b/texi/ChangeLog
new file mode 100644 (file)
index 0000000..8472e3a
--- /dev/null
@@ -0,0 +1,26 @@
+Wed Sep 20 16:14:11 1995  Lars Ingebrigtsen  <lars@eyesore.no>
+
+       * gnus.texi (Drafts): New.
+       (Thread Commands): Addition.
+       (Rejected Articles): New.
+
+Tue Sep 19 00:30:15 1995  Lars Ingebrigtsen  <lars@eyesore.no>
+
+       * gnus.texi (Mail): Addition.
+       (Mail Group Commands): Addition.
+       (Group Parameters): Addition.
+       (Summary Buffer Mode Line): Addition.
+       (Finding the Parent): Addition.
+
+Mon Sep 18 11:49:16 1995  Lars Ingebrigtsen  <lars@eyesore.no>
+
+       * gnus.texi (Limiting): New.
+       (Slave Gnusii): New.
+       (Setting Process Marks): Addition.
+       (The Server is Down): Change.
+       (Article Treatment): New.
+       (Article Hiding): New.
+       (Article Washing): New.
+       (Article Date): New.
+       (Finding the News): Addition.
+       (Customizing Threading): Addition.