The Newsbruiser Plugin System
This document describes the NewsBruiser plugin system, which makes
it easy to add features to NewsBruiser.
Plugin Overview
When NewsBruiser starts up, it looks at all the directories under
nb/plugins/. If a directory has an
__init.py__ file, NewsBruiser runs the code in that
file. So the first step in creating a plugin is making a subdirectory
of nb/plugins/.
The plugins that come with NewsBruiser are sorted by what they
do. For instance, the plugins underneath entry/export
deal with notifying things external to NewsBruiser (such as files on
the filesystem, or other web sites) about changes to NewsBruiser
entries. Each plugin subdirectory has a description of the plugins
inside its README file; you can use the existing subdirectories to
guide your path, or make your own subdirectories.
What your plugin can do
Create new screens
You can define a new screen for your plugin by simply declaring a
subclass of NBCGI and registering it with the NewsBruiser dispatch
CGI.
To register your screen, put code like the following in your
plugin's __init__.py file (assuming your screens are all
kept in MyScreens.py):
from nb.NewsBruiserCGIs import DispatchCGI
import MyScreens
DispatchCGI.cgiModules.append(MyScreens.__name__)
You can put all your NBCGI subclasses in one module
(eg. MyScreens.py); DispatchCGI will scan that module for
NBCGI subclasses and register them all.
Make sure that your NBCGI subclass defines the NAME field, so that
the dispatcher will know when a user is trying to access your
screen. Every screen must have one and only one NAME, and no two
screens can share a NAME.
Example: the api/XML-RPC plugin defines a new screen
that provides a CGI interface to XML-RPC.
Respond to NewsBruiser events
Your plugin can be notified when certain important events happen in
NewsBruiser, like an entry being published.
To have a method called when an event of type "Foo" happens, you
just define a function in __init__.py called eventFoo.
Right now there are four NewsBruiser events you can listen for:
- eventEntryPublished(entry): A user has published a notebook
entry. It might be a new entry, or it might have previously been a
draft.
- eventEntrySaved(entry): A user has modified an entry or a
draft. This will be called instead of eventEntryPublished() the first
time a user submits a draft.
- eventEntryDeleted(entry): A user has deleted an entry.
- eventFullStaticRewrite(notebook): A user has signalled that all
external representations of NewsBruiser artifacts should be
rewritten. If you keep representations of NewsBruiser artifacts and
listening for Entry{Published,Deleted,Saved} isn't sufficient to keep
things in sync, you should respond to this event by recreating
everything from scratch.
Examples: The entry/export/ plugin listens for all
four events and propagates them to any registered EntryExporter
objects.
Define entry filters
Entry filters can be used to automatically change the text of an
entry. This is generally used to save some work on the part of the
person who's typing in notebook entries by, eg. automatically linking
URLs they type.
If you want to define an entry text filter, first define the
transformation function. This function takes a NBCGI instance and a
text string. It performs some transformation on the text and returns
the transformed version.
Now you must register your filter. import the Entry
class and call Entry.registerFilter, passing in the type
of filter (currently only 'text' is supported) and a reference to the
transformation function.
If you define an entry filter you will also want to define a
configuration option to turn the filter on and off (see below). Your
function should check the value of the option for the given notebook
and, if it is not set, return an unaltered version of the text.
Keep in mind that entry filters are cumulative; a user may enable
more than one, and the results of one will be fed into the next. There
is no guarantee of the order in which the filters will be run.
Examples: This is basically all the entry/filter
plugins do.
Add configuration options
You can allow the user to make changes to the way your plugin
operates through NewsBruiser's integrated configuration interface. You
do this by creating an options.conf file in the plugin
directory. You can access the value of an option for a particular
notebook by calling getOptionValue on the notebook,
and passing in the name of the option.
NewsBruiser's configuration interface is provided by a library
called "I Want Options", in nb/lib/IWantOptions.py. It
has way too many features to mention here, but it NewsBruiser's main
options.conf file exercises almost all of them, the IWO
library contains internal documentation, and you can get an overview
of IWO from a paper I wrote on configuration frameworks, called Beyond
The Config File.
Examples: Each entry/filter plugin has a configuration
option to turn it on and off.
Define templates and template directives
See the entry/annotate/Comments plugin for an example.
Define syndication hooks
RSS 1.0 and 2.0 will call any registered hook methods when printing
out their feed head (1.0) and their channel descriptor (1.0 and
2.0). See the notebook/annotate/License plugin for an example.
Probably other stuff
There is probably other stuff that I didn't add here when I added
it to NewsBruiser. Please see the existing plugins for lots of ideas.
What your plugin can't do (yet)
Right now there is no good way to link to plugin CGIs from
NewsBruiser CGIs. The basic NewsBruiser CGIs are full of links to the
CGIs provided by the included plugins. I can and plan to add hooks in
specific places for plugins to provide HTML snippets, which should
work for most purposes.
There are a few places in which a plugin can add form fields and
form processing logic to the basic CGIs. Only those neccessary to
support the outgoing trackback plugin have been implemented.
Templates defined in the core (like the entry template) sometimes
contain template directives defined by plugins (like the comment
plugin). Ideally the core template would call out to certain
templating system hooks which would have been registered by the
components. I know this problem is solvable, but have not yet come up
with a satisfactory solution.
Back to the main documentation