3
from webob import Request, Response
5
from tempita import HTMLTemplate
7
VIEW_TEMPLATE = HTMLTemplate("""\
10
<title>{{page.title}}</title>
13
<h1>{{page.title}}</h1>
15
<div style="background-color: #99f">{{message}}</div>
18
<div>{{page.content|html}}</div>
21
<a href="{{req.url}}?action=edit">Edit</a>
26
EDIT_TEMPLATE = HTMLTemplate("""\
29
<title>Edit: {{page.title}}</title>
33
<h1>Edit: {{page.title}}</h1>
35
<h1>Create: {{page.title}}</h1>
38
<form action="{{req.path_url}}" method="POST">
39
<input type="hidden" name="mtime" value="{{page.mtime}}">
40
Title: <input type="text" name="title" style="width: 70%" value="{{page.title}}"><br>
41
Content: <input type="submit" value="Save">
42
<a href="{{req.path_url}}">Cancel</a>
44
<textarea name="content" style="width: 100%; height: 75%" rows="40">{{page.content}}</textarea>
46
<input type="submit" value="Save">
47
<a href="{{req.path_url}}">Cancel</a>
52
class WikiApp(object):
54
view_template = VIEW_TEMPLATE
55
edit_template = EDIT_TEMPLATE
57
def __init__(self, storage_dir):
58
self.storage_dir = os.path.abspath(os.path.normpath(storage_dir))
60
def __call__(self, environ, start_response):
61
req = Request(environ)
62
action = req.params.get('action', 'view')
63
page = self.get_page(req.path_info)
66
meth = getattr(self, 'action_%s_%s' % (action, req.method))
67
except AttributeError:
68
raise exc.HTTPBadRequest('No such action %r' % action).exception
69
resp = meth(req, page)
70
except exc.HTTPException, e:
72
return resp(environ, start_response)
74
def get_page(self, path):
75
path = path.lstrip('/')
78
path = os.path.join(self.storage_dir, path)
79
path = os.path.normpath(path)
80
if path.endswith('/'):
82
if not path.startswith(self.storage_dir):
83
raise exc.HTTPBadRequest("Bad path").exception
87
def action_view_GET(self, req, page):
89
return exc.HTTPTemporaryRedirect(
90
location=req.url + '?action=edit')
91
if req.cookies.get('message'):
92
message = req.cookies['message']
95
text = self.view_template.substitute(
96
page=page, req=req, message=message)
99
resp.delete_cookie('message')
101
resp.last_modified = page.mtime
102
resp.conditional_response = True
105
def action_view_POST(self, req, page):
106
submit_mtime = int(req.params.get('mtime') or '0') or None
107
if page.mtime != submit_mtime:
108
return exc.HTTPPreconditionFailed(
109
"The page has been updated since you started editing it")
111
title=req.params['title'],
112
content=req.params['content'])
113
resp = exc.HTTPSeeOther(
114
location=req.path_url)
115
resp.set_cookie('message', 'Page updated')
118
def action_edit_GET(self, req, page):
119
text = self.edit_template.substitute(
121
return Response(text)
124
def __init__(self, filename):
125
self.filename = filename
129
return os.path.exists(self.filename)
134
# we need to guess the title
135
basename = os.path.splitext(os.path.basename(self.filename))[0]
136
basename = re.sub(r'[_-]', ' ', basename)
137
return basename.capitalize()
138
content = self.full_content
139
match = re.search(r'<title>(.*?)</title>', content, re.I|re.S)
140
return match.group(1)
143
def full_content(self):
144
f = open(self.filename, 'rb')
154
content = self.full_content
155
match = re.search(r'<body[^>]*>(.*?)</body>', content, re.I|re.S)
156
return match.group(1)
163
return os.stat(self.filename).st_mtime
165
def set(self, title, content):
166
dir = os.path.dirname(self.filename)
167
if not os.path.exists(dir):
169
new_content = """<html><head><title>%s</title></head><body>%s</body></html>""" % (
171
f = open(self.filename, 'wb')
175
if __name__ == '__main__':
177
parser = optparse.OptionParser(
178
usage='%prog --port=PORT'
185
help='Port to serve on (default 8080)')
190
help='Place to put wiki data into (default ./wiki/)')
191
options, args = parser.parse_args()
192
print 'Writing wiki pages to %s' % options.wiki_data
193
app = WikiApp(options.wiki_data)
194
from wsgiref.simple_server import make_server
195
httpd = make_server('localhost', options.port, app)
196
print 'Serving on http://localhost:%s' % options.port
198
httpd.serve_forever()
199
except KeyboardInterrupt: