bengillies.net

a blog by Ben Gillies

POSTing to TiddlyWeb

Following on with my current theme of making TiddlyWeb easier to use, especially without Javascript, I thought I'd introduce another little plugin that I've written. To sum it up in a sentence, it adds POST support to TiddlyWeb.

What this means in practise, is that you can put an HTML form in your page (TiddlyWebPages is a good place to put it), and when somebody clicks submit, the data they entered is POSTed to TiddlyWeb, which then stores it, all with no JavaScript whatsoever. Sounds simple right? Well yes, that's kind of the point.

To use it properly, you can POST to the following urls:

  • /bags/<bag_name>/tiddlers/<tiddler_name>
  • /recipes/<recipe_name>/tiddlers/<tiddler_name>

in the same way that you can PUT to them in normal TiddlyWeb. However, this also lets you POST direct to the bag (or recipe), allowing users to specify their own title right there in the input box. To do this, you can post to:

  • /bags/<bag_name>/tiddlers
  • /recipes/<recipe_name>/tiddlers

and just include a "title" field in your HTML form. Sometimes though, the title doesn't matter (my comments section is a good example of this), so if you don't include one, TiddlyWeb will now generate a random title for you, saving you the bother.

Fields and Values


Essentially, you could POST any set of values you like to the above URLs, and they'd get put in the right bag as tiddlers, but some names are reserved for specific tiddler attributes. They are as follows:

  • Title - this specifies the tiddler title
  • Text - this specifies the text (or content) of the tiddler
  • tags - this specifies the tags that you want the tiddler to have (specify these as a TiddlyWiki style space/double square bracket delimited list)

Other values will simply be set as tiddler fields.

Binary Files



There is one exception to this though - binary files. If you include a file input box and give it a name of "file", then TiddlyWeb will use that instead, and put that file into the correct bag (or recipe), as a tiddler. You can then both navigate to and view it in the usual way.

Example



The following would be a simple example of how to use this plugin. It assumes that the plugin has been installed already (ie - added to tiddlywebconfig.py)

<form method="POST" action="/bags/foo/tiddlers">
    <input type="text" name="title" value="Insert title here" /><br>
    <textarea rows="5" cols="40" name="text">Insert text here</textarea><br>
    <input type="tags" name="tags" value="tags" /><br>
    <input type="submit" value="Send to TiddlyWeb" />
</form>

--- or for binary files (like images) ---

<form method="POST" action="/bags/foo/tiddlers" enctype="multipart/form-data">
    <input type="file" name="file" /><br>
    <input type="submit" value="Send to TiddlyWeb" />
</form>


Its that simple. If you want this plugin, its on GitHub at http://github.com/bengillies/TiddlyWeb-Plugins/tree/master/form/

You can test it out by leaving me a comment. :)

Introducing TiddlyWebPages

Since my last post, I've been hard at work on a new plugin, much larger in scope than my previous TiddlyWeb plugins. Before I start, you'll notice that my website has changed in look and feel quite considerably. Don't worry, the old site still exists at http://bengillies.net/.a/recipes/oldsite/tiddlers should you wish to see it.

So, onto the new plugin

What new plugin?


It's called TiddlyWebPages (or tiddly-webpages).

What's with the new site layout?


Well, the new layout is built on new plugin. Makes sense right?

What's it all about?


TiddlyWeb is a great server side, and with TiddlyWebWiki installed, it allows you to create some great websites pretty quickly and easily. In fact, once everything's set up, you don't even need to log in to your server. You can create the site right there from the comfort of your browser. The problem comes when you don't want to use TiddlyWebWiki for your entire site. Maybe you don't want to use it at all, or maybe you just want a site that doesn't use Javascript, or a site that functions more like a normal one. Well, that's where TiddlyWebPages comes in. It allows you to rapidly create new HTML templates, and store them in tiddlers. These templates then fit together however you like, reusing different templates wherever and whenever you like, to create your site. You can then define custom URLs (again within Tiddlers) and tell TiddlyWebPages which template and which recipe you want to assign to which URL. You can even tell it to load different templates depending on which bag you're viewing.

That sounds pretty interesting


Thanks

But what if I want to include templates within other templates?


You can do that too. You just tell it which recipe you want that template to use, and include it in your other template wherever you want. Doesn't need a recipe? Uses the same recipe as the other template? That's not a problem either.

Ok, so how do these sub templates differ to normal templates?


