diff lisp/gnus-edit.el @ 13401:178d730efae2

entered into RCS
author Lars Magne Ingebrigtsen <larsi@gnus.org>
date Sat, 04 Nov 1995 03:54:42 +0000
parents
children a30c26085053
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus-edit.el	Sat Nov 04 03:54:42 1995 +0000
@@ -0,0 +1,628 @@
+;;; gnus-edit.el --- Gnus SCORE file editing
+;; Copyright (C) 1995 Free Software Foundation, Inc.
+;;
+;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
+;; Keywords: news, help
+;; Version: 0.2
+
+;;; Commentary:
+;;
+;; Type `M-x gnus-score-customize RET' to invoke.
+
+;;; Code:
+
+(require 'custom)
+(require 'gnus-score)
+
+(defconst gnus-score-custom-data
+  '((tag . "Score")
+    (doc . "Customization of Gnus SCORE files.
+
+SCORE files allow you to assign a score to each article when you enter
+a group, and automatically mark the articles as read or delete them
+based on the score.  In the summary buffer you can use the score to
+sort the articles by score (`C-c C-s C-s') or to jump to the unread
+article with the highest score (`,').")
+    (type . group)
+    (data "\n"
+	  ((header . nil)
+	   (doc . "Name of SCORE file to customize.
+
+Enter the name in the `File' field, then push the [Load] button to
+load it.  When done editing, push the [Save] button to save the file.
+
+Several score files may apply to each group, and several groups may
+use the same score file.  This is controlled implicitly by the name of
+the score file and the value of the global variable
+`gnus-score-find-score-files-function', and explicitly by the the
+`Files' and `Exclude Files' entries.") 
+	   (compact . t)
+	   (type . group)
+	   (data ((tag . "Load")
+		  (type . button)
+		  (query . gnus-score-custom-load))
+		 ((tag . "Save")
+		  (type . button)
+		  (query . gnus-score-custom-save))
+		 ((name . file)
+		  (tag . "File")
+		  (directory . "~/News/")
+		  (default-file . "SCORE")
+		  (type . file))))
+	  ((name . files)
+	   (tag . "Files")
+	   (doc . "\
+List of score files to load when the the current score file is loaded.
+You can use this to share score entries between multiple score files.
+
+Push the `[INS]' button add a score file to the list, or `[DEL]' to
+delete a score file from the list.")
+	   (type . list)
+	   (data ((type . repeat)
+		  (header . nil)
+		  (data (type . file)
+			(directory . "~/News/")))))
+	  ((name . exclude-files)
+	   (tag . "Exclude Files")
+	   (doc . "\
+List of score files to exclude when the the current score file is loaded.
+You can use this if you have a score file you want to share between a
+number of newsgroups, except for the newsgroup this score file
+matches.  [ Did anyone get that? ]
+
+Push the `[INS]' button add a score file to the list, or `[DEL]' to
+delete a score file from the list.")
+	   (type . list)
+	   (data ((type . repeat)
+		  (header . nil)
+		  (data (type . file)
+			(directory . "~/News/")))))
+	  ((name . mark)
+	   (tag . "Mark")
+	   (doc . "\
+Articles below this score will be automatically marked as read.
+
+This means that when you enter the summary buffer, the articles will
+be shown but will already be marked as read.  You can then press `x'
+to get rid of them entirely.
+
+By default articles with a negative score will be marked as read.  To
+change this, push the `Mark' button, and choose `Integer'.  You can
+then enter a value in the `Mark' field.")
+	   (type . gnus-score-custom-maybe-type))
+	  ((name . expunge)
+	   (tag . "Expunge")
+	   (doc . "\
+Articles below this score will not be shown in the summary buffer.")
+	   (type . gnus-score-custom-maybe-type))
+	  ((name . mark-and-expunge)
+	   (tag . "Mark and Expunge")
+	   (doc . "\
+Articles below this score will be marked as read, but not shown.
+
+Someone should explain me the difference between this and `expunge'
+alone or combined with `mark'.")
+	   (type . gnus-score-custom-maybe-type))
+	  ((name . eval)
+	   (tag . "Eval")
+	   (doc . "\
+Evaluate this lisp expression when the entering summary buffer.")
+	   (type . sexp))
+	  ((name . read-only)
+	   (tag . "Read Only")
+	   (doc . "Read-only score files will not be updated or saved.
+Except from this buffer, of course!")
+	   (type . toggle))
+	  ((type . doc)
+	   (doc . "\
+Each news header has an associated list of score entries.  
+You can use the [INS] buttons to add new score entries anywhere in the
+list, or the [DEL] buttons to delete specific score entries.
+
+Each score entry should specify a string that should be matched with
+the content actual header in order to determine whether the entry
+applies to that header.  Enter that string in the `Match' field.
+
+If the score entry matches, the articles score will be adjusted with
+some amount.  Enter that amount in the in the `Score' field.  You
+should specify a positive amount for score entries that matches
+articles you find interesting, and a negative amount for score entries
+matching articles you would rather avoid.  The final score for the
+article will be the sum of the score of all score entries that match
+the article. 
+
+The score entry can be either permanent or expirable.  To make the
+entry permanent, push the `Date' button and choose the `Permanent'
+entry.  To make the entry expirable, choose instead the `Integer'
+entry.  After choosing the you can enter the date the score entry was
+last matched in the `Date' field.  The date will be automatically
+updated each time the score entry matches an article.  When the date
+become too old, the the score entry will be removed.
+
+For your convenience, the date is specified as the number of days
+elapsed since the (imaginary) Gregorian date Sunday, December 31, 1
+BC.
+
+Finally, you can choose what kind of match you want to perform by
+pushing the `Type' button.  For most entries you can choose between
+`Exact' which mean the header content must be exactly identical to the
+match string, or `Substring' meaning the match string should be
+somewhere in the header content, or even `Regexp' to use Emacs regular
+expression matching.  The last choice is `Fuzzy' which is like `Exact'
+except that whitespace derivations, a beginning `Re:' or a terminating
+parenthetical remark are all ignored.  Each of the four types have a
+variant which will ignore case in the comparison.  That variant is
+indicated with a `(fold)' after its name."))
+	  ((name . from)
+	   (tag . "From")
+	   (doc . "Scoring based on the authors email address.")
+	   (type . gnus-score-custom-string-type))
+	  ((name . subject)
+	   (tag . "Subject")
+	   (doc . "Scoring based on the articles subject.")
+	   (type . gnus-score-custom-string-type))
+	  ((name . followup)
+	   (tag . "Followup")
+	   (doc . "Scoring based on who the article is a followup to.
+
+If you want to see all followups to your own articles, add an entry
+with a positive score matching your email address here.  You can also
+put an entry with a negative score matching someone who is so annoying
+that you don't even want to see him quoted in followups.")
+	   (type . gnus-score-custom-string-type))
+	  ((name . xref)
+	   (tag . "Xref")
+	   (doc . "Scoring based on article crossposting.
+
+If you want to score based on which newsgroups an article is posted
+to, this is the header to use.  The syntax is a little different from
+the `Newsgroups' header, but scoring in `Xref' is much faster.  As an
+example, to match all crossposted articles match on `:.*:' using the
+`Regexp' type.")
+	   (type . gnus-score-custom-string-type))
+	  ((name . references)
+	   (tag . "References")
+	   (doc . "Scoring based on article references.
+
+The `References' header gives you an alternative way to score on
+followups.  If you for example want to see follow all discussions
+where people from `iesd.auc.dk' school participate, you can add a
+substring match on `iesd.auc.dk>' on this header.")
+	   (type . gnus-score-custom-string-type))
+	  ((name . message-id)
+	   (tag . "Message-ID")
+	   (doc . "Scoring based on the articles message-id.
+
+This isn't very useful, but Lars like completeness.  You can use it to
+match all messaged generated by recent Gnus version with a `Substring'
+match on `.fsf@'.")
+	   (type . gnus-score-custom-string-type))
+	  ((type . doc)
+	   (doc . "\
+WARNING:  Scoring on the following three pseudo headers is very slow!
+Scoring on any of the real headers use a technique that avoids
+scanning the entire article, only the actual headers you score on are
+scanned, and this scanning has been heavily optimized.  Using just a
+single entry for one the three pseudo-headers `Head', `Body', and
+`All' will require GNUS to retrieve and scan the entire article, which
+can be very slow on large groups.  However, if you add one entry for
+any of these headers, you can just as well add several.  Each
+subsequent entry cost relatively little extra time."))
+	  ((name . head)
+	   (tag . "Head")
+	   (doc . "Scoring based on the article header.
+
+Instead of matching the content of a single header, the entire header
+section of the article is matched.  You can use this to match on
+arbitrary headers, foe example to single out TIN lusers, use a substring
+match on `Newsreader: TIN'.  That should get 'em!")
+	   (type . gnus-score-custom-string-type))
+	  ((name . body)
+	   (tag . "Body")
+	   (doc . "Scoring based on the article body.
+
+If you think any article that mentions `Kibo' is inherently
+interesting, do a substring match on His name.  You Are Allowed.")
+	   (type . gnus-score-custom-string-type))
+	  ((name . all)
+	   (tag . "All")
+	   (doc . "Scoring based on the whole article.")
+	   (type . gnus-score-custom-string-type))
+	  ((name . date)
+	   (tag . "Date")
+	   (doc . "Scoring based on article date.
+
+You can change the score of articles that have been posted before,
+after, or at a specific date.  You should add the date in the `Match'
+field, and then select `before', `after', or `at' by pushing the
+`Type' button.  Imagine you want to lower the score of very old
+articles, or want to raise the score of articles from the future (such
+things happen!).  Then you can't use date scoring for that.  In fact,
+I can't imagine anything you would want to use this for.   
+
+For your convenience, the date is specified in Usenet date format.")
+	   (type . gnus-score-custom-date-type))
+	  ((type . doc)
+	   (doc . "\
+The Lines and Chars headers use integer based scoring.  
+
+This means that you should write an integer in the `Match' field, and
+the push the `Type' field to if the `Chars' or `Lines' header should
+be larger, equal, or smaller than the number you wrote in the match
+field."))
+	  ((name . chars)
+	   (tag . "Characters")
+	   (doc . "Scoring based on the number of characters in the article.")
+	   (type . gnus-score-custom-integer-type))
+	  ((name . lines)
+	   (tag . "Lines")
+	   (doc . "Scoring based on the number of lines in the article.")
+	   (type . gnus-score-custom-integer-type))
+	  ((name . orphan)
+	   (tag . "Orphan")
+	   (doc . "Score to add to articles with no parents.")
+	   (type . gnus-score-custom-maybe-type))
+	  ((name . adapt)
+	   (tag . "Adapt")
+	   (doc . "Adapting the score files to your newsreading habits.
+
+When you have finished reading a group GNUS can automatically create
+new score entries based on which articles you read and which you
+skipped.  This is normally controled by the two global variables
+`gnus-use-adaptive-scoring' and `gnus-default-adaptive-score-alist',
+The first determines whether adaptive scoring should be enabled or
+not, while the second determines what score entries should be created.
+
+You can overwrite the setting of `gnus-use-adaptive-scoring' by
+selecting `Enable' or `Disable' by pressing the `Adapt' button.
+Selecting `Custom' will allow you to specify the exact adaption
+rules (overwriting `gnus-default-adaptive-score-alist').")
+	   (type . choice)
+	   (data ((tag . "Default")
+		  (default . nil)
+		  (type . const))
+		 ((tag . "Enable")
+		  (default . t)
+		  (type . const))
+		 ((tag . "Disable")
+		  (default . ignore)
+		  (type . const))
+		 ((tag . "Custom")
+		  (doc . "Customization of adaptive scoring.
+
+Each time you read an article it will be marked as read.  Likewise, if
+you delete it it will be marked as deleted, and if you tick it it will
+be marked as ticked.  When you leave a group, GNUS can automatically
+create score file entries based on these marks, so next time you enter
+the group articles with subjects that you read last time have higher
+score and articles with subjects that deleted will have lower score.  
+
+Below is a list of such marks.  You can insert new marks to the list
+by pushing on one of the `[INS]' buttons in the left margin to create
+a new entry and then pushing the `Mark' button to select the mark.
+For each mark there is another list, this time of article headers,
+which determine how the mark should affect that header.  The `[INS]'
+buttons of this list are indented to indicate that the belong to the
+mark above.  Push the `Header' button to choose a header, and then
+enter a score value in the `Score' field.   
+
+For each article that are marked with `Mark' when you leave the
+group, a temporary score entry for the articles `Header' with the
+value of `Score' will be added the adapt file.  If the score entry
+already exists, `Score' will be added to its value.  If you understood
+that, you are smart.
+
+You can select the special value `Other' when pressing the `Mark' or
+`Header' buttons.  This is because Lars might add more useful values
+there.  If he does, it is up to you to figure out what they are named.")
+		  (type . list)
+		  (default . ((__uninitialized__)))
+		  (data ((type . repeat)
+			 (header . nil)
+			 (data . ((type . list)
+				  (header . nil)
+				  (compact . t)
+				  (data ((type . choice)
+					 (tag . "Mark")
+					 (data ((tag . "Unread")
+						(default . gnus-unread-mark)
+						(type . const))
+					       ((tag . "Ticked")
+						(default . gnus-ticked-mark)
+						(type . const))
+					       ((tag . "Dormant")
+						(default . gnus-dormant-mark)
+						(type . const))
+					       ((tag . "Deleted")
+						(default . gnus-del-mark)
+						(type . const))
+					       ((tag . "Read")
+						(default . gnus-read-mark)
+						(type . const))
+					       ((tag . "Expirable")
+						(default . gnus-expirable-mark)
+						(type . const))
+					       ((tag . "Killed")
+						(default . gnus-killed-mark)
+						(type . const))
+					       ((tag . "Kill-file")
+						(default . gnus-kill-file-mark)
+						(type . const))
+					       ((tag . "Low-score")
+						(default . gnus-low-score-mark)
+						(type . const))
+					       ((tag . "Catchup")
+						(default . gnus-catchup-mark)
+						(type . const))
+					       ((tag . "Ancient")
+						(default . gnus-ancient-mark)
+						(type . const))
+					       ((tag . "Canceled")
+						(default . gnus-canceled-mark)
+						(type . const))
+					       ((prompt . "Other")
+						(default . ??)
+						(type . sexp))))
+					((type . repeat)
+					 (prefix . "            ")
+					 (data . ((type . list)
+						  (compact . t)
+						  (data ((tag . "Header")
+							 (type . choice)
+							 (data ((tag . "Subject")
+								(default . subject)
+								(type . const))
+							       ((prompt . "From")
+								(tag . "From   ")
+								(default . from)
+								(type . const))
+							       ((prompt . "Other")
+								(width . 7)
+								(default . nil)
+								(type . symbol))))
+							((tag . "Score")
+							 (type . integer))))))))))))))
+	  ((name . local)
+	   (tag . "Local")
+	   (doc . "\
+List of local variables to set when this score file is loaded.
+
+Using this entry can provide a convenient way to set variables that
+will affect the summary mode for only some specific groups, i.e. those
+groups matched by the current score file.")
+	   (type . list)
+	   (data ((type . repeat)
+		  (header . nil)
+		  (data . ((type . list)
+			   (compact . t)
+			   (data ((tag . "Name")
+				  (width . 26)
+				  (type . symbol))
+				 ((tag . "Value")
+				  (width . 26)
+				  (type . sexp)))))))))))
+
+(defconst gnus-score-custom-type-properties
+  '((gnus-score-custom-maybe-type
+     (type . choice)
+     (data ((type . integer)
+	    (default . 0))
+	   ((tag . "Default")
+	    (type . const)
+	    (default . nil))))
+    (gnus-score-custom-string-type
+     (type . list)
+     (data ((type . repeat)
+	    (header . nil)
+	    (data . ((type . list)
+		     (compact . t)
+		     (data ((tag . "Match")
+			    (width . 59)
+			    (type . string))
+			   "\n            "
+			   ((tag . "Score")
+			    (type . integer))
+			   ((tag . "Date")
+			    (type . choice)
+			    (data ((type . integer)
+				   (default . 0)
+				   (width . 9))
+				  ((tag . "Permanent")
+				   (type . const)
+				   (default . nil))))
+			   ((tag . "Type")
+			    (type . choice)
+			    (data ((tag . "Exact")
+				   (default . E)
+				   (type . const))
+				  ((tag . "Substring")
+				   (default . S) 
+				   (type . const))
+				  ((tag . "Regexp")
+				   (default . R)
+				   (type . const))
+				  ((tag . "Fuzzy")
+				   (default . F)
+				   (type . const))
+				  ((tag . "Exact (fold)")
+				   (default . e)
+				   (type . const))
+				  ((tag . "Substring (fold)")
+				   (default . s) 
+				   (type . const))
+				  ((tag . "Regexp (fold)")
+				   (default . r)
+				   (type . const))
+				  ((tag . "Fuzzy  (fold)")
+				   (default . f)
+				   (type . const))))))))))
+    (gnus-score-custom-integer-type
+     (type . list)
+     (data ((type . repeat)
+	    (header . nil)
+	    (data . ((type . list)
+		     (compact . t)
+		     (data ((tag . "Match")
+			    (type . integer))
+			   ((tag . "Score")
+			    (type . integer))
+			   ((tag . "Date")
+			    (type . choice)
+			    (data ((type . integer)
+				   (default . 0)
+				   (width . 9))
+				  ((tag . "Permanent")
+				   (type . const)
+				   (default . nil))))
+			   ((tag . "Type")
+			    (type . choice)
+			    (data ((tag . "<")
+				   (default . <)
+				   (type . const))
+				  ((tag . ">")
+				   (default . >) 
+				   (type . const))
+				  ((tag . "=")
+				   (default . =)
+				   (type . const))
+				  ((tag . ">=")
+				   (default . >=)
+				   (type . const))
+				  ((tag . "<=")
+				   (default . <=)
+				   (type . const))))))))))
+    (gnus-score-custom-date-type
+     (type . list)
+     (data ((type . repeat)
+	    (header . nil)
+	    (data . ((type . list)
+		     (compact . t)
+		     (data ((tag . "Match")
+			    (width . 59)
+			    (type . string))
+			   "\n           "
+			   ((tag . "Score")
+			    (type . integer))
+			   ((tag . "Date")
+			    (type . choice)
+			    (data ((type . integer)
+				   (default . 0)
+				   (width . 9))
+				  ((tag . "Permanent")
+				   (type . const)
+				   (default . nil))))
+			   ((tag . "Type")
+			    (type . choice)
+			    (data ((tag . "Before")
+				   (default . before)
+				   (type . const))
+				  ((tag . "After")
+				   (default . after) 
+				   (type . const))
+				  ((tag . "At")
+				   (default . at)
+				   (type . const))))))))))))
+
+(defvar gnus-score-custom-file nil
+  "Name of SCORE file being customized.")
+
+(defun gnus-score-customize ()
+  "Create a buffer for editing gnus SCORE files."
+  (interactive)
+  (let (gnus-score-alist)
+    (custom-buffer-create "*Score Edit*" gnus-score-custom-data
+			  gnus-score-custom-type-properties
+			  'gnus-score-custom-set
+			  'gnus-score-custom-get
+			  'gnus-score-custom-save))
+  (make-local-variable 'gnus-score-custom-file)
+  (setq gnus-score-custom-file (expand-file-name  "SCORE" "~/News"))
+  (make-local-variable 'gnus-score-alist)
+  (setq gnus-score-alist nil)
+  (custom-reset-all))
+
+(defun gnus-score-custom-get (name)
+  (if (eq name 'file)
+      gnus-score-custom-file
+    (let ((entry (assoc (symbol-name name) gnus-score-alist)))
+      (if entry 
+	  (mapcar 'gnus-score-custom-sanify (cdr entry))
+	(setq entry (assoc name gnus-score-alist))
+	(if (or (memq name '(files exclude-files local))
+		(and (eq name 'adapt)
+		     (not (symbolp (car (cdr entry))))))
+	    (cdr entry)
+	  (car (cdr entry)))))))
+
+(defun gnus-score-custom-set (name value)
+  (cond ((eq name 'file)
+	 (setq gnus-score-custom-file value))
+	((assoc (symbol-name name) gnus-score-alist)
+	 (if value
+	     (setcdr (assoc (symbol-name name) gnus-score-alist) value)
+	   (setq gnus-score-alist (delq (assoc (symbol-name name) 
+					       gnus-score-alist) 
+					gnus-score-alist))))
+	((assoc (symbol-name name) gnus-header-index)
+	 (if value
+	     (setq gnus-score-alist 
+		   (cons (cons (symbol-name name) value) gnus-score-alist))))
+	((assoc name gnus-score-alist)
+	 (cond ((null value)
+		(setq gnus-score-alist (delq (assoc name gnus-score-alist)
+					     gnus-score-alist)))
+	       ((and (listp value) (not (eq name 'eval)))
+		(setcdr (assoc name gnus-score-alist) value))
+	       (t
+		(setcdr (assoc name gnus-score-alist) (list value)))))
+	((null value))
+	((and (listp value) (not (eq name 'eval)))
+	 (setq gnus-score-alist (cons (cons name value) gnus-score-alist)))
+	(t
+	 (setq gnus-score-alist 
+	       (cons (cons name (list value)) gnus-score-alist)))))
+
+(defun gnus-score-custom-sanify (entry)
+  (list (nth 0 entry)
+	(or (nth 1 entry) gnus-score-interactive-default-score)
+	(nth 2 entry)
+	(cond ((null (nth 3 entry))
+	       's)
+	      ((memq (nth 3 entry) '(before after at >= <=))
+	       (nth 3 entry))
+	      (t
+	       (intern (substring (symbol-name (nth 3 entry)) 0 1))))))
+
+(defvar gnus-score-cache nil)
+
+(defun gnus-score-custom-load ()
+  (interactive)
+  (let ((file (custom-name-value 'file)))
+    (if (eq file custom-nil)
+	(error "You must specify a file name"))
+    (setq file (expand-file-name file "~/News"))
+    (gnus-score-load file)
+    (setq gnus-score-custom-file file)
+    (custom-reset-all)
+    (message "Loaded")))
+
+(defun gnus-score-custom-save ()
+  (interactive)
+  (custom-apply-all)
+  (gnus-score-remove-from-cache gnus-score-custom-file)
+  (let ((file gnus-score-custom-file)
+	(score gnus-score-alist)
+	emacs-lisp-mode-hook)
+    (save-excursion
+      (set-buffer (get-buffer-create "*Score*"))
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (pp score (current-buffer))
+      (gnus-make-directory (file-name-directory file))
+      (write-region (point-min) (point-max) file nil 'silent)
+      (kill-buffer (current-buffer))))
+  (message "Saved"))
+
+(provide 'gnus-edit)
+
+;;; gnus-edit.el end here