import os import re from webob import Request, Response from webob import exc from tempita import HTMLTemplate VIEW_TEMPLATE = HTMLTemplate("""\ {{page.title}}

{{page.title}}

{{if message}}
{{message}}
{{endif}}
{{page.content|html}}

Edit """) EDIT_TEMPLATE = HTMLTemplate("""\ Edit: {{page.title}} {{if page.exists}}

Edit: {{page.title}}

{{else}}

Create: {{page.title}}

{{endif}}
Title:
Content: Cancel

Cancel
""") class WikiApp(object): view_template = VIEW_TEMPLATE edit_template = EDIT_TEMPLATE def __init__(self, storage_dir): self.storage_dir = os.path.abspath(os.path.normpath(storage_dir)) def __call__(self, environ, start_response): req = Request(environ) action = req.params.get('action', 'view') page = self.get_page(req.path_info) try: try: meth = getattr(self, 'action_%s_%s' % (action, req.method)) except AttributeError: raise exc.HTTPBadRequest('No such action %r' % action) resp = meth(req, page) except exc.HTTPException, e: resp = e return resp(environ, start_response) def get_page(self, path): path = path.lstrip('/') if not path: path = 'index' path = os.path.join(self.storage_dir, path) path = os.path.normpath(path) if path.endswith('/'): path += 'index' if not path.startswith(self.storage_dir): raise exc.HTTPBadRequest("Bad path") path += '.html' return Page(path) def action_view_GET(self, req, page): if not page.exists: return exc.HTTPTemporaryRedirect( location=req.url + '?action=edit') if req.cookies.get('message'): message = req.cookies['message'] else: message = None text = self.view_template.substitute( page=page, req=req, message=message) resp = Response(text) if message: resp.delete_cookie('message') else: resp.last_modified = page.mtime resp.conditional_response = True return resp def action_view_POST(self, req, page): submit_mtime = int(req.params.get('mtime') or '0') or None if page.mtime != submit_mtime: return exc.HTTPPreconditionFailed( "The page has been updated since you started editing it") page.set( title=req.params['title'], content=req.params['content']) resp = exc.HTTPSeeOther( location=req.path_url) resp.set_cookie('message', 'Page updated') return resp def action_edit_GET(self, req, page): text = self.edit_template.substitute( page=page, req=req) return Response(text) class Page(object): def __init__(self, filename): self.filename = filename @property def exists(self): return os.path.exists(self.filename) @property def title(self): if not self.exists: # we need to guess the title basename = os.path.splitext(os.path.basename(self.filename))[0] basename = re.sub(r'[_-]', ' ', basename) return basename.capitalize() content = self.full_content match = re.search(r'(.*?)', content, re.I|re.S) return match.group(1) @property def full_content(self): f = open(self.filename, 'rb') try: return f.read() finally: f.close() @property def content(self): if not self.exists: return '' content = self.full_content match = re.search(r']*>(.*?)', content, re.I|re.S) return match.group(1) @property def mtime(self): if not self.exists: return None else: return int(os.stat(self.filename).st_mtime) def set(self, title, content): dir = os.path.dirname(self.filename) if not os.path.exists(dir): os.makedirs(dir) new_content = """%s%s""" % ( title, content) f = open(self.filename, 'wb') f.write(new_content) f.close() if __name__ == '__main__': import optparse parser = optparse.OptionParser( usage='%prog --port=PORT' ) parser.add_option( '-p', '--port', default='8080', dest='port', type='int', help='Port to serve on (default 8080)') parser.add_option( '--wiki-data', default='./wiki', dest='wiki_data', help='Place to put wiki data into (default ./wiki/)') options, args = parser.parse_args() print 'Writing wiki pages to %s' % options.wiki_data app = WikiApp(options.wiki_data) from wsgiref.simple_server import make_server httpd = make_server('localhost', options.port, app) print 'Serving on http://localhost:%s' % options.port try: httpd.serve_forever() except KeyboardInterrupt: print '^C'