Guile and Leafnode


Here's how you can use Guile filter your news feed, using the Leafnode news server.

Rename the main function in fetchnews.c to inner_main and add the following suffix to the file.

#include <libguile.h>
 
int main(int argc,char *argv[])
{
     scm_boot_guile(argc, argv, inner_main, NULL);
     return 0;
}
Add/amend the following options to Makefile.
CPPFLAGS =  -Ipcre `guile-config compile`
LDFLAGS = `guile-config link`
Replace filterutil.c with the following code.
#include "leafnode.h"
#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

#include <libguile.h>


typedef struct {
  SCM proc;
  SCM args;
} DATA, *PDATA;


static SCM loadfilters(void *filename)
{
     return scm_c_primitive_load((char *) filename);
}

static SCM loadfilters_with_catch(char *filename)
{
     scm_internal_catch(SCM_BOOL_T,
			loadfilters,
			(void *) filename,
			scm_handle_by_message_noexit,
			NULL);
     return SCM_BOOL_T;
}

void readfilter(char *fifi)
{
     SCM b = loadfilters_with_catch(fifi);
     
     if (SCM_FALSEP(b)) {
	  syslog(LOG_ERR, "Unable to load filterfile %s: %m", fifi);
	  printf("Unable to load filterfile %s\n", fifi);
	  return;
     }
}

SCM scm_dofilter(void *h)
{
     SCM headers = scm_makfrom0str((char *) h);
     SCM var = scm_c_lookup("dofilter");

     if (SCM_NFALSEP(scm_variable_p(var))) {
	  SCM proc = SCM_VARIABLE_REF(var);
	  if (SCM_NFALSEP(scm_procedure_p(proc)))
	       return scm_apply_1(proc, headers, SCM_EOL);
     }

     return SCM_BOOL_F;
}

int dofilter(char *headers)
{
     SCM r = scm_internal_catch(SCM_BOOL_T,
				scm_dofilter,
				headers,
				scm_handle_by_message_noexit,
				NULL);

     return SCM_NFALSEP(r);
}
Here's an example filter file.
;;; -*- scheme -*-

(use-modules
    (srfi srfi-1)
    (ice-9 format)
    (ice-9 regex))

(define *filters* '())

(define (define-filter regex thunk)
    (set! *filters*
	(cons
	    (cons (make-regexp regex) thunk)
	    *filters*)))

(define (unzip-pair p)
    (lambda (x)
	(if (pair? x)
	    (p (car x) (cdr x)))))

(define (dofilter h)
    (define (run-filter regex action)
	(let ((m (regexp-exec regex h)))
	    (if m
		(action m))))
    (catch 'trigger
	(lambda ()
	    (for-each (unzip-pair run-filter) *filters*)
	    #f)
	(lambda (key val)
	    val)))

(define *from* #f)

(define-filter
    "^From: (.*)"
    (lambda (m)
	(set! *from* (match:substring m 1))))

(define (make-result b)
    (lambda (syms)
	(format #t "filter: ~a ~a ~a~%" ; for debugging
	    (if b 'kill 'keep)
	    syms
	    *from*)
	(throw 'trigger b)))

(define kill (make-result #t))
(define keep (make-result #f))

(define *seen* '())

(define (reset-seen!)
    (set! *seen* '()))

(define (seen! sym)
    (set! *seen* (cons sym *seen*)))

(define (seen? sym)
    (memq sym *seen*))

(define-filter "^Path:.*"
    (lambda (m)
	(reset-seen!)
	#f))

(define *triggers* '())

(define (make-trigger syms action)
    (lambda ()
	(if (every seen? syms)
	    (action syms)
	    #f)))

(define (trigger action . syms)
    (set! *triggers*
	(cons
	    (make-trigger syms action)
	    *triggers*)))


(define-filter "^Xref:.*"
    (lambda (m)
	(for-each
	    (lambda (thunk) (thunk))
	    (reverse *triggers*))
	(reset-seen!)
	#f))

(define (see field)
    (lambda (name sym)
	(define-filter
	    (string-append "^" field ":.*" name)
	    (lambda (m)
		(seen! sym)))))

(define seen-from (see "From"))
(define seen-ref (see "References"))
(define seen-ng (see "Newsgroups"))

(define (seen-from-ref name sym)
    (seen-from name sym)
    (seen-ref name sym))

;;; The following is specific to the newsgroups that I read.
;;; In alt.slack, I'm only interested in posts by two people
;;; and their replies.

(seen-from-ref "modemac" 'ok)
(seen-from-ref "stang" 'ok)
(seen-from-ref "[Nn]erdware" 'ok)
(seen-ng "alt.slack" 'slack)
(trigger keep 'slack 'ok)
(trigger kill 'slack)

;;; In comp.lang.lisp, I'm even more selective.

(seen-from "cbbrowne" 'ok)
(seen-from "pitman" 'ok)
(seen-ng "comp.lang.lisp" 'lisp)
(trigger keep 'lisp 'ok)
(trigger kill 'lisp)


Martin Rodgers
Last modified: Fri Sep 27 15:07:26 2002