"""tw.cgiA 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.38Version 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 <div/> for the tiddler. """ fields = tuple(map(escape_html, (content['title'], content['modified'], content['author'], content['tags'], type, content['body'].replace('\n','\\n')))) return '<div tiddler="%s" modified="%s" modifier="%s" tags="%s" type="%s">%s</div>\n' % fieldsdef 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 <divs> 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_htmldef 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 ) breakdef 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 '</div></body></html>' # Include the plugins: print '<script type="text/javascript">' for plugin in TWPlugins: print open( 'plugins/%s' % plugin ).read() print '</script>'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()