#!/usr/bin/env python
#coding: utf8
# wikiri - A single-file wiki engine.
# Alberto Bertogli (albertito@blitiri.com.ar)
#
# Configuration section
#
# You can edit these values, or create a file named "config.py" and put them
# there to make updating easier. The ones in config.py take precedence.
#
# Directory where entries are stored
data_path = "data/"
# Path where templates are stored. Use an empty string for the built-in
# default templates. If they're not found, the built-in ones will be used.
templates_path = "templates/"
# URL to the wiki, including the name. Can be a full URL or just the path.
wiki_url = "/wiki/wikiri.cgi"
# Style sheet (CSS) URL. Can be relative or absolute. To use the built-in
# default, set it to wiki_url + "/style".
css_url = wiki_url + "/style"
# Wiki title.
title = "I like wikis"
# History backend. Can be one of:
# - "none" for no history
# - "git" to use git (needs git and the data path to be a repository)
# - "darcs" to use darcs (needs darcs and the data path to be a repository)
# - "auto" to select automatically (looks if the data path is a repository)
history = "auto"
#
# End of configuration
# DO *NOT* EDIT ANYTHING PAST HERE
#
import sys
import os
import errno
import datetime
import urllib
import cgi
# Load the config file, if there is one
try:
from config import *
except:
pass
# Find out our URL, just in case the templates need it (the default templates
# do not use it at all)
try:
n = os.environ['SERVER_NAME']
p = os.environ['SERVER_PORT']
s = os.environ['SCRIPT_NAME']
if p == '80': p = ''
else: p = ':' + p
full_url = 'http://%s%s%s' % (n, p, s)
except KeyError:
full_url = 'Not needed'
#
# Markup processing
#
import docutils.parsers.rst
import docutils.nodes
from docutils.core import publish_parts
def _wiki_link_role(role, rawtext, text, lineno, inliner,
options = {}, content = []):
ref = wiki_url + '/' + \
urllib.quote_plus(text.encode('utf8'), safe = '')
node = docutils.nodes.reference(rawtext, text, refuri = ref,
**options)
return [node], []
def content2html(content):
settings = {
'input_encoding': 'utf8',
'output_encoding': 'utf8',
}
docutils.parsers.rst.roles.register_canonical_role('wikilink',
_wiki_link_role)
docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE = 'wikilink'
parts = publish_parts(content,
settings_overrides = settings,
writer_name = "html")
return parts['body'].encode('utf8')
def diff2html(diff):
from xml.sax.saxutils import escape
s = '<div class="diff">'
for l in diff.split('\n'):
l = l.rstrip()
if l.startswith("+") and not l.startswith("+++"):
c = "add"
elif l.startswith("-") and not l.startswith("---"):
c = "remove"
elif l.startswith(" "):
c = "unchanged"
elif l.startswith("@@"):
c = "position"
elif l.startswith("diff"):
c = "header"
else:
c = "other"
s += '<span class="%s">' % c + escape(l) + '</span>\n'
# note there's no need to put <br/>s because the div.diff has
# "white-space: pre" in the css
s += '</div>'
return s
#
# Templates
#
# Default template
default_article_content = """\
title: %(artname)s
This page does *not* exist yet.
"""
default_main_header = """
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<link href="%(css_url)s" rel="stylesheet" type="text/css" />
<title>%(title)s</title>
</head>
<body>
<h1><a href="%(url)s">%(title)s</a></h1>
<div class="content">
"""
default_main_footer = """
</div><p/>
</div>
</body>
</html>
"""
default_article_header = """
<div class="article">
<h2><a href="%(url)s/%(artqname)s">%(arttitle)s</a></h2>
<span class="artinfo">
updated on %(uyear)04d-%(umonth)02d-%(uday)02d
%(uhour)02d:%(uminute)02d<br/>
</span><p/>
<div class="artbody">
"""
default_article_footer = """
<p/>
</div>
<hr/><p/>
<div class="footer">
<a href="%(url)s/about">about</a> |
<a href="%(url)s/help">help</a> |
<a href="%(url)s/%(artqname)s/log">view history</a> |
<a href="%(url)s/%(artqname)s/edit">edit this page</a><br/>
</div>
</div>
"""
default_edit_page = """
<h2>Edit <a href="%(url)s/%(artqname)s">%(artname)s</a></h2>
<p/>
<div class="editpage">
<form method="POST" action="save">
<label for="newtitle">Title: </label>
<input id="newtitle" type="text" name="newtitle"
value="%(arttitle)s"/><p/>
<textarea name="newcontent" cols="76" rows="25">
%(content)s
</textarea><p/>
<label for="comment">Change summary: </label>
<input id="comment" type="text" name="comment"
size="50" value="%(comment)s"/><p/>
<button name="submit" type="submit" value="submit">Save</button>
<button name="preview" type="submit" value="preview">Preview</button>
</form>
<p/>
</div>
<div class="quickhelp">
<h2>Quick help</h2>
<ul>
<li>Paragraphs are split by an empty line.</li>
<li>Lines that are underlined with dashes ("----") are a title.</li>
<li>Lines beginning with an "*" form a list.</li>
<li>To create new links, put them between backticks, <tt>`like
this`</tt>.</li>
</ul>
More information:
<a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">reST
quick reference</a>.<p/>
</div>
</div>
"""
default_removed_page = """
<div class="article">
<h2><a href="%(url)s/%(artqname)s">%(artname)s</a></h2>
<div class="artbody">
<p/>
This page has been successfuly removed.<p/>
</div>
</div>
"""
default_diff_header = """
<h2>Modification to <a href="%(url)s/%(artqname)s">%(arttitle)s</a></h2>
<p/>
This page displays the modifications performed by the selected change. Green
lines are additions, red lines are removals.<p/>
"""
default_diff_footer = """
<hr/><p/>
<div class="footer">
<a href="%(url)s/about">about</a> |
<a href="%(url)s/help">help</a> |
<a href="%(url)s/%(artqname)s/log">view history</a> |
<a href="%(url)s/%(artqname)s/edit">edit this page</a><br/>
</div>
</div>
"""
default_help_page = """
<div class="article">
<h2>Help</h2>
<div class="artbody">
<p/>
This wiki uses <a href="http://docutils.sf.net/rst.html">reStructuredText</a>
for markup.<br/>
Here is a quick syntax summary.<p/>
<ul>
<li>Paragraphs are split by an empty line.</li>
<li>Lines that are underlined with dashes ("----") are a title.</li>
<li>Lines beginning with an "*" form a list.</li>
<li>To create new links, put them between backticks, <tt>`like
this`</tt>.</li>
</ul>
If you want more information, see the
<a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">reST
quick reference</a>.<p/>
</div>
</div>
"""
default_about_page = """
<div class="article">
<h2>About</h2>
<div class="artbody">
<p/>
This wiki is powered by
<a href="http://blitiri.com.ar/p/misc.html">wikiri</a>, a single-file
blog engine written in <a href="http://python.org">Python</a>, and uses
<a href="http://docutils.sf.net/rst.html">reStructuredText</a>
for markup.<p/>
If you have any questions or comments, please send an email to
<a href="mailto:albertito@blitiri.com.ar">Alberto Bertogli</a>.<p/>
</div>
</div>
"""
default_log_header = """
<h2>Change history for <a href="%(url)s/%(artqname)s">%(artname)s</a></h2>
<p/>
<table class="log">
"""
default_log_entry = """
<tr>
<td class="date">
%(date_y)04d-%(date_m)02d-%(date_d)02d
%(date_H)02d:%(date_M)02d:%(date_S)02d
</td>
<td class="summary">%(summary)s</td>
<td class="links">
<a href="%(url)s/logview/%(commitid)s/%(artqname)s">view</a> |
<a href="%(url)s/restore/%(commitid)s/%(artqname)s">restore</a> |
<a href="%(url)s/diff/%(commitid)s/%(artqname)s">diff</a>
</td>
</tr>
"""
default_log_footer = """
</table>
"""
# Default CSS
default_css = """
body {
font-family: sans-serif;
}
div.content {
width: 60%;
}
h1 {
font-size: x-large;
border-bottom: 2px solid #99F;
width: 65%;
margin-bottom: 1em;
}
h2 {
font-size: large;
font-weight: bold;
margin-bottom: 1pt;
border-bottom: 1px solid #99C;
}
h1 a, h2 a {
text-decoration: none;
color: black;
}
span.artinfo {
font-size: x-small;
}
span.artinfo a {
text-decoration: none;
color: #339;
}
span.artinfo a:hover {
text-decoration: none;
color: blue;
}
div.artbody {
margin-left: 1em;
}
div.article {
margin-bottom: 2em;
}
hr {
/* hack to properly align the hr */
text-align: left;
margin: 0 auto 0 0;
height: 2px;
border: 0;
background-color: #99F;
width: 60%;
}
div.footer {
font-size: small;
}
div.footer a {
text-decoration: none;
}
table.log {
border-right: 1px solid #6666cc;
border-collapse: collapse;
margin-left: 1em;
}
table.log td {
border-left: 1px solid #6666cc;
padding-right: 1em;
padding-left: 1em;
}
table.log td.date {
white-space: nowrap;
}
table.log td.summary {
}
table.log td.links {
white-space: nowrap;
font-size: small;
}
/* Articles are enclosed in <div class="section"> */
div.section h1 {
font-size: medium;
font-weight: bold;
width: 100%;
margin-bottom: 1pt;
border-bottom: 1px dotted #99C;
}
div.section h2 {
font-size: small;
font-weight: bold;
width: 75%;
margin-bottom: 1pt;
border-bottom: 1px dotted #DDD;
}
/* diff */
div.diff {
font-family: monospace;
white-space: pre;
margin: 0;
padding: 0;
}
div.diff span.add {
color: #090;
}
div.diff span.remove {
color: #900;
}
div.diff span.unchanged {
}
div.diff span.position {
background-color: #E5E5FF;
}
div.diff span.header {
background-color: #CCF;
font-weight: bold;
}
div.diff span.other {
}
"""
class Templates (object):
def __init__(self):
self.tpath = templates_path
now = datetime.datetime.now()
self.vars = {
'css_url': css_url,
'title': title,
'url': wiki_url,
'fullurl': full_url,
'year': now.year,
'month': now.month,
'day': now.day,
}
def art_vars(self, article):
avars = self.vars.copy()
avars.update( {
'arttitle': article.title,
'artname': article.name,
'artqname': article.qname,
'updated': article.updated.isoformat(' '),
'uyear': article.updated.year,
'umonth': article.updated.month,
'uday': article.updated.day,
'uhour': article.updated.hour,
'uminute': article.updated.minute,
'usecond': article.updated.second,
} )
return avars
def get_main_header(self):
p = self.tpath + '/header.html'
if os.path.isfile(p):
return open(p).read() % self.vars
return default_main_header % self.vars
def get_default_article_content(self, artname):
avars = self.vars.copy()
avars.update( {
'artname': artname,
'artqname': urllib.quote_plus(artname, safe = ''),
} )
p = self.tpath + '/default_article_content.wiki'
if os.path.isfile(p):
return open(p).read() % avars
return default_article_content % avars
def get_main_footer(self):
p = self.tpath + '/footer.html'
if os.path.isfile(p):
return open(p).read() % self.vars
return default_main_footer % self.vars
def get_article_header(self, article):
avars = self.art_vars(article)
p = self.tpath + '/art_header.html'
if os.path.isfile(p):
return open(p).read() % avars
return default_article_header % avars
def get_article_footer(self, article):
avars = self.art_vars(article)
p = self.tpath + '/art_footer.html'
if os.path.isfile(p):
return open(p).read() % avars
return default_article_footer % avars
def get_edit_page(self, article, comment):
avars = self.art_vars(article)
avars.update( {
'content': article.raw_content,
'comment': comment,
} )
p = self.tpath + '/edit_page.html'
if os.path.isfile(p):
return open(p).read() % avars
return default_edit_page % avars
def get_removed_page(self, artname):
avars = self.vars.copy()
avars.update( {
'artname': artname,
'artqname': urllib.quote_plus(artname, safe = ''),
} )
p = self.tpath + '/removed_page.html'
if os.path.isfile(p):
return open(p).read() % avars
return default_removed_page % avars
def get_diff_header(self, article, commitid):
avars = self.art_vars(article)
avars.update( {
'commitid': commitid,
} )
p = self.tpath + '/diff_header.html'
if os.path.isfile(p):
return open(p).read() % avars
return default_diff_header % avars
def get_diff_footer(self, article, commitid):
avars = self.art_vars(article)
avars.update( {
'commitid': commitid,
} )
p = self.tpath + '/diff_footer.html'
if os.path.isfile(p):
return open(p).read() % avars
return default_diff_footer % avars
def get_help_page(self):
p = self.tpath + '/help_page.html'
if os.path.isfile(p):
return open(p).read() % self.vars
return default_help_page % self.vars
def get_about_page(self):
p = self.tpath + '/about_page.html'
if os.path.isfile(p):
return open(p).read() % self.vars
return default_about_page % self.vars
def get_log_header(self, artname):
avars = self.vars.copy()
avars.update( {
'artname': artname,
'artqname': urllib.quote_plus(artname, safe = ''),
} )
p = self.tpath + '/log_header.html'
if os.path.isfile(p):
return open(p).read() % avars
return default_log_header % avars
def get_log_entry(self, artname, commit):
avars = self.vars.copy()
avars.update( {
'artname': artname,
'artqname': urllib.quote_plus(artname, safe = ''),
'summary': commit['msg'],
'commitid': commit['commit'],
'date_y': commit['atime'].year,
'date_m': commit['atime'].month,
'date_d': commit['atime'].day,
'date_H': commit['atime'].hour,
'date_M': commit['atime'].minute,
'date_S': commit['atime'].second,
} )
p = self.tpath + '/log_entry.html'
if os.path.isfile(p):
return open(p).read() % avars
return default_log_entry % avars
def get_log_footer(self, artname):
avars = self.vars.copy()
avars.update( {
'artname': artname,
'artqname': urllib.quote_plus(artname, safe = ''),
} )
p = self.tpath + '/log_footer.html'
if os.path.isfile(p):
return open(p).read() % avars
return default_log_footer % avars
#
# Article handling
#
class Article (object):
def __init__(self, name, title = None, content = None,
has_header = True):
self.name = name
self.qname = urllib.quote_plus(name, safe = "")
self.updated = None
self.loaded = False
self.preloaded_title = title
self.preloaded_content = content
self.has_header = has_header
# loaded on demand
self.attrs = {}
self._raw_content = ''
def get_raw_content(self):
if not self.loaded:
self.load()
return self._raw_content
raw_content = property(fget = get_raw_content)
def get_title(self):
if not self.loaded:
self.load()
# use the name by default
return self.attrs.get('title', self.name)
title = property(fget = get_title)
def load(self):
try:
if self.preloaded_content:
raw = self.preloaded_content
raw = [ s + '\n' for s in raw.split('\n') ]
self.updated = datetime.datetime.now()
else:
fd = open(data_path + '/' + self.qname)
raw = fd.readlines()
stat = os.fstat(fd.fileno())
self.updated = datetime.datetime.fromtimestamp(stat.st_mtime)
except:
t = Templates()
raw = t.get_default_article_content(self.name)
raw = [ s + '\n' for s in raw.split('\n') ]
self.updated = datetime.datetime.now()
hdr_lines = 0
if self.has_header:
for l in raw:
if ':' in l:
name, value = l.split(':', 1)
name = name.lower().strip()
self.attrs[name] = value.strip()
hdr_lines += 1
elif l == '\n' or l == '\r\n':
# end of header
hdr_lines += 1
break
self._raw_content = ''.join(raw[hdr_lines:])
self.loaded = True
if self.preloaded_title:
self.attrs['title'] = self.preloaded_title
def save(self, newtitle, newcontent, raw = False):
fd = open(data_path + '/' + self.qname, 'w+')
if raw:
fd.write(newcontent)
else:
fd.write('title: %s\n\n' % newtitle)
fd.write(newcontent.rstrip() + '\n')
fd.close()
# invalidate our information
self.loaded = False
def remove(self):
try:
os.unlink(data_path + '/' + self.qname)
except OSError, errno.ENOENT:
pass
def to_html(self):
return content2html(self.raw_content)
#
# History backends
#
# At the moment we support none, git and darcs
class HistoryError (Exception):
pass
class History:
def __init__(self):
if history == 'auto':
if os.path.isdir(data_path + '.git'):
self.be = GitBackend(data_path)
elif os.path.isdir(data_path + '_darcs'):
self.be = DarcsBackend(data_path)
else:
self.be = NoneBackend(data_path)
elif history == 'git':
self.be = GitBackend(data_path)
elif history == 'darcs':
self.be = DarcsBackend(data_path)
else:
self.be = NoneBackend(data_path)
def commit(self, msg, author = 'Wikiri <somebody@wikiri>'):
self.be.commit(msg, author = author)
def log(self, fname):
# log() yields commits of the form:
# {
# 'commit': commit id
# 'author': author of the commit
# 'committer': committer
# 'atime': time of the change itself
# 'ctime': time of the commit
# 'msg': commit msg (one line)
# }
return self.be.log(file = fname)
def add(self, *files):
return self.be.add(*files)
def remove(self, *files):
return self.be.remove(*files)
def get_content(self, fname, cid):
return self.be.get_content(fname, cid)
def get_commit(self, cid):
return self.be.get_commit(cid)
def get_diff(self, cid):
# get_diff() returns the diff in unified format
return self.be.get_diff(cid)
class NoneBackend:
def __init__(self, repopath):
pass
def commit(self, *args, **kwargs):
pass
def log(self, *args, **kwargs):
return ()
def add(self, *files):
pass
def remove(self, *files):
pass
def get_content(self, fname, cid):
return "Not supported."
def get_commit(self, cid):
return { 'commit': cid }
def get_diff(self, cid):
return ""
class GitBackend:
def __init__(self, repopath):
self.repo = repopath
self.prevdir = None
def cdrepo(self):
self.prevdir = os.getcwd()
os.chdir(self.repo)
def cdback(self):
os.chdir(self.prevdir)
def git(self, *args):
# delay the import to avoid the hit on a regular page view
import subprocess
self.cdrepo()
cmd = subprocess.Popen( ['git'] + list(args),
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = sys.stderr )
self.cdback()
return cmd
def gitq(self, *args):
cmd = self.git(*args)
stdout, stderr = cmd.communicate()
return cmd.returncode
def commit(self, msg, author = None):
if not author:
author = "Unknown <unknown@example.com>"
# see if we have something to commit; if not, just return
self.gitq('update-index', '--refresh')
r = self.gitq('diff-index', '--exit-code', '--quiet', 'HEAD')
if r == 0:
return
r = self.gitq('commit',
'-m', msg,
'--author', author)
if r != 0:
raise HistoryError, r
def log(self, file = None, files = None):
if not files:
files = []
if file:
files.append(file)
cmd = self.git("rev-list",
"--all", "--pretty=raw",
"HEAD", "--", *files)
cmd.stdin.close()
commit = { 'msg': '' }
in_hdr = True
l = cmd.stdout.readline()
while l:
if l != '\n' and not l.startswith(' '):
name, value = l[:-1].split(' ', 1)
if in_hdr:
commit[name] = value
else:
# the previous commit has ended
_add_times(commit)
yield commit
commit = { 'msg': '' }
in_hdr = True
# continue reusing the line
continue
else:
if in_hdr:
in_hdr = False
if l.startswith(' '):
l = l[4:]
commit['msg'] += l
l = cmd.stdout.readline()
# the last commit, if there is one
if not in_hdr:
_add_times(commit)
yield commit
cmd.wait()
if cmd.returncode != 0:
raise HistoryError, cmd.returncode
def add(self, *files):
r = self.gitq('add', "--", *files)
if r != 0:
raise HistoryError, r
def remove(self, *files):
r = self.gitq('rm', '-f', '--', *files)
if r != 0:
raise HistoryError, r
def get_content(self, fname, commitid):
cmd = self.git("show", "%s:%s" % (commitid, fname))
content = cmd.stdout.read()
cmd.wait()
return content
def get_commit(self, cid):
cmd = self.git("rev-list", "-n1", "--pretty=raw", cid)
out = cmd.stdout.readlines()
cmd.wait()
commit = { 'msg': '' }
for l in out:
if l != '\n' and not l.startswith(' '):
name, value = l[:-1].split(' ', 1)
commit[name] = value
else:
commit['msg'] += l
_add_times(commit)
return commit
def get_diff(self, cid):
cmd = self.git("diff", cid + "^.." + cid)
out = cmd.stdout.read()
cmd.wait()
return out
def _add_times(commit):
if 'author' in commit:
author, epoch, tz = commit['author'].rsplit(' ', 2)
epoch = float(epoch)
commit['author'] = author
commit['atime'] = datetime.datetime.fromtimestamp(epoch)
if 'committer' in commit:
committer, epoch, tz = commit['committer'].rsplit(' ', 2)
epoch = float(epoch)
commit['committer'] = committer
commit['ctime'] = datetime.datetime.fromtimestamp(epoch)
class DarcsBackend:
def __init__(self, repopath):
self.repo = repopath
self.prevdir = None
def cdrepo(self):
self.prevdir = os.getcwd()
os.chdir(self.repo)
def cdback(self):
os.chdir(self.prevdir)
def darcs(self, *args):
# delay the import to avoid the hit on a regular page view
import subprocess
self.cdrepo()
cmd = subprocess.Popen( ['darcs'] + list(args),
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = sys.stderr )
self.cdback()
return cmd
def darcsq(self, *args):
cmd = self.darcs(*args)
stdout, stderr = cmd.communicate()
return cmd.returncode
def commit(self, msg, author = None):
if not author:
author = "Unknown <unknown@example.com>"
# see if we have something to commit; if not, just return
if self.darcsq('whatsnew') != 0:
return
r = self.darcsq('record',
'-a',
'-m', msg,
'-A', author)
if r != 0:
raise HistoryError, r
def log(self, file = None, files = None):
import xml.dom.minidom as minidom
if not files:
files = []
if file:
files.append(file)
cmd = self.darcs("changes",
"--xml-output",
"--", *files)
cmd.stdin.close()
xml = minidom.parse(cmd.stdout)
cmd.wait()
if cmd.returncode != 0:
raise HistoryError, cmd.returncode
for p in xml.getElementsByTagName('patch'):
# ignore patches not directly inside <changelog> (they
# can be, for instance, inside <created_as>, which we
# want to ignore)
if p.parentNode.nodeName != 'changelog':
continue
cid = p.getAttribute("hash")
author = p.getAttribute('author')
atime = p.getAttribute('date')
atime = datetime.datetime.strptime(atime,
"%Y%m%d%H%M%S")
msg = p.getElementsByTagName('name')[0]
msg = msg.childNodes[0].data
msg = msg.split('\n')[0].strip()
commit = {
'commit': cid.encode('utf8'),
'author': author.encode('utf8'),
'committer': author.encode('utf8'),
'atime': atime,
'ctime': atime,
'msg': msg.encode('utf8'),
}
yield commit
def add(self, *files):
r = self.darcsq('add', "--", *files)
# 0 means success, 2 means the file was already there (which
# is ok because we always add the files)
if r != 0 and r != 2:
raise HistoryError, r
def remove(self, *files):
r = self.darcsq('remove', '--', *files)
if r != 0:
raise HistoryError, r
def get_content(self, fname, commitid):
cmd = self.darcs("show", "contents",
"--match", "hash %s" % commitid,
"--", fname)
content = cmd.stdout.read()
cmd.wait()
return content
def get_commit(self, cid):
import xml.dom.minidom as minidom
cmd = self.darcs("changes",
"--xml-output",
"--match", "hash %s" % cid)
cmd.stdin.close()
xml = minidom.parse(cmd.stdout)
cmd.wait()
if cmd.returncode != 0:
raise HistoryError, cmd.returncode
try:
p = xml.getElementsByTagName('patch')[0]
except IndexError:
raise HistoryError, "not such patch"
cid = p.getAttribute("hash")
author = p.getAttribute('author')
atime = p.getAttribute('date')
atime = datetime.datetime.strptime(atime,
"%Y%m%d%H%M%S")
msg = p.getElementsByTagName('name')[0]
msg = msg.childNodes[0].data
msg = msg.split('\n')[0].strip()
commit = {
'commit': cid.encode('utf8'),
'author': author.encode('utf8'),
'committer': author.encode('utf8'),
'atime': atime,
'ctime': atime,
'msg': msg.encode('utf8'),
}
return commit
def get_diff(self, cid):
# TODO
return ""
#
# Main
#
def render_article(art):
template = Templates()
print 'Content-type: text/html; charset=utf-8\n'
print template.get_main_header()
print template.get_article_header(art)
print art.to_html()
print template.get_article_footer(art)
print template.get_main_footer()
def render_edit(art, preview = False, comment = ""):
template = Templates()
print 'Content-type: text/html; charset=utf-8\n'
print template.get_main_header()
if preview:
print template.get_article_header(art)
print art.to_html()
print template.get_article_footer(art)
print template.get_edit_page(art, comment)
print template.get_main_footer()
def render_removed(artname):
template = Templates()
print 'Content-type: text/html; charset=utf-8\n'
print template.get_main_header()
print template.get_removed_page(artname)
print template.get_main_footer()
def render_log(artname, log):
template = Templates()
print 'Content-type: text/html; charset=utf-8\n'
print template.get_main_header()
print template.get_log_header(artname)
for commit in log:
print template.get_log_entry(artname, commit)
print template.get_log_footer(artname)
print template.get_main_footer()
def render_diff(article, cid, diff):
template = Templates()
print 'Content-type: text/html; charset=utf-8\n'
print template.get_main_header()
print template.get_diff_header(article, cid)
print diff2html(diff)
print template.get_diff_footer(article, cid)
print template.get_main_footer()
def render_help():
template = Templates()
print 'Content-type: text/html; charset=utf-8\n'
print template.get_main_header()
print template.get_help_page()
print template.get_main_footer()
def render_about():
template = Templates()
print 'Content-type: text/html; charset=utf-8\n'
print template.get_main_header()
print template.get_about_page()
print template.get_main_footer()
def render_style():
print 'Content-type: text/css\n'
print default_css
def redirect(artname):
print 'Status: 303 See Other\r\n',
print 'Location: %s/%s\r\n' % (wiki_url, artname),
print
def handle_cgi():
import cgitb; cgitb.enable()
form = cgi.FieldStorage()
edit = False
save = False
log = False
artname = 'index'
newcontent = form.getfirst("newcontent", '')
newtitle = form.getfirst("newtitle", '').strip()
preview = form.getfirst("preview", '').strip()
comment = form.getfirst("comment", '').strip()
remoteip = os.environ.get("REMOTE_ADDR", "unknownip")
author = "Somebody <%s@wikiri>" % remoteip
if os.environ.has_key('PATH_INFO'):
path_info = os.environ['PATH_INFO']
path_info = os.path.normpath(path_info)
edit = path_info.endswith('/edit')
save = path_info.endswith('/save')
log = path_info.endswith('/log')
if edit or save or log:
artname = path_info[1:].rsplit('/', 1)[0]
else:
artname = path_info[1:]
if artname == '' or artname == '/':
artname = 'index'
if save and not os.environ.get('REQUEST_METHOD', 'GET') == 'POST':
# only allow saves if the request is a post to prevent people
# from accidentally performing a GET .../save, which would
# result in an empty save, with the following page removal
save = False
artname = urllib.unquote_plus(artname)
if artname == 'style':
render_style()
elif artname == 'help':
render_help()
elif artname == 'about':
render_about()
elif edit or (save and preview):
if preview:
art = Article(artname,
title = newtitle,
content = newcontent,
has_header = False)
render_edit(art, preview = True, comment = comment)
else:
render_edit(Article(artname))
elif save:
if not comment:
comment = "No comment"
h = History()
a = Article(artname)
if newcontent.strip():
a.save(newtitle, newcontent)
h.add(a.qname)
h.commit(msg = comment, author = author)
redirect(artname)
else:
a.remove()
h.remove(a.qname)
h.commit(msg = comment, author = author)
redirect(artname)
elif log:
a = Article(artname)
render_log(a.name, History().log(a.qname))
elif artname.startswith("logview/"):
unused, cid, artname = artname.split('/', 2)
artname = urllib.unquote_plus(artname)
oldcontent = History().get_content(Article(artname).qname, cid)
render_article(Article(artname, content = oldcontent))
elif artname.startswith("restore/"):
unused, cid, artname = artname.split('/', 2)
artname = urllib.unquote_plus(artname)
h = History()
a = Article(artname)
oldcontent = h.get_content(a.qname, cid)
a.save(None, oldcontent, raw = True)
h.add(a.qname)
ctime = h.get_commit(cid)['atime'].strftime("%Y-%m-%d %H:%M:%S")
h.commit(msg = 'Restored ' + ctime, author = author)
redirect(artname)
elif artname.startswith("diff/"):
unused, cid, artname = artname.split('/', 2)
artname = urllib.unquote_plus(artname)
diff = History().get_diff(cid)
render_diff(Article(artname), cid, diff)
else:
render_article(Article(artname))
def handle_cmd():
print "This is a CGI application."
print "It only runs inside a web server."
return 1
if os.environ.has_key('GATEWAY_INTERFACE'):
handle_cgi()
else:
sys.exit(handle_cmd())