Mercurial > yarnball
changeset 0:ccb7ad78b336
Initial import
| author | Radomir Dopieralski <moin@sheep.art.pl> |
|---|---|
| date | Tue, 27 Nov 2007 23:44:54 +0100 |
| parents | |
| children | 80a265d4f2db |
| files | diff.cgi edit.cgi recent.cgi refs.cgi save.cgi tag.cgi view.cgi wiki/__init__.py wiki/__init__.pyc wiki/action.py wiki/config.py wiki/config.pyc wiki/diff.py wiki/diff.pyc wiki/edit.py wiki/edit.pyc wiki/link.py wiki/link.pyc wiki/page.py wiki/page.pyc wiki/parser.py wiki/parser.pyc wiki/recent.py wiki/recent.pyc wiki/references.py wiki/references.pyc wiki/refs.py wiki/refs.pyc wiki/save.py wiki/save.pyc wiki/storage.py wiki/storage.pyc wiki/tags.py wiki/theme.py wiki/theme.pyc wiki/utils.py wiki/utils.pyc wiki/view.py wiki/view.pyc |
| diffstat | 39 files changed, 1087 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/diff.cgi Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,5 @@ +#!/usr/bin/python +import cgitb +cgitb.enable() +import wiki.diff +wiki.diff.diff()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/edit.cgi Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,5 @@ +#!/usr/bin/python +import cgitb +cgitb.enable() +import wiki.edit +wiki.edit.edit()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/recent.cgi Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +from wiki import recent +import cgitb +cgitb.enable() +recent.recent()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/refs.cgi Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,5 @@ +#!/usr/bin/python +import cgitb +cgitb.enable() +import wiki.refs +wiki.refs.refs()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/save.cgi Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,5 @@ +#!/usr/bin/python +import cgitb +cgitb.enable() +import wiki.save +wiki.save.save()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tag.cgi Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,5 @@ +#!/usr/bin/python +import cgitb +cgitb.enable() +import wiki.tags +wiki.tags.tags()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view.cgi Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,5 @@ +#!/usr/bin/python +import cgitb +cgitb.enable() +import wiki.view +wiki.view.view()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/__init__.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,1 @@ +'''The code for the wiki engine.'''
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/action.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,66 @@ +import difflib, time +from page import Page +from parser import Parser +import storage, link, theme, references + + +def system(page): + if page.name=='recent_changes': + pages = storage.recent_changes(15) + links = [] + for page_name in pages: + page = Page(page_name) + page.action = 'diff' + links.append( + ( + link.Link(None, page.get_title(), None, page).html(), + time.strftime('%c %Z',time.localtime(page.edit_date())), + page.edit_user() or u'anonymous', + ) + ) + plist = ''.join([ ''.join([ '<li>', l[0], ' <span class="editdate">(', l[1], ' by ', l[2], ')</span>', '</li>', ]) for l in links]) + + html = ''.join([ + '<ul class="recent">', + plist, + '</ul>', + '<p>%s changed.</p>'%plural_page(len(pages)), + ]) + return html + elif page.name=='page_index': + pages = storage.page_index() + pages.sort() + html = ''.join([ + '<ul class="results">', + link_list(pages, 'view'), + '</ul>', + '<p>%s total.</p>'%plural_page(len(pages)), + ]) + return html + elif page.name=='find_page': + html = ''.join([ + u'<form action="view.cgi" method="get" class="find" enctype="utf-8">', + u'<p>Title search. Enter the words from page titles you want to look for.</p>', + u'<input name="find" value="">', + u'<div class="toolbar">', + u'<button type="submit">find</button>', + u'</div>', + u'</form>', + ]) + return html + else: + return 'Bad system page' + +def find(pg): + hits = storage.find_pages(pg.name) + if hits: + html = ''.join([ + '<p>Following pages contain <q>%s</q> in their titles:</p>'%pg.name, + '<ul class="results">', + link_list(hits, 'view'), + '</ul>', + '<p>%s found.</p>'%plural_page(len(hits)), + ]) + return html + else: + return "<p>No page contain <q>%s</q> in its title."%pg.name
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/config.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,8 @@ + +class Config: + '''Various settings that might be different for different wikis.''' + + front_page = 'Welcome' + pages_dir = 'pages' + ref_file = 'data/references' + tag_file = 'data/tags'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/diff.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,38 @@ +from theme import Theme +from config import Config +from page import Page +from parser import Parser +import utils +import difflib + +def diff(): + '''Display diff with the previous version of the page.''' + + page_name = utils.get_query() or Config.front_page + theme = Theme() + parser = Parser() + page = Page(page_name) + text = page.get_text() + old_page = Page(page.get_id()) + old_page.version += 1 + old_text = old_page.get_text() + d = difflib.Differ() + delta = d.compare(old_text.splitlines(1), text.splitlines(1)) + html = '<div class="diff">' + for l in delta: + a = l[0] + t = parser._escape_html(l[2:].strip()) + if a=='+': + html += '<div class="new">'+t+'</div>' + elif a=='-': + html += '<div class="old">'+t+'</div>' + elif a=='?': + pass + else: + html += '<div class="nochange">'+t+'</div>' + html += '</div>' + response = ''.join([ + u"Content-type: text/html; charset=utf-8\n\n", + theme.view(page, html), + ]) + print response.encode('utf-8')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/edit.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,20 @@ +from theme import Theme +from config import Config +from page import Page +import utils + +def edit(): + '''Display an edit form for the page.''' + + page_name = utils.get_query() or Config.front_page + page = Page(page_name) + theme = Theme() + form = ''.join([ + u'<input type="hidden" name="page" value="%s">' % page.name, + u'<textarea name="text" rows="18" cols="78">%s</textarea>' % page.get_text(), + ]) + response = ''.join([ + u"Content-type: text/html; charset=utf-8\n\n", + theme.edit(page, form), + ]) + print response.encode('utf-8')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/link.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,58 @@ +import urllib +from page import Page + +class Link: + '''Create a link on the page.''' + + def __init__(self, addr=None, title=None, kind=None, page=None, action=None): + self.addr = addr or '' + self.kind = kind + self.page = page or Page(addr) + if action: + page.action=action + self.title = title or self.page.get_title() + + def url(self): + if self.addr.startswith('http:'): + return self.addr + page = self.page + action = u'%s.cgi' % page.action + if page.version==0: + version = '' + else: + version = u'.%d' % page.version + return '?'.join([ + action, + urllib.quote(''.join([page.name, version]).encode('utf-8')), + ]) + + def html(self): + url = self.url() + link_class ='' + page = self.page + mopen = u'<span class="markup">[[</span>' + mclose = u'<span class="markup">]]</span>' + mkind=u'' + if self.kind=='navi' or self.kind=='tool': + mopen = '' + mclose = '' + elif self.kind=='tag' or self.kind=='tagged' or self.page.action=='tagged': + page.action = 'tag' + url = self.url() + link_class = 'taglink' + else: + if self.addr.startswith('http:'): + link_class = 'external' + else: + if page.exists(): + link_class = 'existant' + else: + link_class = 'nonexistant' + html = u''.join([ + u'<a href="%s" class="%s" title="%s">'%(url, link_class, self.addr), + mopen, + self.title, + mclose, + u'</a>', + ]) + return html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/page.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,83 @@ + +#vim: filencoding=utf-8 + +"""Implement page for the wiki. + +Contains code used for accessing page +contents and metadata. +""" + +import re, urllib +import codecs +from storage import StoredPage + +page_id_r = r'((?P<action>view|edit|diff|system|find|refs|tagged)[:=])? (?P<page> [\w _]+ ) (\.(?P<version>\d+))?' + +class Page: + """Access page metadata.""" + + def __init__(self, page_id): + """Create page object identified by the name.""" + self.is_system = False + self.toolbar = True + self.stored = None + page_re = re.compile(''.join(['^', page_id_r, '$']), re.U|re.X) + match = page_re.match(unicode(page_id)) + if match: + self.version = int(match.group('version') or 0) + self.action = match.group('action') or 'view' + name = match.group('page') + self.name = re.sub(r'[\s_]+', '_', name) + #self.name = urllib.quote(name.encode('utf-8')) + self.title = name.replace('_', ' ') + else: + self.name = u'Bad_page_name' + self.title = page_id + self.version = 0 + self.action = 'error' + + def get_id(self): + if self.action=='view': + action = '' + else: + action = u'%s:'%self.action + if self.version==0: + version = '' + else: + version = u'.%d'%self.version + name = self.name + return u''.join([action, name, version]) + + def get_title(self): + """Give human-readable page name.""" + return unicode(self.title) + + def get_stored(self): + if self.stored is None: + self.stored = StoredPage(self) + return self.stored + + def get_text(self): + stored = self.get_stored() + text = stored.get_text() + if text is None: + return 'No content' + else: + return text + + def save_text(self, text, user=None): + stored = self.get_stored() + stored.save_text(text, user) + + def exists(self): + stored = self.get_stored() + return stored.exists() + + def edit_date(self): + stored = self.get_stored() + return stored.edit_date() + + def edit_user(self): + stored = self.get_stored() + return stored.edit_user() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/parser.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,314 @@ +#!/usr/bin/env python + +import re +from link import Link + +class Parser: + '''Parse the page source into html.''' + + def _alts(self, alt_list): + return r'|'.join( + [re.escape(a) for a in alt_list] + ) + + def __init__(self): + + # Punctation characters that can end a word + punct = ''.join(['[',re.escape('-!?.,:;(+=\'"{[`></)]}'),']']) + # Bullet characters that can be used in lists + bullet = ''.join(['[ ]*[',re.escape('-+*@#'),']+']) + # Page title pattern + title = r"[\w ]+" + + # Characters that should get escaped in HTML + self.html_tab = { + r'>': '>', + r'<': '<', + r'&': '&', + } + html = r'(?P<html> %s)'%self._alts(self.html_tab.keys()) + + # Character sequences that get substituted with unicode characters + self.typo_tab = { + r' -- ': ' – ', + r' --- ': ' — ', + r'(R)': '®', + r'(C)': '©', + r'(TM)': '™', + r'...': '…', + } + typo = r"(?P<typo> %s)"%self._alts(self.typo_tab.keys()) + + # Smileys that get substituted with images + self.smiley_tab = { + ':)': 'style/emot-smile.png', + ':(': 'style/emot-sad.png', + ':D': 'style/emot-grin.png', + ';)': 'style/emot-wink.png', + ':P': 'style/emot-tongue.png', + ':[': 'style/emot-angry.png', + ':/': 'style/emot-hmm.png', + ':o': 'style/emot-oh.png', + } + smiley = r'((?<= \s)|^) (?P<smile>%(smiley)s) ((?=%(punct)s|\s)|$)'%{ + 'punct':punct, + 'smiley': self._alts(self.smiley_tab.keys()), + } + + # Inline quotes + quote = r'((?<= \s)|^) (?P<qopen>") | (?P<qclose>") ((?=%(punct)s|\s)|$)'%{'punct':punct} + + # Emphasis + emph1 = r'((?<= \s)|^) (?P<e1open>\*) | (?P<e1close>\*) ((?=%(punct)s|\s)|$)'%{'punct':punct} + emph2 = r'((?<= \s)|^) (?P<e2open>\*\*) | (?P<e2close>\*\*) ((?=%(punct)s|\s)|$)'%{'punct':punct} + emph3 = r'((?<= \s)|^) (?P<e3open>\*\*\*) | (?P<e3close>\*\*\*) ((?=%(punct)s|\s)|$)'%{'punct':punct} + + # Inlide code snippets, variable names, etc. + code = r'( (?<= \s) | ^ ) `(?P<code> ( [^`] | `` ){1,}? )` ((?=%(punct)s|\s)|$)'%{'punct':punct} + + # Links + link = r'\[\[ ( (?P<link_title>[^\|\]]+)\s*\|\s* )? (?P<link> %(page_id)s | http://[^\]]+ ) \]\]'%{'page_id':title} + text = '|'.join([ + html, + code, + emph3, + emph2, + emph1, + quote, + link, + typo, + smiley, + ]) + self.text_re = re.compile(text, re.X|re.M|re.U) + + block_code = r'^<(?P<trigger>\S+)\s*\n (?P<code>(.|\n){1,}?) \n<(?P=trigger)\s*$' + par_line = r'^(?P<par_line>.*)$' + hrule = r'^\s*(?P<hrule>----+)\s*$' + block_quote = r'^(?P<quote_level> [>]+) \s (?P<block_quote> .*) $' + ulist = r'^(?P<ulist_level> %(bullet)s) \s (?P<ulist> .*) $'%{'bullet':bullet} + dlist = r'^\s*(?P<dlist_term> [^:\n]+)::\s (?P<dlist> .*) $' + block = '|'.join([ + block_code, + block_quote, + hrule, + ulist, + dlist, + par_line, + ]) + self.block_re = re.compile(block, re.X|re.M|re.U) + + self.in_q = 0 + self.in_emph1 = 0 + self.in_emph2 = 0 + self.qlevel = 0 + self.dlevel = 0 + self.plevel = 0 + self.llevel = 0 + self.llevels = [] + + self.links = None + self.tags = None + + + def get_links(self,text): + if self.links is None: + self.format(text) + return self.links + + # Make a link + def _format_link(self, addr, title): + if not self.links: + self.links = [] + self.links.append(addr) + return Link(addr,title).html() + + # Escape HTML tags + def _escape_html(self, text): + return text.replace('&', '&').replace('<','<').replace('>','>') + + # Performs the substitutions while parsing paragraph text + def _sub_text(self, match): + g = match.groupdict() + if g.get('html', None): + return self.html_tab.get(g['html'], '?') + elif g.get('qopen', None): + self.in_q+=1 + return '<q>' + elif g.get('qclose', None): + self.in_q-=1 + return '</q>' + elif g.get('e1open', None): + self.in_emph1+=1 + return '<em><span class="markup">*</span>' + elif g.get('e1close', None): + self.in_emph1-=1 + return '<span class="markup">*</span></em>' + elif g.get('e2open', None): + self.in_emph1+=1 + return '<strong><span class="markup">**</span>' + elif g.get('e2close', None): + self.in_emph1-=1 + return '<span class="markup">**</span></strong>' + elif g.get('e3open', None): + self.in_emph1+=1 + self.in_emph2+=1 + return '<strong><em><span class="markup">***</span>' + elif g.get('e3close', None): + self.in_emph1-=1 + self.in_emph2-=1 + return '<span class="markup">***</span></em></strong>' + elif g.get('code', None): + html = u''.join([ + '<span class="markup">`</span>', + '<code>', + self._escape_html(g['code'].replace('``', '`')), + '</code>', + '<span class="markup">`</span>', + ]) + return html + elif g.get('link', None): + link = g['link'].strip() + title = g['link_title'] + return self._format_link(link, self._format_text(title)) + elif g.get('typo', None): + return self.typo_tab.get(g['typo'], '?') + elif g.get('smile', None): + smile = g['smile'] + icon = self.smiley_tab.get(smile, '') + return u'<img src="%s" alt="%s" class="smile">'%(icon, smile) + else: + return '?' + + def _format_text(self, text): + if text: + formatted_text = self.text_re.sub(self._sub_text, text) + else: + formatted_text = u'' + return formatted_text + + + def _sub_block(self, match): + g = match.groupdict() + if g.get('hrule', None): + return self.close_tags()+'<hr>' + elif g.get('head_level', None): + level = len(g['head_level']) + html = u''.join([ + self.close_tags(), + '<h%d>'%level, + g['head'], + '</h%d>'%level, + ]) + return html + elif g.get('code', None): + trigger = g.get('trigger', '') + html = u''.join([ + self.close_tags(), + '<pre>', + u'<div class="markup"><%s</div>'%trigger, + self._escape_html(g['code']), + u'<div class="markup"><%s</div>'%trigger, + '</pre>', + ]) + return html + elif g.get('quote_level', None): + saveqlevel = self.qlevel + self.qlevel = 0 + qopen = self.close_tags() + self.qlevel = saveqlevel + qclose ='' + level = len(g['quote_level']) + if level>self.qlevel: + qopen += '<blockquote>'*(level-self.qlevel) + else: + qclose += '</blockquote>'*(self.qlevel-level) + self.qlevel = level + return ''.join([qopen, self._format_text(g['block_quote']), qclose]) + elif g.get('dlist_term', None): + dopen = self.close_tags() + if not self.dlevel: + self.dlevel=1 + dopen += '<dl><dt>' + else: + dopen += '<dt>' + return ''.join([dopen, g['dlist_term'], '</dt>\n<dd>', g['dlist']]) + elif g.get('ulist_level', None): + return self.format_list(g) + elif g.has_key('par_line') and g['par_line']!=None: + if g['par_line'].strip(): + popen = '' + if self.plevel==0 and self.llevel==0 and self.qlevel==0 and self.dlevel==0: + self.plevel = 1 + popen = '<p>' + return ''.join([popen, self._format_text(g['par_line'])]) + else: + return self.close_tags() + else: + return str(g) + + def format_list(self, g): + savellevel = self.llevel + self.llevel = 0 + lopen = self.close_tags() + self.llevel = savellevel + + bullet = g['ulist_level'] +# bullet = bullet.strip() + try: + level = self.llevels.index(bullet) + except ValueError: + level = len(self.llevels) + self.llevels.append(bullet) + level += 1 + if level>self.llevel: + for i in range(self.llevel, level): + bullet = self.llevels[i] + lopen += '<ul>' + elif self.llevel>level: + for i in range(self.llevel-1, level-1, -1): + bullet = self.llevels[i] + lopen += '</li></ul>' + else: + lopen = '</li>' + self.llevel = level + self.llevels = self.llevels[:level] + return ''.join([lopen, '<li>' , self._format_text(g['ulist'])]) + + def close_tags(self): + clear = '' + if self.in_q: + clear+= '</q>'*self.in_q + self.in_q = 0 + if self.in_emph1: + clear+= '</em>'*self.in_emph1 + self.in_emph1 = 0 + if self.in_emph2: + clear+= '</strong>'*self.in_emph2 + self.in_emph2 = 0 + if self.plevel: + self.plevel = 0 + clear += '</p>' + if self.dlevel: + self.dlevel = 0 + clear += '</dd></dl>' + if self.qlevel: + clear += '</blockquote>'*self.qlevel + self.qlevel = 0 + if self.llevel: + clear = '' + for i in range(self.llevel-1, -1, -1): + clear += '</li></ul>' + self.llevels = [] + self.llevel = 0 + return clear + + def format(self, text): + formatted_text = self.block_re.sub(self._sub_block, text+'\n') + return formatted_text + +if __name__ == '__main__': + import sys + text = u''.join(sys.stdin.readlines()) + print '<html><head><title>test</title></head><body>' + print Parser().format(text) + print '</body></html>'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/recent.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,13 @@ +import storage +from link import Link +from theme import Theme +from config import Config + +def recent(): + theme = Theme() + pages = storage.recent_changes(16) + response = ''.join([ + u"Content-type: text/html; charset=utf-8\n\n", + theme.recent(pages), + ]) + print response.encode('utf-8')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/references.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,39 @@ +import shelve +from parser import Parser +from page import Page +from config import Config + +'''Track page references and tags.''' + +reffile = Config.ref_file + +def update(p, text): + par = Parser() + oldtext = p.get_text() + + # Update references + reftab = shelve.open(reffile) + links = par.get_links(oldtext) + if links: + for l in links: + if not l.startswith('http:'): + link = Page(l).name.encode('utf-8') + refs = reftab.get(link, []) + try: + refs.remove(p.name) + except: + pass + reftab[link]= refs + links = par.get_links(text) + if links: + for l in links: + if not l.startswith('http:'): + link = Page(l).name.encode('utf-8') + refs = reftab.get(link,[]) + refs.append(p.name) + reftab[link]= refs + reftab.close() + +def get(link): + reftab = shelve.open(reffile) + return reftab.get(link.encode('utf-8'), [])
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/refs.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,29 @@ +from theme import Theme +from config import Config +from page import Page +from parser import Parser +import utils, references + +def refs(): + '''Display the pages with specified link.''' + + page_name = utils.get_query() or Config.front_page + page = Page(page_name) + theme = Theme() + pages = references.get(page_name) + if pages: + html = ''.join([ + '<p>Following pages contain links to <q>%s</q> page:</p>' % page.get_title(), + '<ul class="results">', + utils.link_list(pages, 'view'), + '</ul>', + '<p>References on %s.</p>' % utils.plural_page(len(pages)), + ]) + else: + html = '<p>There are no pages linking to <q>%s</q>.</p>' % page.get_title() + + response = ''.join([ + u"Content-type: text/html; charset=utf-8\n\n", + theme.view(page, html), + ]) + print response.encode('utf-8')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/save.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,26 @@ +import cgi, urllib, os +import utils, references, link +from page import Page + +def save(): + '''Save new page content.''' + + user = utils.to_unicode(urllib.unquote(os.getenv('REMOTE_USER', ''))) + fields = get_fields() + page_name = fields.get('page') + page = Page(page_name) + text = utils.to_unicode(fields.get('text', '')) + references.update(page, text) + page.save_text(text, user=user) + print u'Location: %s\n\n' % link.Link(page_name).url() + +def get_fields(): + cgifields = cgi.FieldStorage(keep_blank_values=1) + fields = {} + for field in cgifields.keys(): + if type(cgifields[field]) == type([]): + fields[field] = [x.value for x in cgifields[field]] + else: + fields[field] = cgifields[field].value + return fields +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/storage.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,131 @@ +import os, codecs, time, urllib, re +from config import Config + +'''Storage backend.''' + + +def recent_changes(n): + """Return a list of n pages edited most recently.""" + filenames = os.listdir(Config.pages_dir) + files = [(os.stat(os.path.join(Config.pages_dir, name)).st_mtime, name) for name in filenames] + files.sort() + files.reverse() + changes = [name for (time, name) in files] + return changes[:n] + +def find_pages(word): + """Returns a list of pages with titles containing specified word.""" + search_re = re.compile(r'^.*%s.*$' % re.escape(word), re.I | re.U) + filenames = os.listdir(Config.pages_dir) + hits = filter(search_re.match, filenames) + return hits + +def page_index(): + """Returns a list of all the pages (even the deleted ones).""" + filenames = os.listdir(Config.pages_dir) + filenames.sort() + return filenames + +class StoredPage: + """Used to store and retrieve pages.""" + + def __init__(self, page): + self.version = page.version + self.filename = None + self.versions = None + self.text = None + self.qname = urllib.quote(page.name.encode('utf-8')) + self.path = os.path.join(Config.pages_dir, self.qname) + + def get_text(self): + """Return the page's contents.""" + if self.text: + return self.text + else: + if self.exists(): + self.text = u''.join( + codecs.open( + os.path.join(self.path, self.get_filename()), + 'r', + 'utf-8' + ).readlines()) + return self.text + else: + return None + + def exists(self): + """Detrmine the existence of a page.""" + try: + if not self.get_filename(): + return False + except: + return False + return os.access(os.path.join(self.path, self.get_filename()), os.R_OK) + + + def edit_date(self): + fn = self.get_filename() + try: + date = os.stat(os.path.join(self.path, fn)).st_mtime + except: + date = 0 + return date + + def edit_user(self): + v = self.get_versions() + try: + fn = v[self.version] + except: + fn = u'' + pos = fn.find(u'~') + if pos>=0: + user = fn[pos+1:] + else: + user = u'' + return user + + def save_text(self, text, user=''): + """Save a new revision of the page, with backup.""" +# stamp = '%s~%s' % (time.ctime(), user) + stamp = '%s_%s' % (time.strftime('%Y-%m-%d_%H.%M.%S'), user) + try: + os.mkdir(os.path.join(self.path)) + except: + pass + codecs.open(os.path.join(self.path, stamp), 'w', 'utf-8').write(text) + if text.strip(): + codecs.open(os.path.join(self.path, 'current'), 'w', 'utf-8').write(text) + else: + try: + os.remove(os.path.join(self.path, 'current')) + except: + pass + + def get_versions(self): + """Return a list of all the revisions of this page.""" + if self.versions is None: + if os.access(self.path, os.R_OK): + filenames = os.listdir(self.path) + files = [(os.stat(os.path.join(self.path, name)).st_mtime, name) for name in filenames] + files.sort() + files.reverse() + self.versions = [name for (time, name) in files] + try: + self.versions.remove('current') + except: + pass + else: + self.versions = [] + return self.versions + + def get_filename(self): + """Determine the name of file containing the page's text.""" + if self.version: + versions = self.get_versions() + try: + self.filename = versions[self.version] + except: + self.filename = 'error' + else: + self.filename = 'current' + return self.filename
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/tags.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,29 @@ +from theme import Theme +from config import Config +from page import Page +from parser import Parser +import utils, references + +def tags(): + '''Display the pages tagged with specified tag.''' + + page_name = utils.get_query() or Config.front_page + page = Page(page_name) + theme = Theme() + pages = references.tagpages(page.name) + if pages: + html = ''.join([ + '<p>Following pages are marked with <q>%s</q> tag:</p>' % page.get_title(), + '<ul class="results">', + utils.link_list(pages, 'view'), + '</ul>', + '<p>%s marked.</p>' % utils.plural_page(len(pages)), + ]) + else: + html = '<p>There are no pages marked with <q>%s</q> tag.</p>' % page.get_title() + + response = ''.join([ + u"Content-type: text/html; charset=utf-8\n\n", + theme.view(page, html), + ]) + print response.encode('utf-8')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/theme.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,142 @@ +#!/usr/bin/env python +import time +import link +from page import Page + +class Theme: + '''The looks of all the parts of the wiki.''' + + def __init__(self): + pass + + def html_head(self, title): + return u'''<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" +"http://www.w3.org/TR/html4/strict.dtd"> +<html><head> + <meta http-equiv="Content-type" content="text/html; charset=utf-8"> + <title>%s</title> + <link rel="Stylesheet" href="style/style.css"> +</head><body>'''%title + + def html_foot(self): + return u'''</body></html>''' + + def header(self): + return self.navibar() + + def navibar(self): + navilinks = [ 'Welcome', 'sandbox', 'help' ] + links = [] + for page in navilinks: + p = Page(page) + links.append(link.Link(None, p.get_title(), 'navi', p).html()) + links.append('<a href="recent.cgi">Recent changes</a>') + html = ''.join([ '<div class="navibar">', ''.join(links), '</div>' ]) + return html + + def toolbar(self, page): + p = page + if page.toolbar: + menu = u'<a href="#" target="menu">menu</a>' + saveaction = page.action + edit = link.Link(None, 'edit', 'tool', p, 'edit').html() + diff = link.Link(None, 'diff', 'tool', p, 'diff').html() + refs = link.Link(None, 'references', 'tool', p, 'refs').html() + saveversion = p.version + p.version = 0 + view = link.Link(None, 'view', 'tool', p, 'view').html() + if saveversion>0: + p.version = saveversion-1 + next = link.Link(None, 'next', 'tool', p, 'view').html() + else: + next = '' + p.version = saveversion+1 + prev = link.Link(None, 'prev', 'tool', p, 'view').html() + p.version = saveversion + toolbar = u'''<div class="toolbar">%s</div>''' % ''.join([ + prev, + next, + edit, + diff, + refs, + menu, + view, + ]) + else: + toolbar = u'' + return toolbar + + def page_head(self, page): + if page.action == 'edit': + title_class = 'action' + elif page.version: + title_class = 'older' + elif page.is_system: + title_class = 'system' + elif not page.exists(): + title_class = 'missing' + else: + title_class = 'title' + html = u''.join([ + u'<div class="page">', + u'<h1 class="%s">%s</h1>'%(title_class, page.get_title()) + ]) + return html + + def page_foot(self, page): + html = u'\n'.join([ + u'''</div>''', + self.toolbar(page), + ]) + return html + + def footer(self): + return u'' + + def recent(self, pages): + return ''.join([ + self.html_head('Recent changes'), + self.header(), + '<div class="page"><h1 class="system">Recent changes</h1>', + '<ul>', + ''.join('<li>%s</li>' % link.Link(page).html() for page in pages), + '</ul>', + '</div>', + self.html_foot(), + ]) + + def edit(self, page, form): + return ''.join([ + self.html_head(page.get_title()), + self.header(), + self.page_head(page), + '<form action="save.cgi" method="post" class="editor" enctype="utf-8"><div>', + form, + '<dvi class="toolbar"><input type="submit" value="save"></div>', + '</div></form>', + self.html_foot(), + ]) + + def view(self, page, content): + edited = page.edit_date() + user = page.edit_user() + if edited and user: + date = u'<div class="editdate">Last modified %s by %s</div>' % ( + str(time.strftime('%c %Z', time.localtime(edited))), + user + ) + elif edited: + date = u'<div class="editdate">Last modified %s.</div>' % str( + time.strftime('%c %Z', time.localtime(edited)) + ) + else: + date = u'<div class="editdate">Never modified.</div>' + return ''.join([ + self.html_head(page.get_title()), + self.header(), + self.page_head(page), + content, + date, + self.page_foot(page), + self.html_foot(), + ])
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/utils.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,35 @@ +import urllib, os +import link +from page import Page + +'''Various helper functions.''' + +def to_unicode(text): + try: + return unicode(text, 'utf-8') + except UnicodeError: + return unicode(text, fallback_charset) + +def get_query(): + return to_unicode(urllib.unquote(os.getenv('QUERY_STRING', ''))) + +def plural_page(number): + return plural(number, 'page') + +def plural(number, word): + if number<1: + return 'no %ss'%word + elif number==1: + return '1 %s'%word + else: + return '%d %ss'%(number,word) + +def link_list(pages, action=None): + links = [] + for page in pages: + p = Page(page) + if action: + p.action = action + links.append(link.Link(None, p.get_title(), None, p).html()) + html = ''.join([ ''.join([ '<li>', l, '</li>', ]) for l in links]) + return html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wiki/view.py Tue Nov 27 23:44:54 2007 +0100 @@ -0,0 +1,19 @@ +from theme import Theme +from config import Config +from page import Page +from parser import Parser +import utils + +def view(): + '''Display the formatted page.''' + + page_name = utils.get_query() or Config.front_page + page = Page(page_name) + theme = Theme() + content = Parser().format(page.get_text()) + response = ''.join([ +# u"404 Not found\n", + u"Content-type: text/html; charset=utf-8\n\n", + theme.view(page, content), + ]) + print response.encode('utf-8')
