WikklyText A wikitext server and rendering library

files/feed-icon-14x14.png Recently Edited Valid XHTML 1.0 Strict
WritingPlugins edit
frank, 17 August 2009 (created 10 August 2008)
Module wikklytext.plugins

WikklyText Plugins

The Plugin API allows you to extend the functionality of WikklyText in several ways:
  • You can add new macros than can be called from wikitexts as <<mymacro ..>> (link)
  • You can add functions that appear in the namespace of <?py code embedded in wikitexts. (link)
  • You can attach extra styling information (CSS) to the rendered document (link)

Click a link for more, or read on for full details.

Creating new macros

Macros are ordinary Python functions with the following calling sequence:
def mymacro(context, ...):

Where context is a WikContext (ref: wikklytext.base) and the remainder of the args are passed from the macro call. For example:
<<mymacro 123 456 "abc">>

Would call:
def mymacro(context, a, b, c):
    # a.text = "123"
    # b.text = "456"
    # c.text = "abc"

Note that each passed argument is an Element so you use .text to get the text portion. (More general macros might want to deal with arbitrary ElementTrees as arguments, but text args are the simplest and most common.)

Macros can return any of the following:
  • An Element
  • A unicode string
  • A byte-string
  • A list/tuple of any of the above types.

Here is what a typical plugin looks like. Let's assume:
  • My plugin is named myplugin.
  • I'm defining two macros that are safe for anyone to call named happy and nice.
  • I'm defining two macros that only trusted users are allowed to call named evil and bad.

My __init__.py would look like this:
__safe__ = ['happy', 'nice']
__unsafe = ['evil', 'bad']

def happy(context, ...):
def nice(context, ...):

.. etc. ..

(In a real plugin, you'd probably split your code up into multiple files. The above example shows all code being placed in __init__.py for simplicity.)

Creating embedded functions

Embedded functions are ordinary Python functions that are available by default in the global namespace of embedded Python code (<?py). They do not get a context arg, and are called like any other function (and may return any value).

To add functions to the embedded namespace, add their names to the __embed__ list at the top of your module (similar to __safe__ and __unsafe___ above).

Adding styling (CSS) information

You might find yourself defining new CSS classes as part of your macros. While it is certainly possible to tell your macro users to add your new styles to their wiki StyleSheet, it is much more convenient to provide the classes automatically.

To do this, simply define a module level variable __css__ that is a string with the styling information to be added to the document <HEAD>. For example:
__css__ = '''
    div.myclass1 {
        color: red;

The string will be added to a <style> tag when creating the HTML document.

A simple example

Here is a simple complete example:

  1. First, create a folder hello under plugins/ in your wiki folder.
  2. Create a file plugins/hello/__init__.py
    This is just like any other Python package where you have __init__.py
    defining the interface.

    __init__ may define any/all/none of: __embed__, __safe__ and __unsafe__.
    __embed__ = ['hello_embedded']
    __safe__ = ['hello_safe', 'hello_box']
    __unsafe__ = ['hello_unsafe']
    __css__ = '''
        div.hellobox {
            background: yellow;
            color: black;
            border: 3px solid blue;
            padding-left: 3em;
    For real code you'd probably split these functions into their
    own files and just import them here.
    def hello_safe(context, msg):
        return "Hello safe, message=%s" % msg.text

    def hello_unsafe(context, msg):
        return "Hello unsafe, message=%s" % msg.text

    def hello_box(context, msg):
        "A slightly more complex example ..."
        from wikklytext.plugapi import DIV, Text, eval_wiki_text

        d = DIV('hellobox')
        d.append(eval_wiki_text(context, msg.text))
        return d
    def hello_embedded(msg):
        return "Hello Embedded, message=%s" % msg.text
  3. Finally, create a new wiki item with this text:
    def hello(context, msg):
       return hello_embedded(msg) # auto-added to embedded namespace

    /% Minimal example, showing how args are passed. %/
    !!!From ''<$py''
    <<hello "Embedded message!">>

    !!!From ''<nowiki><<hello_safe>></nowiki>''
    <<hello_safe "Safe message!">>

    !!!From ''<nowiki><<hello_unsafe>></nowiki>''
    <<hello_unsafe "Unsafe message!">>

    !!!From ''<nowiki><<hello_box>></nowiki>''

    <<hello_box '''This text should be inside a yellow box. 
    This macro uses a custom CSS class. All
    __wikitext__ //styling// @@markup@@ works
    here as well.'''>>

Save the item and you should see that it works.
Returned from load_plugins(). All currently loaded plugins.

Dicts of {name: func} where name can be used as <<name ...>>. Mapped from __safe__ and __unsafe__ attrs defined in modules.
Dict of {name: func} where name will be inserted into the embedded namespace for <?py code in wikitexts. Loaded from __embed__ attr defined in module.
CSS styles to be added to <HEAD> (text).
Dict of WSGI applications defined by plugins, as: URL: handler. Each URL is of form /api/plugins/NAME/SUBURL where NAME is the name of the plugin and SUBURL is the URL path the plugin defined.
Like safe and unsafe, a map of {name: func}. wsgi_macros are just like safe & unsafe macros, except that they receive a WSGI-like environment instead of a WikContext as their first argument.
List of (name, module) for all loaded plugins. (This is for information purposes; all functional data has been pulled out and organized in the other attributes here.)
load_plugins(plugdirs=None, namespace='plugins')
A little wrapper around real_load_plugins() that catches import errors — namely errors due to being unable to import 'imp'. Was initially created to workaround the lack of the imp module under GoogleAppEngine.

XXX TODO - make loading of plugins work under GAE, etc., instead of just returning an empty set of plugins.

blog comments powered by Disqus