You should be automatically redirected . If not, visit
http://newlisper.wordpress.com
and update your bookmarks.

27/06/2008

Plug addict

I'm trying out a very simple plug-in system for the newLISP script (as yet unnamed) that produces this blog. The idea is that you can more easily add or remove features by adding or removing plug-ins, rather than by modifying the main script each time. So far the results are promising.

Each plug-in is just a newLISP context stored in a file, so the first relevant piece of code in the main script is this:

(set 'plug-ins '())
(dolist (i (sort (directory plug-ins-dir "\\.lsp")))
   (load i))

which loads every newLISP file in the plug-ins directory. The code for a plug-in file looks like this:

(context 'Whatever)
; ... function definitions
; and finally:
(push (context) plug-ins -1)
; eof

In the last line the plug-in registers its presence and willingness to participate by appending its name to the master list. The advantage of doing it this way is that you can modify the order in which plug-ins load, and execute, by adjusting the file names, rather than by changing any code. For example, given these plug-ins:

01debug.lsp
10html.lsp
20atom.lsp
30cache.lsp
default.lsp

you can play with the numbers to change the order of execution.

The following macro function in the main script runs through the plug-ins (ie the contexts) in order, executing the same function in each.

(define-macro (call-plug-in-function flag func)
  (catch 
    (dolist (ctxt plug-ins)
      (if (context ctxt func)
          (set 'result (apply (sym func ctxt) (args)))
          (set 'result nil))
      (if (and flag result) 
          (throw result))))
   result)

This tries to call function func in every available context, in the order the files were loaded. There's a flag argument to the macro which is used to specify whether only one plug-in can handle the task or whether all plug-ins can have a shot at the task. The result returned by each function also determines whether subsequent plug-ins can handle the task if for any reason the current plug-in can't cope.

All that's now required is for the tasks to be organized into functions, and for the functions to be passed to call-plug-in-function for evaluation in all contexts.

Here's an example of how it works, for the Atom news-feed plug-in, given the example set of plug-ins shown above. The main script decides that it's time to choose a template:

 (call-plug-in-function true "choose-template")

The call-plug-in-function macro looks for a choose-template function in every context. Only one template set can be chosen, so the true value tells all the plug-ins that the first one to handle it successfully will be the only one to handle it. There's no definition for choose-template in the first plug-in (ie Debug:choose-template doesn't exist), but in the next plug-in there is. However, HTML:choose-template decides that it doesn't want to handle anything to do with Atom, so returns nil. Next, it's the turn of Atom:choose-template, which should be able to complete its tasks successfully and return true:

; in context Atom:
(define (choose-template)
  (cond 
     ((= required-format "atom")
         ;
         ; choose suitable template set
         ;
         true)
    (true
         nil)))

No other plug-ins get the chance of choosing templates now.

Changes to the way Atom-formatted news is generated can be made to the Atom context. The Atom context can be removed altogether without affecting the behaviour of other parts of the application. I could write a different Atom context and try it out by changing the numbers in the filenames.

Notice that I've assumed that the main script provides enough information about the environment and the task in hand for the plug-ins to make intelligent decisions about what to do. I'm also assuming - obviously - that there's no malicious intent or desire to disrupt the whole set-up. It's not a good idea to load any plug-ins from an untrusted source...

It's possible to override built-in behaviour because even the basic tasks are handled by a plug-in file as well. This is loaded last, so it can provide default behaviour to be carried out when none of the other plug-ins have volunteered for tasks.

Some tasks can be handled by a number of different plug-ins one after the other. For example, a 'tidying-up' function is called from the main script with:

(call-plug-in-function nil "tidy-up")

and each plug-in can do some tidying up if necessary.

It's early days yet, but I'm interested to see how many different tasks I can get the plug-ins to handle until I have to make changes to the main script!

0 Comments:

Post a Comment

Links to this post:

Create a Link

<< Home