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.'''
Binary file wiki/__init__.pyc has changed
--- /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'
Binary file wiki/config.pyc has changed
--- /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')
Binary file wiki/diff.pyc has changed
--- /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')
Binary file wiki/edit.pyc has changed
--- /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
Binary file wiki/link.pyc has changed
--- /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()
+
Binary file wiki/page.pyc has changed
--- /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'>': '&gt;',
+            r'<': '&lt;',
+            r'&': '&amp;',
+        }
+        html = r'(?P<html> %s)'%self._alts(self.html_tab.keys())
+       
+        # Character sequences that get substituted with unicode characters
+        self.typo_tab = {
+            r' -- ':  ' &ndash; ',
+            r' --- ':  ' &mdash; ',
+            r'(R)':   '&reg;',
+            r'(C)':   '&copy;',
+            r'(TM)':  '&trade;',
+            r'...':  '&hellip;',
+        }
+        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('&', '&amp;').replace('<','&lt;').replace('>','&gt;')
+
+    # 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">&lt;%s</div>'%trigger,
+                self._escape_html(g['code']),
+                u'<div class="markup">&lt;%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>'
Binary file wiki/parser.pyc has changed
--- /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')
Binary file wiki/recent.pyc has changed
--- /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'), [])
Binary file wiki/references.pyc has changed
--- /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')
Binary file wiki/refs.pyc has changed
--- /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
+
Binary file wiki/save.pyc has changed
--- /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
Binary file wiki/storage.pyc has changed
--- /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(),
+        ])
Binary file wiki/theme.pyc has changed
--- /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
Binary file wiki/utils.pyc has changed
--- /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')
Binary file wiki/view.pyc has changed