""" tw.cgi A Python-based backend for TiddlyWiki. """ __author__ = "Joseph Reisinger (joeraii@cs.utexas.edu)" __copyright__ = "Copyright 2005, Joseph Reisinger" __contributors__ = [ 'Andrew Nelis (andrew.nelis@gmail.com)', 'Devon Jones (soulcatcher@evilsoft.org)', 'Rodney Gomes (rodneygomes@gmail.com)'] __version__ = "1.1.0.1 $Rev:$" __license__ = "GPL" __history__ = """ Version 1.1.0.1: - Cleaned up revisions handling (Rodney) - Fix a newline bug with IE (Devon) - Sync to TW 1.2.38 Version 1.1: - Architecture change: added a type to each tiddler to track what importer handles it. (Joe) - Cleaned up/optimized refresh.js. (Joe) - Bugfix: Fixed a small newline bug (Joe) and a similar bug with \s (Devon) - Bugfix: disabled "save changes" (Devon) - Added a mysql database backend (Devon) - Added a set of plugins for displaying revisions (Rodney) See README for older versions. """ import os, sys, time ############################################################################### # Configuration and Global Parameters ############################################################################### # The base html file from http://www.tiddlywiki.com (sans the last few lines) TWBase = 'tiddlywiki-1.2.38.html' # Javascript plugins. remote.js is mandatory for pytw to work, but can be # removed to disable serverside functionality cleanly. TWPlugins = ['remote.js', 'refresh.js', 'revisions.js'] # Importers for turning other kinds of files into tiddlers. pytw uses pickle # Static importers are cached on write, while Dynamic importers are not. StaticContent = ['pickleimporter', 'lockimporter'] DynamicContent = ['pdfimporter'] # Handlers for cgi requests. Format is CGIFormAttribute:python module . Handlers = {'save':'save', 'delete':'delete', 'refresh':'refresh', 'revisions':'revisions'} # Where the html div cache is stored. CachePath = 'store/tiddler_divs_html.cache' # Used for timestamping on save. Best not to change this. CurrentTime = time.strftime('%Y%m%d%H%M') ############################################################################### # Code ############################################################################### def escape_html(text): """Does the same as cgi.escape but doesn't require the cgi module to be loaded.""" return text.replace('&', '&').replace('<', '<').replace('>', '>') def extract( form, name ): """ Safe Extraction from a CGI form. All unicode encoding is done here. """ return unicode( form.getvalue(name, '').replace(r'\s', '\ss').replace(r'\n','\sn').replace('\n','\\n'), 'utf-8' ).encode('utf-8') def form_to_tiddler( form ): """ Set up the Tiddler content variables. Not Pythonic, I'm sure. """ return {'title': extract( form, 'title' ), 'oldtitle': extract( form, 'oldtitle' ), 'body': extract( form, 'body' ), 'author': extract( form, 'author' ), 'tags': extract( form, 'tags' ), 'modified': CurrentTime} def tiddler_to_markup( content, type='undefined' ): """ Returns the
for the tiddler. """ fields = tuple(map(escape_html, (content['title'], content['modified'], content['author'], content['tags'], type, content['body'].replace('\n','\\n')))) return '
%s
\n' % fields def get_html_content( content_type, store_files=None ): """Runs load_tiddlers on each plugin in content_type, sending them store_files, if available. Very proud of this.""" return ''.join( [__import__(plugin).load_tiddlers( store_files ) for plugin in content_type] ) def get_divs( force_reload = False ): """ Reads all the entries in the store and returns them as a set of for direct HTML inclusion. """ store_files = os.listdir( 'store/' ) # Read static tiddlers from cache if we are allowed/can if not force_reload and os.path.exists( CachePath ): div_html = open( CachePath ).read() else: # Otherwise read and re-cache the static tiddlers. div_html = get_html_content( StaticContent, store_files ) # Cache the static tiddlers. TODO: fcntl here too? try: open( CachePath, 'w' ).write( div_html ) except: print 'Error writing cache "%s".' % CachePath # Always refresh dynamic tiddlers. div_html += get_html_content( DynamicContent, store_files ) return div_html def handle_post(): """Handle a save/delete from the client. cgi is massive, use it sparingly.""" from cgi import FieldStorage form = FieldStorage() # Look through our handlers to find a (single) match. for (key, handler) in Handlers.items(): if form.has_key( key ): __import__(handler).handle( form ) break def render_page( ): """ Send out the page contents. TODO: HACK: Removed the last three tags present in the tiddlywiki base file. """ print 'Content-type: text/html; charset=utf-8\n' print open( TWBase ).read() # Read each filename from the TOC and inject it into the markup at load. print get_divs() # Close the markup. print '
' # Include the plugins: print '' def execute(): """ Actually runs the cgi script. """ # Add the handler and importer paths. sys.path.append( 'importers/' ) sys.path.append( 'handlers/' ) # Forbid certain kinds of interactions, and fail elegantly, e.g. on 405 request methods. request_method = os.environ['REQUEST_METHOD'] if request_method == 'POST': handle_post() elif request_method == 'GET': render_page()