They don't. All templates are the same, they all use the same syntax, and you can view all of them on their own should you wish to. Likewise, you can include any template inside any other template too. In fact, this website makes heavy use of that very aspect. You can even reuse other people's templates as they're all automatically available on the web. Just copy them over into your TiddlyWeb instance.

Sounds pretty cool. You mentioned something about URLs?


I was just getting to that bit. You can create your own URLs much like you can create your own templates. URLs can be entered in a pattern format, allowing you to match with any bag or tiddler, and associate that with a recipe (yes, that means you can carry over said bag/tiddler into the recipe to create a dynamic recipe) and load all of it up in a template of your choosing. It even works with standard TiddlyWeb filters.

Excellent, so I could have /stuff/bag_name/tiddler_name?


Yes.

You also mentioned something about default templates for bags and recipes?


I did, quite right. You can assign a custom template to load up for each bag or recipe. So if one recipe is your blog and one showcases your project work, you can use a different template as the default for each. Don't worry though, each template has an extension associated with it. You can still view any tiddler/bag/recipe using any template you like, just put the correct extension on the end.

So what about extensions provided by other plugins?


Yes they work too. Remember the link to my old site (http://bengillies.net/.a/recipes/oldsite/tiddlers)? Notice the lack of extension on the end? Yes, that's because I told TiddlyWebPages to load up that recipe using TiddlyWebWiki. Likewise, you could set up a custom URL to use a different plugin, or even add a different plugin inside one of your templates should you so choose.

Sounds great. Where can I get it?


It's on GitHub. You can find it here together with a readme detailing how to use it properly.


Finally, if you have any problems, please let me know. I'm on Twitter @bengillies. I also read the TiddlyWeb Google group so if you post something there, I'll happily reply to it.

Like.py

This is a Python plugin for TiddlyWeb that filters tiddlers, returning all tiddlers that have the specified string somewhere in the specified field (aka - partial matching on fields). It is available on GitHub at like.py.

The code is republished below for reference purposes (though any future updates will only appear on GitHub):

"""
Compare the given string to the supplied field and return everything that partially matches:
 
eg:
 
/bags/foo/tiddlers?like=title:bar
 
will return all tiddlers where bar is contained somewhere within the title
 
"""
 
from tiddlyweb.filters import FILTER_PARSERS
from tiddlyweb.filters.select import select_parse
 
 
 
 
 
def compare_text(source, test, negate=False):
    if source.lower() in test.lower():
        return True != negate
            
    return False != negate
 
def compare_tags(source, test, negate=False):
    count = 0
    for tag in test:
        if source.lower() in tag.lower():
            return True != negate
            
    return False != negate
 
def compare_fields(source, test, attribute, negate=False):
    try:
        if type(test[attribute]) == text:
            return compare_text(source[attribute], test[attribute])
    except KeyError:
        return False != negate
            
    return False != negate
                         
ATTRIBUTE_SELECTOR={
    'tags': compare_tags,
    }
 
 
def like(attribute, args, tiddlers, negate=False):
    for tiddler in tiddlers:
        try:
            test = getattr(tiddler, attribute)
            test_func = ATTRIBUTE_SELECTOR.get(attribute, compare_text)
            found = test_func(args, test, negate)
        except AttributeError:
            found = compare_fields(args, tiddler.fields, attribute, negate)
            
        if found:
            yield tiddler
    
    return
 
 
def like_parse(command):
    attribute, args = command.split(':', 1)
    
    if args.startswith('!'):
        args = args.replace('!', '', 1)
        def selector(tiddlers):
            return like(attribute, args, tiddlers, negate=True)
    else:
        def selector(tiddlers):
            return like(attribute, args, tiddlers)
            
    return selector
 
 
FILTER_PARSERS['like'] = like_parse
 
 
def init(config):
    pass
 

Filtering TiddlyWeb

Following on from my previous post about validators, I thought I'd talk about filters in TiddlyWeb and give a couple of examples about how they work.

Filters take a list of tiddlers, filter that list in some way, and return the filtered list. How it works behind the scenes is slightly more complex, but essentially the end result is that you can apply them to any list of tiddlers and you'll get a filtered list back in return, so whether that's a recipe, or a bag of tiddlers (or even a list of tiddlers in a custom python script) it doesn't matter: they can all be filtered.

Writing a custom filter, as you may expect with TiddlyWeb, is trivially easy: all it requires is a function that takes the filter itself, and returns another function that in turn, does the filtering on a list of tiddlers. If you're thinking about writing a filter yourself, this means that you'll need at least two functions, one to parse the filter into a usable format, and another to take that format, along with a list of tiddlers (technically in Python, this is a generator object) and return the filtered list.

So, onto my filters: One filter matches the tiddlers in a given list with a named tiddler, returning all those tiddlers that are related, ranked in order of most-relatedness; The other filter does partial matching on any field that you specify, returning all tiddlers that have a field that partially matches the given text.

You can use them as follows:

Related tiddlers:


/recipes/foo/tiddlers?related=bar:title,tags,bag

The above filter would take all tiddlers in the recipe "foo", and return all those related to the tiddler "bar" (where "bar" is in the same recipe). The related-ness in this example would be based on three fields: title; tags; and bag.

Like tiddlers:


/recipes/foo/tiddlers?like=tags:tiddly

This filter would take all tiddlers in the recipe "foo" and return all those where "tiddly" appears somewhere in one of the tags. In this particular example, this would be useful if you're looking for tiddlers about either TiddlyWiki, or TiddlyWeb.

Both of these are available on GitHub, where you'll find the most up to date version. I have also reproduced the code (as it is at the moment) on my blog. If you're interested, take a look:

Related.py - find all tiddlers related to the given tiddler
Like.py - compare the given string to the supplied field and return everything that partially matches

TiddlyWeb

TiddlyWeb is a server side version of Tiddlywiki.

More information can be found at the following sites:

http://tiddlywiki.org/wiki/TiddlyWeb
http://tiddlyweb.peermore.com/wiki/

URL Handling

In a previous post on TiddlyWebPages, I mentioned URL handling functionality, and promised a later post detailing how to use it. Well, since then, I've done a considerable amount of extra work on it, simplifying the way URL's are stored, and adding a couple of extra features, which I'll detail here.

First, and most importantly though, it's no longer part of TiddlyWebPages. Instead, it has been packaged up as tiddlywebplugins.urls and can be installed by doing

pip install -U tiddlywebplugins.urls


Then you'll need to put 'tiddlywebplugins.urls' into both the 'server_plugins' and 'twanager_plugins' sections of tiddlywebconfig.py.

Once you've done that, you'll be able to add URLs with twanager. For example, loading up the "default" recipe as a TiddlyWiki at /mywiki, you'd write:

twanager url /mywiki /recipes/default/tiddlers.wiki


Where /mywiki is the url path you want to use, and /recipes/default/tiddlers.wiki is what you want to appear there.

If you wanted to load up any recipe as a wiki, and serve them up at /wikis/<recipe_name> then you could write:

twanager url /wikis/{recipe:segment} "/recipes/{{ recipe }}/tiddlers.wiki"


In this example, {recipe:segment} specifies a variable within the URL called "recipe". The {{ recipe }} then get's replaced with whatever URL you eventually go to. You could then arrive at the /mywiki example above by pointing your browser at /wikis/default.

You can find further instructions about syntax for the URL path you are adding at http://lukearno.com/projects/selector/ though note that any variables are always embedded within the destination URL with double braces (eg - /recipes/{{ recipe }}/tiddlers.wiki).


Redirection



tiddlywebplugins.urls also supports URL redirection. This can be used either internally to your site, or with any external link instead.

To use internally, just add --redirect to the twanager command (eg - twanager url --redirect /s3Rw /bags/common/tiddlers/MyTiddler would redirect anyone going to /s3Rw to /bags/common/tiddlers/MyTiddler).

To use with external sites, you just need to use the full URL. For example, you could redirect to Google with:

twanager url /google http://www.google.com


URL storage and Modification



These URLs are all stored in the TiddlyWeb store with the Title being the URL you want to create, and the text being the URL you want to map/redirect to. The redirect option in the twanager command, relates directly to tagging the tiddler "redirect".

By default, this will be stored in a bag called urls, with a strict policy to stop other users modifying your URLs. This means that, with sufficient permissions, you could manage all your URLs from within TiddlyWebWiki itself.

Just one final note to finish off with, Currently, this only supports GET requests. This means that you cannot send PUT, POST or DELETE requests to these URLs. Saying that though, TiddlyWebWiki should still work regardless, just bear in mind that you should currently be PUTting to the standard set of URLs if you're writing any custom Javascript yourself.

Useful Links



Source code: http://github.com/bengillies/tiddlywebplugins.urls
Package: http://pypi.python.org/pypi/tiddlywebplugins.urls

Guide to Selector: http://lukearno.com/projects/selector/

Running on TiddlyWeb, Part 2

Ok, so we got TiddlyWeb up and running in Part 1. The view that you get from this is fairly plain though, and definitely doesn't look anything like this site. So the next couple of steps involve customising and finalising the install. Most of this is pretty much the same as for any other version of TiddlyWeb, so I'm only going to cover it briefly as information already exists elsewhere.

Step three: Customising



The first thing we'll probably want to do is make a new bag and a couple of recipes. You can do this using twanager and running the following commands from inside your instance directory:

twanager bag <bag_name>
ctrl-D

twanager recipe <recipe_name>
/bags/system/tiddlers
/bags/<bag_name>/tiddlers
ctrl-D


You'll probably want to stop guests from editing your site too. For this, you'll need to create a user account with twanager. You can then set access policies from within each bag to ensure that you're the only person that can write to them. You'll also want to ensure that your web server can write to the store. You can do this using the chmod function. For more details on how it works, type:

man chmod


into a command line.

Once you've done this, the final step before you get into TiddlyWiki itself involves the .htaccess file in the root of your web directory. Open this up and ensure that put and delete are both enabled. Then type in the following, or ensure that something similar exists:

RedirectMatch ^/$ http://<your_domain_name.com>/index.cgi/

SetEnv PATH_INFO /


The RedirectMatch command ensures that whenever somebody types in your URL and hits enter, they get redirected to your TiddlyWeb install. The SetEnv command simply places a forward-slash into the PATH_INFO variable and is necessary in case anybody misses out the slash at the end of index.cgi, which would otherwise result in a 404.

Finalising



Now that your setup is customised and you have some recipes, bags and your own user name, we'll add a few plugins to get TiddlyWeb to load a TiddlyWiki by default.

Plugins in TiddlyWeb can be in two forms - server-side, or client side. Server-side plugins are wirtten in Python, and included by modifying tiddlywebconfig.py. Client side plugins are written in javascript and take the form of standard TiddlyWiki plugins, of which there are loads.

So first things first, let's add a server-side plugin to display one of two recipes (in the form of a TiddlyWiki) depending on whether the site visitor is logged in as an admin, or not. This plugin is called cachinghoster and can be found here. Drop it in the same place as tiddlywebconfig.py and point the links inside the file to the appropriate recipes. Ideally, one of these should allow you to update your blog and one should not. Then open tiddlywebconfig.py and add the following command:

'system_plugins': ['cachinghoster'],


Then just run "twanager server" in order to install it, and manually exit again. Now try opening your site again. You should get a TiddlyWiki loading up by default. With any luck, this should change to a different TiddlyWiki depending upon whether you are logged in or not. This will be primarily to ensure that your site is read only when people visit.

Finally, install the BlogLayout plugin by simply copying the contents across into a new tiddler, and tagging with systemConfig. It should save automatically (you will need to be logged in though) and will present you with a default view of al tiddlers, tagged with "blog", listed in date order with the most recent entry first. By default, it will also summarise these and add a read more link to the bottom. This functionality can be altered, or added as links manually in other areas too (see the links at the top of this site for an example) and more details can be found out in the BlogLayout plugin itself.

Well, that's it. Other than that, you might like to install a theme, or create your own, but you should now have a fully functioning blog created in TiddlyWeb.

Happy Blogging....

Publishing Tiddlers

A while ago, I sat down with Michael Mahemoff and Jonathan Lister and between us we came up with a method for copying tiddlers in a TiddlyWiki/TiddlyWeb environment from one bag to another. The purpose for this was to allow a user to draft out an article (or a document of any sort really), and save it to allow work on it later, without worrying about somebody seeing their half finished work. As it turned out, this was relatively easy to accomplish within TiddlyWiki and involved modifying the server.workspace field of the tiddler. There was some cleanup to do afterwards of course, involving sorting out potential revision id clashes, and setting the server.workspace field back to the original bag, but the core of the process involved simply changing one field, and then saving the tiddler again.

Since then I have refined this process, fixing a couple of bugs and adding move functionality (copying the tiddler and then deleting the original) and now feel it is in a state ready to blog about.

Usage


To use this, you will need the PublishCommand tiddler, and the ZPublishCleanup tiddler. Add the Publish command button to a toolbar (I add mine in the "more" section of ViewToolbar and you should be good to go. All you'll need to do is set the custom fields in PublishCommand to the bag you want, and whether you want to move or copy:

fields['publishtobag'] = "blog" //set this to your desired bag
fields['publishlevel'] = "copy" //this value can be either "copy" or "move"


Then to use it, just click publish. on a tiddler that you want to perform the action on.

In practise, you may want a more dynamic version, allowing you to set the bag and publish level depending on what it is you're copying/moving. If you need to accomplish this, I would suggest using Jon Robson's AdvancedEditTemplate Plugin and setting dropdown inputs to allow users to set the bag and copy/move as required. To see this in action, save the index.html file locally and then load it up in your browser. Then click edit on the open tiddler to get a demonstration.

To integrate these two plugins successfully, you'll want one dropdown called "publishtobag" with a list of all bag names, and one called "publishlevel" with a choice of "copy" or "move".

I personally found it very useful to proof read this post before I made it live, but I'm sure there are many other uses that I have not yet considered, so feel free to modify it as much as you need for your purpose (I know I have already).

Running on TiddlyWeb, Part One

As I've just finished putting this site together, I thought I'd give you a brief overview of TiddlyWeb (the platform this site is built on) and how I went about implementing it.

First things first, there's quite a lot of documentation about TiddlyWeb already (see here) so I'm not going to explain the basics about it, or try to detail the underlying structure. All I'm going to do here is provide a set of instructions for installing and running TiddlyWeb on a setup similar to mine.

So what's my setup?

I'm running this site on Apache and Linux. I'm running Python through CGI, and I don't have root access to the server. If this sounds like you, then you've probably got one of the many hosting companies around providing you with your own web space on one of their servers. I would imagine this is a fairly common situation and unfortunately, requires jumping through a few extra hoops than the other install methods available, but here we go anyway.

Step One: Installing TiddlyWeb



The first hurdle to jump over is actually installing TiddlyWeb itself. Now, TiddlyWeb is usually installed with the Python tool "easy_install" but, if you don't have easy_install you're going to have to install it. The main problem with this however is the lack of root access. So to start, we're going to install Virtual Python. This is a tool that creates a virtual install of Python in your home directory and lets you install other libraries on top of that. So go here to install Virtual Python. Then go here and install easy_install from this script. From here, normal instructions can be followed for installation, so do:

easy_install -U tiddlyweb


Step Two: Setting up an Instance



Now that TiddlyWeb's installed, we need to create an instance in the directory you publish websties in (most likely public_html). So, go into this directory and run:

twanager instance <instance_name>


Where <instance_name> is the name you want to give your wiki. This will then set things up for a default install, creating the store (where your content is stored) and creating a default config file called "tiddlywebconfig.py". However, we don't want a default install, so go and grab the index.cgi file from the svn repository and drop it in the root of your web directory (e.g. public_html). Now open it up in a text editor (vi, nano, etc) and do the following:

Change the path at the top of the file to be your virtual python install path

then set the following:

file_store_location = <path_to_your_store> 
//most likely <instance_name>/store


Then check that the following lines are either commented out or do not exist (note that these changes may be implemented in a future version of TiddlyWeb, thus saving you the bother):

#sys.path.append('/home/cdent/src/TiddlyWeb')

#web_server_base = '/tiddlyweb/index.cgi'

#   hostname = os.environ['HTTP_HOST']
#   if ':' in hostname:
#       hostname, port = hostname.split(':')

#   config['server_prefix'] = web_server_base


and finally make sure the line
app = serve.load_app(hostname, port, config['urls_map'])

looks more like this:
app = serve.load_app()


That's it for now, so save and quit and load up tiddlywebconfig.py in a text editor. Add the following lines:

    'server_prefix': '<instance_name>/index.cgi',
    'server_host': {
    'scheme': 'http',
    'host': '<domain_name>',
    'port': '80',
    },


Save and quit. That should be it, TiddlyWeb should now work. Open up your browser and point it to http://your_domain_name/<instance_name/index.cgi/ to browse through bags, recipes, etc. Note that you will need the forward slash after the index.cgi, or you'll probably get an error.

Coming in Part 2 - Configuration and finalisation

Update


The index.cgi file has now changed. Please see CGI Install Update for more information.

Binary Tiddlers and You

I want to take this opportunity to talk about the current state of binary tiddlers within TiddlyWeb, how you can use them, and to introduce some new stuff that I've been working on.

There are currently a few ways of dealing with binary tiddlers in TiddlyWeb/TiddlyWiki. If we first take a look at TiddlyWiki, we can see that binary tiddlers can be loaded in the form of base64 encoded data: URIs (see TiddlyPictoWiki for a good example of this). Indeed, the upcoming TiddlyWiki5 fully supports all of this natively (among other things).

Of course TiddlyWeb also supports binary tiddlers (setting the tiddler.type attribute determines this), with the difficulty being how to get the data into the store in the first place. For this, natively, your only real option is is to PUT it using curl and the RESTful API (more on another native option below). This works for the most part, though I wouldn't call it particularly user friendly. There is also my form plugin (see POSTing to TiddlyWeb), which I'll go into greater detail about later on.

Finally, you can use twanager, and a small plugin for importing binary content that I wrote called tiddlywebplugins.bimport. I should note first that I wrote this a few months ago and some of the functionality has since been integrated with TiddlyWeb core (specifically, the twanager command twimport) so most of this also applies to that command. The main benefit of using bimport however, is that you can guarantee that the file you're importing will always be stored as binary (javascript files or TiddlyWiki files might be a good use case). So, to install:
sudo pip install -U tiddlywebplugins.bimport

and then add 'tiddlywebplugins.bimport' to you tiddlywebconfig.py file. You can then import binary files with:
twanager bimport <bag_name> <tiddler_name> <URI>

where the URI can be anything (eg - a file: URI). Alternatively, the twimport command can be run as:
twanager twimport <bag_name> <URI>

with anything not recognised as a valid type for TiddlyWeb to use being stored as binary content. What this means is that an image will import fine, but a Javascript file, will get tagged "systemConfig" and lose its mime type. If you don't want this to happen, you should use bimport. Otherwise, twimport is probably easier (as you don't have to install an extra plugin).

Uploading via the Web


A while ago I wrote a blog entry entitled POSTing to TiddlyWeb which mentioned that you could use it to upload files to a TiddlyWeb bag or recipe. I've recently updated this to support tagging binary tiddlers, and have released it to pypi. You can install it by running:
sudo pip install -U tiddlywebplugins.form

from a command line. If you were using the old version, you should probably update as the new version supports better handling of tiddler titles, in that you can now override the title of a binary file being uploaded, and the aforementioned tagging of binary content (so that you can tag for example, an image, or a pdf).

I've also created a TiddlyWiki plugin to use with it. You can find it in my SVN repository at http://svn.tiddlywiki.org/Trunk/contributors/BenGillies/TiddlyWeb/Plugins/Binary/tiddlers/BinaryUploadPlugin.js, and can import it into your TiddlyWeb store with:
twanager twimport <bag_name> http://svn.tiddlywiki.org/Trunk/contributors/BenGillies/TiddlyWeb/Plugins/Binary/tiddlers/split.recipe

To use it, simply add:
<<binaryUpload bag:bag_name edit:tags edit:title tags:default_tags>>

where bag:bag_name is optional and allows you to specify a different bag to the one you're currently in, edit:tags and edit:title are also optional, and allow you to add a title or tags, and tags:default_tags allows you to set some default tags if you have the edit:tags option on.

I believe that binary tiddlers now have a fairly good story in TiddlyWeb, and am quite looking forward to seeing potential applications that surface.

Validating Comments with reCAPTCHA

Since I enabled comments on my site, I've been getting quite a lot of spam. Now, this is a problem that's existed all over the web now for quite a long time. What's more, it's been solved quite often before, usually requiring log in, or some sort of CAPTCHA validation to ensure that whoever is posting a comment is actually a human being.

While TiddlyWeb supports log in off the bat, I'm not a huge fan of requiring it just to post a comment, as logging in seems, to me at least, a bit unnecessary when all you want to do is leave one comment on a site.

With that in mind, I've gone down the CAPTCHA route, and implemented a validator that supports the popular reCAPTCHA validator that you've probably seen all over the rest of the web.

To use it, you'll need to grab it from my GitHub repository here. Then you can add it to system_plugins in your tiddlywebconfig.py file. As its an external service, you'll then need to sign up to it at http://recaptcha.net/api/getkey in order to get the requisite private and public keys.

Once you've signed up, add your private key to the tiddlywebconfig.py file like so:
    'recaptcha_private_key': '<private_key>'

Then, wherever you need to use it, add the following HTML:
<script type="text/javascript"
src="http://api.recaptcha.net/challenge?k=<your_public_key>">
</script>
 
<noscript>
<iframe src="http://api.recaptcha.net/noscript?k=<your_public_key>"
height="300" width="500" frameborder="0"></iframe><br>
<textarea name="recaptcha_challenge_field" rows="3" cols="40">
</textarea>
<input type="hidden" name="recaptcha_response_field"
value="manual_challenge">
</noscript>

Making sure to substitute <your_public_key> for the public key you got when you signed up (its in there twice).

Finally, it's a validator, so don't forget to set your accept policy.

Related.py

This is a Python plugin for TiddlyWeb that filters tiddlers, returning all related tiddlers, ranked in order of related-ness. It is available on GitHub at related.py.

The code is republished below for reference purposes (though any future updates will only appear on GitHub):

"""
Compare the given tiddler with other tiddlers in the bag and return
anything that is related byt the supplied fields, sorted in order with most related first
 
eg:
 
/bags/foo/tiddlers?related=bar:title,tags
 
will return all tiddlers related (by title and tags) to the tiddler "bar", ranked in most related first order
 
"""
 
from tiddlyweb.filters import FILTER_PARSERS, parse_for_filters, recursive_filter
 
import logging
 
import re
 
 
def compare_text(source, test):
    source_words = re.split('\W',source)
    count = 0
    for word in source_words:
        if word.lower() in test.lower():
            count += 1
            
    return count
 
def compare_tags(source, test):
    count = 0
    for tag in source:
        if tag in test:
            count += 1
            
    return count
 
def compare_fields(source, test, match):
    count = 0
    try:
        if type(source[match]) == text:
            count = compare_text(source[match], test[match])
    except KeyError:
        pass
            
    return count
                         
ATTRIBUTE_SELECTOR={
    'tags': compare_tags,
    }
 
def match_related_articles(title, matches, tiddlers):
    def empty_generator(): return ;yield 'never'
    tiddlers = [tiddler for tiddler in tiddlers]
    try:
        source_tiddler = recursive_filter(parse_for_filters('select=title:%s' % title)[0], tiddlers).next()
    except StopIteration:
        #nothing to match on, so return an empty generator
        return empty_generator()
                         
    sort_set = []
    for tiddler in tiddlers:
        count = 0
        for match in matches:
            try:
                source = getattr(source_tiddler, match)
                test = getattr(tiddler, match)
                test_func = ATTRIBUTE_SELECTOR.get(match, compare_text)
                count += test_func(source, test)
            except AttributeError:
                count += compare_fields(source_tiddler.fields, tiddler.fields, match)
                            
        if count > 0 and source_tiddler.title != tiddler.title:
            sort_set.append([tiddler,count])
    
    def sort_function(a,b): return cmp(b[1],a[1])
    sort_set.sort(sort_function)
    
    result = (tiddler_set[0] for tiddler_set in sort_set)
    
    return result
 
 
 
def related_parse(command):
    
    attribute, args = command.split(':', 1)
    args = args.split(',')
    
    def relator(tiddlers):
        return match_related_articles(attribute, args, tiddlers)
    
    return relator
 
 
FILTER_PARSERS['related'] = related_parse
        
def init(config):
    pass

CGI Install Update

Update - These instructions have been superseded. Please see CGI and TiddlyWeb 1.0 for up to date instructions

Following on from my original post Running on TiddlyWeb, Part One, there has recently been an update to the index.cgi file to make installing clearer and easier to understand. The new file can be found on github at http://github.com/tiddlyweb/tiddlyweb/blob/master/index.cgi. This simplifies the first stage of the install procedure to the following:

  • Run this script from python.
  • Run this script from the new instance of python created in your home directory.
  • Type easy_install -U tiddlyweb into a command line and hit enter.
  • TiddlyWeb should now be installed. Type twanager instance <instance_name> somewhere in your home directory.
  • Copy index.cgi to somewhere in your public_html folder.
  • Move the tiddlywebconfig.py file from your instance to a different folder.

That first part should be fairly similar to the old method of installing. The main difference comes in how you edit the index.cgi and tiddlywebconfig.py files. So from here, go into index.cgi and change tiddlywebconfig_dir = <path to your tiddlywebconfig.py directory>. Then change os.environ['PYTHON_EGG_CACHE'] = <path to your ".python-eggs" folder>. With virtual python, this is "~/.python-eggs". Also, make sure that the path to python at the top is the path to your virtual python install.

Save that and go into tiddlywebconfig.py. enter the following information:
 'server_store': ['text', {'store_root': '<path to your instance>/store'}],
'server_prefix': '<path to your index.cgi file>',
'server_host': {
'scheme': 'http',
'host': '<your domain name here>',
'port': '80'},


Hopefully, this should make things simpler to understand, and easier to use. So from this point, continue the install as before to get tiddlyweb up and running.

Validating TiddlyWeb

Recently, I've been looking at TiddlyWeb with a view to securing content and protecting from cross site scripting attacks. Luckily, this is all pretty simple in TiddlyWeb, which has the concept of Validators built right in and ready to extend at will.

Validators are essentially run on any tiddler that has been PUT to a bag by a user without "accept" permission. By "accept" permission, I mean that the accept field in the policy file for that bag has been set, but does not contain their username or role. You can have any number of validators running, and load in more by using the standard TiddlyWeb plugin model.

So, onto my Validators. I have written 2 validators: One for generic HTML, and one specifically for TiddlyWebWiki.

The HTML Validator is based on the Beautiful Soup package for Python, and works by providing a whitelist of allowed HTML tags and attributes, and simply removing anything that doesn't match it. The code is rather simple (thanks to Beautiful Soup), and has been taken and modified slightly from here. To use it, add or remove any tags/attributes as required from the two lists in the plugin, and add to system_plugins in tiddlywebconfig.py as usual.

The TiddlyWebWiki validator, as you might expect, is even simpler. All it does is remove the systemConfig tag (if present) and reject any tiddlers whose name is in a blacklist (MarkupPreHead for example). Again, you can add to this list as necessary.

That's it, hopefully you'll find one or the other useful.

Both plugins are available on my GitHub account.

html_validator.py - validate incoming tiddlers against a whitelist of allowed tags/attributes
tiddlywiki_validator.py - validate incoming tiddlers against a blacklist of tiddler titles and remove systemConfig tags

CGI and TiddlyWeb 1.0

Now that TiddlyWeb has hit the 1.0 milestone, I figured it was about time to blog the latest and greatest way to get it running using cgi. I've simplified the instructions somewhat so that, apart from editing a few config files, and changing the paths to your specific paths, you should be able to copy/paste most of it.

Just one final note before we get going, these instructions are for Linux/*nix systems only, so they should work on most versions of Linux, Mac OSX, BSD, etc. If you are using a Windows system, you probably want the Installing TiddlyWeb on Windows instructions.

Installation


Download virtualenv-x.x.x.tar.gz (replace x.x.x with the latest version number) from http://pypi.python.org/pypi/virtualenv and put it into your home directory. Then, from you home directory, do the following:
tar -xvzf virtualenv-x.x.x.tar.gz
cp virtualenv-x.x.x/virtualenv.py ~
python virtualenv.py ~
source bin/activate
easy_install -U pip
pip install -U virtualenv
pip install -U tiddlywebwiki
twinstance <instance_name>
mkdir .python-eggs
mkdir twconfig
mv <instance_name>/tiddlywebconfig.py twconfig/tiddlywebconfig.py
touch twconfig/tiddlyweb.log
wget http://github.com/tiddlyweb/tiddlyweb/raw/master/index.cgi
chmod +x index.cgi
mv index.cgi public_html/cgi-bin/


Configuration


Nano is a simple text editor that you can use to make some changes to index.cgi. Feel free to use whatever you like, though bear in mind that nano is probably the easiest to start off with.
cd public_html/cgi-bin
nano index.cgi

So, change the line at the top to point to your local version of python (type in "which python" to find this out). It should end up similar to the following:
#!/home/bengillies/bin/python

Also, change the lines that look like:
tiddlywebconfig_dir = '/tmp'

os.environ['PYTHON_EGG_CACHE'] = '/tmp'

To something like (replacing the start with your home directory:
tiddlywebconfig_dir = '/home/bengillies/twconfig/'

os.environ['PYTHON_EGG_CACHE'] = '/home/bengillies/.python-eggs'

You can save with ctrl-o, and then exit with ctrl-x (assuming you're using nano). Now, we need to alter the tiddlywebconfig.py file to finish things off:
cd ~/twconfig
nano tiddlywebconfig.py

Go down to the last but one line (the one before the closing }), and start a new line after it. Enter information so the the file looks like this:
# A basic configuration.
# Run "pydoc tiddlyweb.config" for details on configuration items.

config = {
    'system_plugins': ['tiddlywebwiki'],
    'secret': 'f433fbfcf1871ea99ddb75e97ff5512f5513414d', #this string will be different in your file. Just leave it as it is.
    'twanager_plugins': ['tiddlywebwiki'],
    'server_store': ['text', {'store_root': '/home/bengillies/<instance_name>/store'}],
    'server_prefix': '/cgi-bin/index.cgi',
    'server_host': {
        'scheme': 'http',
        'host': '<your domain name here>',
        'port': '80'
    },
}


That's it. You should now be able to point your web browser to http://your_domain.com/cgi-bin/index.cgi/recipes/default/tiddlers.wiki and start editing.

For more instructions on how to customise your tiddlyweb installation, it's probably best to look directly on the TiddlyWeb site at http://tiddlyweb.com